Unverified Commit b1d8a4e4 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Modify focus traversal policy search to use focus tree instead of widget tree (#121186)

* Modify focus traversal policy search to use focus tree instead of widget tree

* Eliminate unnecessary inherited widget

* Remove unintentional change

* Look for focus nodes without creating a dependency.

* Add test

* Review Changes

* Fix debug_test.dart

* Rebase onto master
parent 5d36cb77
......@@ -370,21 +370,17 @@ class Focus extends StatefulWidget {
/// given [BuildContext].
///
/// If no [Focus] node is found before reaching the nearest [FocusScope]
/// widget, or there is no [Focus] widget in scope, then this method will
/// throw an exception.
/// widget, or there is no [Focus] widget in the context, then this method
/// will throw an exception.
///
/// The `context` and `scopeOk` arguments must not be null.
///
/// Calling this function creates a dependency that will rebuild the given
/// context when the focus changes.
/// {@macro flutter.widgets.focus_scope.Focus.maybeOf}
///
/// See also:
///
/// * [maybeOf], which is similar to this function, but will return null
/// instead of throwing if it doesn't find a [Focus] node.
static FocusNode of(BuildContext context, { bool scopeOk = false }) {
final _FocusInheritedScope? marker = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
final FocusNode? node = marker?.notifier;
static FocusNode of(BuildContext context, { bool scopeOk = false, bool createDependency = true }) {
final FocusNode? node = Focus.maybeOf(context, scopeOk: scopeOk, createDependency: createDependency);
assert(() {
if (node == null) {
throw FlutterError(
......@@ -423,18 +419,24 @@ class Focus extends StatefulWidget {
/// widget, or there is no [Focus] widget in scope, then this method will
/// return null.
///
/// The `context` and `scopeOk` arguments must not be null.
///
/// Calling this function creates a dependency that will rebuild the given
/// context when the focus changes.
/// {@template flutter.widgets.focus_scope.Focus.maybeOf}
/// If `createDependency` is true (which is the default), calling this
/// function creates a dependency that will rebuild the given context when the
/// focus node gains or loses focus.
/// {@endtemplate}
///
/// See also:
///
/// * [of], which is similar to this function, but will throw an exception if
/// it doesn't find a [Focus] node instead of returning null.
static FocusNode? maybeOf(BuildContext context, { bool scopeOk = false }) {
final _FocusInheritedScope? marker = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
final FocusNode? node = marker?.notifier;
/// it doesn't find a [Focus] node, instead of returning null.
static FocusNode? maybeOf(BuildContext context, { bool scopeOk = false, bool createDependency = true }) {
final _FocusInheritedScope? scope;
if (createDependency) {
scope = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
} else {
scope = context.getInheritedWidgetOfExactType<_FocusInheritedScope>();
}
final FocusNode? node = scope?.notifier;
if (node == null) {
return null;
}
......@@ -776,16 +778,16 @@ class FocusScope extends Focus {
ValueChanged<bool>? onFocusChange,
}) = _FocusScopeWithExternalFocusNode;
/// Returns the [FocusScopeNode] of the [FocusScope] that most tightly
/// encloses the given [context].
/// Returns the [FocusNode.nearestScope] of the [Focus] or [FocusScope] that
/// most tightly encloses the given [context].
///
/// If this node doesn't have a [Focus] widget ancestor, then the
/// [FocusManager.rootScope] is returned.
/// If this node doesn't have a [Focus] or [FocusScope] widget ancestor, then
/// the [FocusManager.rootScope] is returned.
///
/// The [context] argument must not be null.
static FocusScopeNode of(BuildContext context) {
final _FocusInheritedScope? marker = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
return marker?.notifier?.nearestScope ?? context.owner!.focusManager.rootScope;
/// {@macro flutter.widgets.focus_scope.Focus.maybeOf}
static FocusScopeNode of(BuildContext context, { bool createDependency = true }) {
return Focus.maybeOf(context, scopeOk: true, createDependency: createDependency)?.nearestScope
?? context.owner!.focusManager.rootScope;
}
@override
......
......@@ -2255,10 +2255,10 @@ abstract class BuildContext {
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
///
/// The [aspect] parameter is only used when `T` is an [InheritedWidget]
/// subclass that supports partial updates, like [InheritedModel]. It
/// specifies what "aspect" of the inherited widget this context depends on,
/// where the meaning of the aspect is determined by the specific subclass.
/// The [aspect] parameter is only used when `T` is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
/// {@endtemplate}
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
......
......@@ -162,7 +162,6 @@ void main() {
' Focus\n'
' _FocusInheritedScope\n'
' Focus\n'
' _FocusTraversalGroupScope\n'
' FocusTraversalGroup\n'
' AbsorbPointer\n'
' Listener\n'
......@@ -201,7 +200,6 @@ void main() {
' TapRegionSurface\n'
' _FocusInheritedScope\n'
' Focus\n'
' _FocusTraversalGroupScope\n'
' FocusTraversalGroup\n'
' _ActionsScope\n'
' Actions\n'
......
......@@ -2201,6 +2201,48 @@ void main() {
expect(unfocusableNode.hasFocus, isFalse);
});
testWidgets('Group applies correct policy if focus tree is different from widget tree.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
final GlobalKey key4 = GlobalKey(debugLabel: '4');
final FocusNode focusNode = FocusNode(debugLabel: 'child');
final FocusNode parentFocusNode = FocusNode(debugLabel: 'parent');
await tester.pumpWidget(
Column(
children: <Widget>[
FocusTraversalGroup(
policy: WidgetOrderTraversalPolicy(),
child: Focus(
child: Focus.withExternalFocusNode(
key: key1,
// This makes focusNode be a child of parentFocusNode instead
// of the surrounding Focus.
parentNode: parentFocusNode,
focusNode: focusNode,
child: Container(key: key2),
),
),
),
FocusTraversalGroup(
policy: SkipAllButFirstAndLastPolicy(),
child: FocusScope(
child: Focus.withExternalFocusNode(
key: key3,
focusNode: parentFocusNode,
child: Container(key: key4),
),
),
),
],
),
);
expect(focusNode.parent, equals(parentFocusNode));
expect(FocusTraversalGroup.maybeOf(key2.currentContext!), const TypeMatcher<SkipAllButFirstAndLastPolicy>());
expect(FocusTraversalGroup.of(key2.currentContext!), const TypeMatcher<SkipAllButFirstAndLastPolicy>());
});
testWidgets("Descendants of FocusTraversalGroup aren't traversable if descendantsAreTraversable is false.", (WidgetTester tester) async {
final FocusNode node1 = FocusNode();
final FocusNode node2 = FocusNode();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment