1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/material.dart';
6/// @docImport 'package:flutter/rendering.dart';
7library;
8
9import 'dart:async';
10import 'dart:ui';
11
12import 'package:flutter/foundation.dart';
13import 'package:flutter/gestures.dart';
14import 'package:flutter/painting.dart';
15import 'package:flutter/scheduler.dart';
16import 'package:flutter/semantics.dart';
17import 'package:flutter/services.dart';
18
19import 'binding.dart';
20import 'focus_scope.dart';
21import 'focus_traversal.dart';
22import 'framework.dart';
23
24/// Setting to true will cause extensive logging to occur when focus changes occur.
25///
26/// Can be used to debug focus issues: each time the focus changes, the focus
27/// tree will be printed and requests for focus and other focus operations will
28/// be logged.
29bool debugFocusChanges = false;
30
31// When using _focusDebug, always call it like so:
32//
33// assert(_focusDebug(() => 'Blah $foo'));
34//
35// It needs to be inside the assert in order to be removed in release mode, and
36// it needs to use a closure to generate the string in order to avoid string
37// interpolation when debugFocusChanges is false.
38//
39// It will throw a StateError if you try to call it when the app is in release
40// mode.
41bool _focusDebug(String Function() messageFunc, [Iterable<Object> Function()? detailsFunc]) {
42 if (kReleaseMode) {
43 throw StateError(
44 '_focusDebug was called in Release mode. It should always be wrapped in '
45 'an assert. Always call _focusDebug like so:\n'
46 r" assert(_focusDebug(() => 'Blah $foo'));",
47 );
48 }
49 if (!debugFocusChanges) {
50 return true;
51 }
52 debugPrint('FOCUS: ${messageFunc()}');
53 final Iterable<Object> details = detailsFunc?.call() ?? const <Object>[];
54 if (details.isNotEmpty) {
55 for (final Object detail in details) {
56 debugPrint(' $detail');
57 }
58 }
59 // Return true so that it can be used inside of an assert.
60 return true;
61}
62
63/// An enum that describes how to handle a key event handled by a
64/// [FocusOnKeyCallback] or [FocusOnKeyEventCallback].
65enum KeyEventResult {
66 /// The key event has been handled, and the event should not be propagated to
67 /// other key event handlers.
68 handled,
69
70 /// The key event has not been handled, and the event should continue to be
71 /// propagated to other key event handlers, even non-Flutter ones.
72 ignored,
73
74 /// The key event has not been handled, but the key event should not be
75 /// propagated to other key event handlers.
76 ///
77 /// It will be returned to the platform embedding to be propagated to text
78 /// fields and non-Flutter key event handlers on the platform.
79 skipRemainingHandlers,
80}
81
82/// Combine the results returned by multiple [FocusOnKeyCallback]s or
83/// [FocusOnKeyEventCallback]s.
84///
85/// If any callback returns [KeyEventResult.handled], the node considers the
86/// message handled; otherwise, if any callback returns
87/// [KeyEventResult.skipRemainingHandlers], the node skips the remaining
88/// handlers without preventing the platform to handle; otherwise the node is
89/// ignored.
90KeyEventResult combineKeyEventResults(Iterable<KeyEventResult> results) {
91 bool hasSkipRemainingHandlers = false;
92 for (final KeyEventResult result in results) {
93 switch (result) {
94 case KeyEventResult.handled:
95 return KeyEventResult.handled;
96 case KeyEventResult.skipRemainingHandlers:
97 hasSkipRemainingHandlers = true;
98 case KeyEventResult.ignored:
99 break;
100 }
101 }
102 return hasSkipRemainingHandlers ? KeyEventResult.skipRemainingHandlers : KeyEventResult.ignored;
103}
104
105/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
106/// to receive key events.
107///
108/// This kind of callback is deprecated and will be removed at a future date.
109/// Use [FocusOnKeyEventCallback] and associated APIs instead.
110///
111/// The [node] is the node that received the event.
112///
113/// Returns a [KeyEventResult] that describes how, and whether, the key event
114/// was handled.
115@Deprecated(
116 'Use FocusOnKeyEventCallback instead. '
117 'This feature was deprecated after v3.18.0-2.0.pre.',
118)
119typedef FocusOnKeyCallback = KeyEventResult Function(FocusNode node, RawKeyEvent event);
120
121/// Signature of a callback used by [Focus.onKeyEvent] and [FocusScope.onKeyEvent]
122/// to receive key events.
123///
124/// The [node] is the node that received the event.
125///
126/// Returns a [KeyEventResult] that describes how, and whether, the key event
127/// was handled.
128typedef FocusOnKeyEventCallback = KeyEventResult Function(FocusNode node, KeyEvent event);
129
130/// Signature of a callback used by [FocusManager.addEarlyKeyEventHandler] and
131/// [FocusManager.addLateKeyEventHandler].
132///
133/// The `event` parameter is a [KeyEvent] that is being sent to the callback to
134/// be handled.
135///
136/// The [KeyEventResult] return value indicates whether or not the event will
137/// continue to be propagated. If the value returned is [KeyEventResult.handled]
138/// or [KeyEventResult.skipRemainingHandlers], then the event will not continue
139/// to be propagated.
140typedef OnKeyEventCallback = KeyEventResult Function(KeyEvent event);
141
142// Represents a pending autofocus request.
143@immutable
144class _Autofocus {
145 const _Autofocus({required this.scope, required this.autofocusNode});
146
147 final FocusScopeNode scope;
148 final FocusNode autofocusNode;
149
150 // Applies the autofocus request, if the node is still attached to the
151 // original scope and the scope has no focused child.
152 //
153 // The widget tree is responsible for calling reparent/detach on attached
154 // nodes to keep their parent/manager information up-to-date, so here we can
155 // safely check if the scope/node involved in each autofocus request is
156 // still attached, and discard the ones which are no longer attached to the
157 // original manager.
158 void applyIfValid(FocusManager manager) {
159 final bool shouldApply =
160 (scope.parent != null || identical(scope, manager.rootScope)) &&
161 identical(scope._manager, manager) &&
162 scope.focusedChild == null &&
163 autofocusNode.ancestors.contains(scope);
164 if (shouldApply) {
165 assert(_focusDebug(() => 'Applying autofocus: $autofocusNode'));
166 autofocusNode._doRequestFocus(findFirstFocus: true);
167 } else {
168 assert(_focusDebug(() => 'Autofocus request discarded for node: $autofocusNode.'));
169 }
170 }
171}
172
173/// An attachment point for a [FocusNode].
174///
175/// Using a [FocusAttachment] is rarely needed, unless building something
176/// akin to the [Focus] or [FocusScope] widgets from scratch.
177///
178/// Once created, a [FocusNode] must be attached to the widget tree by its
179/// _host_ [StatefulWidget] via a [FocusAttachment] object. [FocusAttachment]s
180/// are owned by the [StatefulWidget] that hosts a [FocusNode] or
181/// [FocusScopeNode]. There can be multiple [FocusAttachment]s for each
182/// [FocusNode], but the node will only ever be attached to one of them at a
183/// time.
184///
185/// This attachment is created by calling [FocusNode.attach], usually from the
186/// host widget's [State.initState] method. If the widget is updated to have a
187/// different focus node, then the new node needs to be attached in
188/// [State.didUpdateWidget], after calling [detach] on the previous
189/// [FocusAttachment]. Once detached, the attachment is defunct and will no
190/// longer make changes to the [FocusNode] through [reparent].
191///
192/// Without these attachment points, it would be possible for a focus node to
193/// simultaneously be attached to more than one part of the widget tree during
194/// the build stage.
195class FocusAttachment {
196 /// A private constructor, because [FocusAttachment]s are only to be created
197 /// by [FocusNode.attach].
198 FocusAttachment._(this._node);
199
200 // The focus node that this attachment manages an attachment for. The node may
201 // not yet have a parent, or may have been detached from this attachment, so
202 // don't count on this node being in a usable state.
203 final FocusNode _node;
204
205 /// Returns true if the associated node is attached to this attachment.
206 ///
207 /// It is possible to be attached to the widget tree, but not be placed in
208 /// the focus tree (i.e. to not have a parent yet in the focus tree).
209 bool get isAttached => _node._attachment == this;
210
211 /// Detaches the [FocusNode] this attachment point is associated with from the
212 /// focus tree, and disconnects it from this attachment point.
213 ///
214 /// Calling [FocusNode.dispose] will also automatically detach the node.
215 void detach() {
216 assert(
217 _focusDebug(
218 () => 'Detaching node:',
219 () => <Object>[_node, 'With enclosing scope ${_node.enclosingScope}'],
220 ),
221 );
222 if (isAttached) {
223 if (_node.hasPrimaryFocus ||
224 (_node._manager != null && _node._manager!._markedForFocus == _node)) {
225 _node.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
226 }
227 // This node is no longer in the tree, so shouldn't send notifications anymore.
228 _node._manager?._markDetached(_node);
229 _node._parent?._removeChild(_node);
230 _node._attachment = null;
231 assert(
232 !_node.hasPrimaryFocus,
233 'Node ${_node.debugLabel ?? _node} still has primary focus while being detached.',
234 );
235 assert(
236 _node._manager?._markedForFocus != _node,
237 'Node ${_node.debugLabel ?? _node} still marked for focus while being detached.',
238 );
239 }
240 assert(!isAttached);
241 }
242
243 /// Ensures that the [FocusNode] attached at this attachment point has the
244 /// proper parent node, changing it if necessary.
245 ///
246 /// If given, ensures that the given [parent] node is the parent of the node
247 /// that is attached at this attachment point, changing it if necessary.
248 /// However, it is usually not necessary to supply an explicit parent, since
249 /// [reparent] will use [Focus.of] to determine the correct parent node for
250 /// the context given in [FocusNode.attach].
251 ///
252 /// If [isAttached] is false, then calling this method does nothing.
253 ///
254 /// Should be called whenever the associated widget is rebuilt in order to
255 /// maintain the focus hierarchy.
256 ///
257 /// A [StatefulWidget] that hosts a [FocusNode] should call this method on the
258 /// node it hosts during its [State.build] or [State.didChangeDependencies]
259 /// methods in case the widget is moved from one location in the tree to
260 /// another location that has a different [FocusScope] or context.
261 ///
262 /// The optional [parent] argument must be supplied when not using [Focus] and
263 /// [FocusScope] widgets to build the focus tree, or if there is a need to
264 /// supply the parent explicitly (which are both uncommon).
265 void reparent({FocusNode? parent}) {
266 if (isAttached) {
267 assert(_node.context != null);
268 parent ??= Focus.maybeOf(_node.context!, scopeOk: true);
269 parent ??= _node.context!.owner!.focusManager.rootScope;
270 parent._reparent(_node);
271 }
272 }
273}
274
275/// Describe what should happen after [FocusNode.unfocus] is called.
276///
277/// See also:
278///
279/// * [FocusNode.unfocus], which takes this as its `disposition` parameter.
280enum UnfocusDisposition {
281 /// Focus the nearest focusable enclosing scope of this node, but do not
282 /// descend to locate the leaf [FocusScopeNode.focusedChild] the way
283 /// [previouslyFocusedChild] does.
284 ///
285 /// Focusing the scope in this way clears the [FocusScopeNode.focusedChild]
286 /// history for the enclosing scope when it receives focus. Because of this,
287 /// calling a traversal method like [FocusNode.nextFocus] after unfocusing
288 /// will cause the [FocusTraversalPolicy] to pick the node it thinks should be
289 /// first in the scope.
290 ///
291 /// This is the default disposition for [FocusNode.unfocus].
292 scope,
293
294 /// Focus the previously focused child of the nearest focusable enclosing
295 /// scope of this node.
296 ///
297 /// If there is no previously focused child, then this is equivalent to
298 /// using the [scope] disposition.
299 ///
300 /// Unfocusing with this disposition will cause [FocusNode.unfocus] to walk up
301 /// the tree to the nearest focusable enclosing scope, then start to walk down
302 /// the tree, looking for a focused child at its
303 /// [FocusScopeNode.focusedChild].
304 ///
305 /// If the [FocusScopeNode.focusedChild] is a scope, then look for its
306 /// [FocusScopeNode.focusedChild], and so on, finding the leaf
307 /// [FocusScopeNode.focusedChild] that is not a scope, or, failing that, a
308 /// leaf scope that has no focused child.
309 previouslyFocusedChild,
310}
311
312/// An object that can be used by a stateful widget to obtain the keyboard focus
313/// and to handle keyboard events.
314///
315/// _Please see the [Focus] and [FocusScope] widgets, which are utility widgets
316/// that manage their own [FocusNode]s and [FocusScopeNode]s, respectively. If
317/// they aren't appropriate, [FocusNode]s can be managed directly, but doing this
318/// is rare._
319///
320/// [FocusNode]s are persistent objects that form a _focus tree_ that is a
321/// representation of the widgets in the hierarchy that are interested in focus.
322/// A focus node might need to be created if it is passed in from an ancestor of
323/// a [Focus] widget to control the focus of the children from the ancestor, or
324/// a widget might need to host one if the widget subsystem is not being used,
325/// or if the [Focus] and [FocusScope] widgets provide insufficient control.
326///
327/// [FocusNode]s are organized into _scopes_ (see [FocusScopeNode]), which form
328/// sub-trees of nodes that restrict traversal to a group of nodes. Within a
329/// scope, the most recent nodes to have focus are remembered, and if a node is
330/// focused and then unfocused, the previous node receives focus again.
331///
332/// The focus node hierarchy can be traversed using the [parent], [children],
333/// [ancestors] and [descendants] accessors.
334///
335/// [FocusNode]s are [ChangeNotifier]s, so a listener can be registered to
336/// receive a notification when the focus changes. Listeners will also be
337/// notified when [skipTraversal], [canRequestFocus], [descendantsAreFocusable],
338/// and [descendantsAreTraversable] properties are updated. If the [Focus] and
339/// [FocusScope] widgets are being used to manage the nodes, consider
340/// establishing an [InheritedWidget] dependency on them by calling [Focus.of]
341/// or [FocusScope.of] instead. [FocusNode.hasFocus] can also be used to
342/// establish a similar dependency, especially if all that is needed is to
343/// determine whether or not the widget is focused at build time.
344///
345/// To see the focus tree in the debug console, call [debugDumpFocusTree]. To
346/// get the focus tree as a string, call [debugDescribeFocusTree].
347///
348/// {@template flutter.widgets.FocusNode.lifecycle}
349/// ## Lifecycle
350///
351/// There are several actors involved in the lifecycle of a
352/// [FocusNode]/[FocusScopeNode]. They are created and disposed by their
353/// _owner_, attached, detached, and re-parented using a [FocusAttachment] by
354/// their _host_ (which must be owned by the [State] of a [StatefulWidget]), and
355/// they are managed by the [FocusManager]. Different parts of the [FocusNode]
356/// API are intended for these different actors.
357///
358/// [FocusNode]s (and hence [FocusScopeNode]s) are persistent objects that form
359/// part of a _focus tree_ that is a sparse representation of the widgets in the
360/// hierarchy that are interested in receiving keyboard events. They must be
361/// managed like other persistent state, which is typically done by a
362/// [StatefulWidget] that owns the node. A stateful widget that owns a focus
363/// scope node must call [dispose] from its [State.dispose] method.
364///
365/// Once created, a [FocusNode] must be attached to the widget tree via a
366/// [FocusAttachment] object. This attachment is created by calling [attach],
367/// usually from the [State.initState] method. If the hosting widget is updated
368/// to have a different focus node, then the updated node needs to be attached
369/// in [State.didUpdateWidget], after calling [FocusAttachment.detach] on the
370/// previous [FocusAttachment].
371///
372/// Because [FocusNode]s form a sparse representation of the widget tree, they
373/// must be updated whenever the widget tree is rebuilt. This is done by calling
374/// [FocusAttachment.reparent], usually from the [State.build] or
375/// [State.didChangeDependencies] methods of the widget that represents the
376/// focused region, so that the [BuildContext] assigned to the [FocusScopeNode]
377/// can be tracked (the context is used to obtain the [RenderObject], from which
378/// the geometry of focused regions can be determined).
379///
380/// Creating a [FocusNode] each time [State.build] is invoked will cause the
381/// focus to be lost each time the widget is built, which is usually not desired
382/// behavior (call [unfocus] if losing focus is desired).
383///
384/// If, as is common, the hosting [StatefulWidget] is also the owner of the
385/// focus node, then it will also call [dispose] from its [State.dispose] (in
386/// which case the [FocusAttachment.detach] may be skipped, since dispose will
387/// automatically detach). If another object owns the focus node, then it must
388/// call [dispose] when the node is done being used.
389/// {@endtemplate}
390///
391/// {@template flutter.widgets.FocusNode.keyEvents}
392/// ## Key Event Propagation
393///
394/// The [FocusManager] receives key events from [HardwareKeyboard] and will pass
395/// them to the focused nodes. It starts with the node with the primary focus,
396/// and will call the [onKeyEvent] callback for that node. If the callback
397/// returns [KeyEventResult.ignored], indicating that it did not handle the
398/// event, the [FocusManager] will move to the parent of that node and call its
399/// [onKeyEvent]. If that [onKeyEvent] returns [KeyEventResult.handled], then it
400/// will stop propagating the event. If it reaches the root [FocusScopeNode],
401/// [FocusManager.rootScope], the event is discarded.
402/// {@endtemplate}
403///
404/// ## Focus Traversal
405///
406/// The term _traversal_, sometimes called _tab traversal_, refers to moving the
407/// focus from one widget to the next in a particular order (also sometimes
408/// referred to as the _tab order_, since the TAB key is often bound to the
409/// action to move to the next widget).
410///
411/// To give focus to the logical _next_ or _previous_ widget in the UI, call the
412/// [nextFocus] or [previousFocus] methods. To give the focus to a widget in a
413/// particular direction, call the [focusInDirection] method.
414///
415/// The policy for what the _next_ or _previous_ widget is, or the widget in a
416/// particular direction, is determined by the [FocusTraversalPolicy] in force.
417///
418/// The ambient policy is determined by looking up the widget hierarchy for a
419/// [FocusTraversalGroup] widget, and obtaining the focus traversal policy from
420/// it. Different focus nodes can inherit difference policies, so part of the
421/// app can go in a predefined order (using [OrderedTraversalPolicy]), and part
422/// can go in reading order (using [ReadingOrderTraversalPolicy]), depending
423/// upon the use case.
424///
425/// Predefined policies include [WidgetOrderTraversalPolicy],
426/// [ReadingOrderTraversalPolicy], [OrderedTraversalPolicy], and
427/// [DirectionalFocusTraversalPolicyMixin], but custom policies can be built
428/// based upon these policies. See [FocusTraversalPolicy] for more information.
429///
430/// {@tool dartpad}
431/// This example shows how a FocusNode should be managed if not using the
432/// [Focus] or [FocusScope] widgets. See the [Focus] widget for a similar
433/// example using [Focus] and [FocusScope] widgets.
434///
435/// ** See code in examples/api/lib/widgets/focus_manager/focus_node.0.dart **
436/// {@end-tool}
437///
438/// See also:
439///
440/// * [Focus], a widget that manages a [FocusNode] and provides access to focus
441/// information and actions to its descendant widgets.
442/// * [FocusTraversalGroup], a widget used to group together and configure the
443/// focus traversal policy for a widget subtree.
444/// * [FocusManager], a singleton that manages the primary focus and distributes
445/// key events to focused nodes.
446/// * [FocusTraversalPolicy], a class used to determine how to move the focus to
447/// other nodes.
448class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
449 /// Creates a focus node.
450 ///
451 /// The [debugLabel] is ignored on release builds.
452 ///
453 /// To receive key events that focuses on this node, pass a listener to
454 /// `onKeyEvent`.
455 FocusNode({
456 String? debugLabel,
457 @Deprecated(
458 'Use onKeyEvent instead. '
459 'This feature was deprecated after v3.18.0-2.0.pre.',
460 )
461 this.onKey,
462 this.onKeyEvent,
463 bool skipTraversal = false,
464 bool canRequestFocus = true,
465 bool descendantsAreFocusable = true,
466 bool descendantsAreTraversable = true,
467 }) : _skipTraversal = skipTraversal,
468 _canRequestFocus = canRequestFocus,
469 _descendantsAreFocusable = descendantsAreFocusable,
470 _descendantsAreTraversable = descendantsAreTraversable {
471 // Set it via the setter so that it does nothing on release builds.
472 this.debugLabel = debugLabel;
473
474 if (kFlutterMemoryAllocationsEnabled) {
475 ChangeNotifier.maybeDispatchObjectCreation(this);
476 }
477 }
478
479 /// If true, tells the focus traversal policy to skip over this node for
480 /// purposes of the traversal algorithm.
481 ///
482 /// This may be used to place nodes in the focus tree that may be focused, but
483 /// not traversed, allowing them to receive key events as part of the focus
484 /// chain, but not be traversed to via focus traversal.
485 ///
486 /// This is different from [canRequestFocus] because it only implies that the
487 /// node can't be reached via traversal, not that it can't be focused. It may
488 /// still be focused explicitly.
489 bool get skipTraversal {
490 if (_skipTraversal) {
491 return true;
492 }
493 for (final FocusNode ancestor in ancestors) {
494 if (!ancestor.descendantsAreTraversable) {
495 return true;
496 }
497 }
498 return false;
499 }
500
501 bool _skipTraversal;
502 set skipTraversal(bool value) {
503 if (value != _skipTraversal) {
504 _skipTraversal = value;
505 _manager?._markPropertiesChanged(this);
506 }
507 }
508
509 /// If true, this focus node may request the primary focus.
510 ///
511 /// Defaults to true. Set to false if you want this node to do nothing when
512 /// [requestFocus] is called on it.
513 ///
514 /// If set to false on a [FocusScopeNode], will cause all of the children of
515 /// the scope node to not be focusable.
516 ///
517 /// If set to false on a [FocusNode], it will not affect the focusability of
518 /// children of the node.
519 ///
520 /// The [hasFocus] member can still return true if this node is the ancestor
521 /// of a node with primary focus.
522 ///
523 /// This is different than [skipTraversal] because [skipTraversal] still
524 /// allows the node to be focused, just not traversed to via the
525 /// [FocusTraversalPolicy].
526 ///
527 /// Setting [canRequestFocus] to false implies that the node will also be
528 /// skipped for traversal purposes.
529 ///
530 /// See also:
531 ///
532 /// * [FocusTraversalGroup], a widget used to group together and configure the
533 /// focus traversal policy for a widget subtree.
534 /// * [FocusTraversalPolicy], a class that can be extended to describe a
535 /// traversal policy.
536 bool get canRequestFocus => _canRequestFocus && ancestors.every(_allowDescendantsToBeFocused);
537 static bool _allowDescendantsToBeFocused(FocusNode ancestor) => ancestor.descendantsAreFocusable;
538
539 bool _canRequestFocus;
540 @mustCallSuper
541 set canRequestFocus(bool value) {
542 if (value != _canRequestFocus) {
543 // Have to set this first before unfocusing, since it checks this to cull
544 // unfocusable, previously-focused children.
545 _canRequestFocus = value;
546 if (hasFocus && !value) {
547 unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
548 }
549 _manager?._markPropertiesChanged(this);
550 }
551 }
552
553 /// If false, will disable focus for all of this node's descendants.
554 ///
555 /// Defaults to true. Does not affect focusability of this node: for that,
556 /// use [canRequestFocus].
557 ///
558 /// If any descendants are focused when this is set to false, they will be
559 /// unfocused. When [descendantsAreFocusable] is set to true again, they will
560 /// not be refocused, although they will be able to accept focus again.
561 ///
562 /// Does not affect the value of [canRequestFocus] on the descendants.
563 ///
564 /// If a descendant node loses focus when this value is changed, the focus
565 /// will move to the scope enclosing this node.
566 ///
567 /// See also:
568 ///
569 /// * [ExcludeFocus], a widget that uses this property to conditionally
570 /// exclude focus for a subtree.
571 /// * [descendantsAreTraversable], which makes this widget's descendants
572 /// untraversable.
573 /// * [ExcludeFocusTraversal], a widget that conditionally excludes focus
574 /// traversal for a subtree.
575 /// * [Focus], a widget that exposes this setting as a parameter.
576 /// * [FocusTraversalGroup], a widget used to group together and configure
577 /// the focus traversal policy for a widget subtree that also has a
578 /// `descendantsAreFocusable` parameter that prevents its children from
579 /// being focused.
580 bool get descendantsAreFocusable => _descendantsAreFocusable;
581 bool _descendantsAreFocusable;
582 @mustCallSuper
583 set descendantsAreFocusable(bool value) {
584 if (value == _descendantsAreFocusable) {
585 return;
586 }
587 // Set _descendantsAreFocusable before unfocusing, so the scope won't try
588 // and focus any of the children here again if it is false.
589 _descendantsAreFocusable = value;
590 if (!value && hasFocus) {
591 unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
592 }
593 _manager?._markPropertiesChanged(this);
594 }
595
596 /// If false, tells the focus traversal policy to skip over for all of this
597 /// node's descendants for purposes of the traversal algorithm.
598 ///
599 /// Defaults to true. Does not affect the focus traversal of this node: for
600 /// that, use [skipTraversal].
601 ///
602 /// Does not affect the value of [FocusNode.skipTraversal] on the
603 /// descendants. Does not affect focusability of the descendants.
604 ///
605 /// See also:
606 ///
607 /// * [ExcludeFocusTraversal], a widget that uses this property to conditionally
608 /// exclude focus traversal for a subtree.
609 /// * [descendantsAreFocusable], which makes this widget's descendants
610 /// unfocusable.
611 /// * [ExcludeFocus], a widget that conditionally excludes focus for a subtree.
612 /// * [FocusTraversalGroup], a widget used to group together and configure
613 /// the focus traversal policy for a widget subtree that also has an
614 /// `descendantsAreFocusable` parameter that prevents its children from
615 /// being focused.
616 bool get descendantsAreTraversable => _descendantsAreTraversable;
617 bool _descendantsAreTraversable;
618 @mustCallSuper
619 set descendantsAreTraversable(bool value) {
620 if (value != _descendantsAreTraversable) {
621 _descendantsAreTraversable = value;
622 _manager?._markPropertiesChanged(this);
623 }
624 }
625
626 /// The context that was supplied to [attach].
627 ///
628 /// This is typically the context for the widget that is being focused, as it
629 /// is used to determine the bounds of the widget.
630 BuildContext? get context => _context;
631 BuildContext? _context;
632
633 /// Called if this focus node receives a key event while focused (i.e. when
634 /// [hasFocus] returns true).
635 ///
636 /// This property is deprecated and will be removed at a future date. Use
637 /// [onKeyEvent] instead.
638 ///
639 /// This is a legacy API based on [RawKeyEvent] and will be deprecated in the
640 /// future. Prefer [onKeyEvent] instead.
641 ///
642 /// {@macro flutter.widgets.FocusNode.keyEvents}
643 @Deprecated(
644 'Use onKeyEvent instead. '
645 'This feature was deprecated after v3.18.0-2.0.pre.',
646 )
647 FocusOnKeyCallback? onKey;
648
649 /// Called if this focus node receives a key event while focused (i.e. when
650 /// [hasFocus] returns true).
651 ///
652 /// {@macro flutter.widgets.FocusNode.keyEvents}
653 FocusOnKeyEventCallback? onKeyEvent;
654
655 FocusManager? _manager;
656 List<FocusNode>? _ancestors;
657 List<FocusNode>? _descendants;
658 bool _hasKeyboardToken = false;
659
660 /// Returns the parent node for this object.
661 ///
662 /// All nodes except for the root [FocusScopeNode] ([FocusManager.rootScope])
663 /// will be given a parent when they are added to the focus tree, which is
664 /// done using [FocusAttachment.reparent].
665 FocusNode? get parent => _parent;
666 FocusNode? _parent;
667
668 /// An iterator over the children of this node.
669 Iterable<FocusNode> get children => _children;
670 final List<FocusNode> _children = <FocusNode>[];
671
672 /// An iterator over the children that are allowed to be traversed by the
673 /// [FocusTraversalPolicy].
674 ///
675 /// Returns the list of focusable, traversable children of this node,
676 /// regardless of those settings on this focus node. Will return an empty
677 /// iterable if [descendantsAreFocusable] is false.
678 ///
679 /// See also
680 ///
681 /// * [traversalDescendants], which traverses all of the node's descendants,
682 /// not just the immediate children.
683 Iterable<FocusNode> get traversalChildren {
684 if (!descendantsAreFocusable) {
685 return const Iterable<FocusNode>.empty();
686 }
687 return children.where((FocusNode node) => !node.skipTraversal && node.canRequestFocus);
688 }
689
690 /// A debug label that is used for diagnostic output.
691 ///
692 /// Will always return null in release builds.
693 String? get debugLabel => _debugLabel;
694 String? _debugLabel;
695 set debugLabel(String? value) {
696 assert(() {
697 // Only set the value in debug builds.
698 _debugLabel = value;
699 return true;
700 }());
701 }
702
703 FocusAttachment? _attachment;
704
705 /// An [Iterable] over the hierarchy of children below this one, in
706 /// depth-first order.
707 Iterable<FocusNode> get descendants {
708 if (_descendants == null) {
709 final List<FocusNode> result = <FocusNode>[];
710 for (final FocusNode child in _children) {
711 result.addAll(child.descendants);
712 result.add(child);
713 }
714 _descendants = result;
715 }
716 return _descendants!;
717 }
718
719 /// Returns all descendants which do not have the [skipTraversal] and do have
720 /// the [canRequestFocus] flag set.
721 Iterable<FocusNode> get traversalDescendants {
722 if (!descendantsAreFocusable) {
723 return const Iterable<FocusNode>.empty();
724 }
725 return descendants.where((FocusNode node) => !node.skipTraversal && node.canRequestFocus);
726 }
727
728 /// An [Iterable] over the ancestors of this node.
729 ///
730 /// Iterates the ancestors of this node starting at the parent and iterating
731 /// over successively more remote ancestors of this node, ending at the root
732 /// [FocusScopeNode] ([FocusManager.rootScope]).
733 Iterable<FocusNode> get ancestors {
734 if (_ancestors == null) {
735 final List<FocusNode> result = <FocusNode>[];
736 FocusNode? parent = _parent;
737 while (parent != null) {
738 result.add(parent);
739 parent = parent._parent;
740 }
741 _ancestors = result;
742 }
743 return _ancestors!;
744 }
745
746 /// Whether this node has input focus.
747 ///
748 /// A [FocusNode] has focus when it is an ancestor of a node that returns true
749 /// from [hasPrimaryFocus], or it has the primary focus itself.
750 ///
751 /// The [hasFocus] accessor is different from [hasPrimaryFocus] in that
752 /// [hasFocus] is true if the node is anywhere in the focus chain, but for
753 /// [hasPrimaryFocus] the node must to be at the end of the chain to return
754 /// true.
755 ///
756 /// A node that returns true for [hasFocus] will receive key events if none of
757 /// its focused descendants returned true from their [onKey] handler.
758 ///
759 /// This object is a [ChangeNotifier], and notifies its [Listenable] listeners
760 /// (registered via [addListener]) whenever this value changes.
761 ///
762 /// See also:
763 ///
764 /// * [Focus.isAt], which is a static method that will return the focus
765 /// state of the nearest ancestor [Focus] widget's focus node.
766 bool get hasFocus =>
767 hasPrimaryFocus || (_manager?.primaryFocus?.ancestors.contains(this) ?? false);
768
769 /// Returns true if this node currently has the application-wide input focus.
770 ///
771 /// A [FocusNode] has the primary focus when the node is focused in its
772 /// nearest ancestor [FocusScopeNode] and [hasFocus] is true for all its
773 /// ancestor nodes, but none of its descendants.
774 ///
775 /// This is different from [hasFocus] in that [hasFocus] is true if the node
776 /// is anywhere in the focus chain, but here the node has to be at the end of
777 /// the chain to return true.
778 ///
779 /// A node that returns true for [hasPrimaryFocus] will be the first node to
780 /// receive key events through its [onKey] handler.
781 ///
782 /// This object notifies its listeners whenever this value changes.
783 bool get hasPrimaryFocus => _manager?.primaryFocus == this;
784
785 /// Returns the [FocusHighlightMode] that is currently in effect for this node.
786 FocusHighlightMode get highlightMode => FocusManager.instance.highlightMode;
787
788 /// Returns the nearest enclosing scope node above this node, including
789 /// this node, if it's a scope.
790 ///
791 /// Returns null if no scope is found.
792 ///
793 /// Use [enclosingScope] to look for scopes above this node.
794 FocusScopeNode? get nearestScope => enclosingScope;
795
796 FocusScopeNode? _enclosingScope;
797 void _clearEnclosingScopeCache() {
798 final FocusScopeNode? cachedScope = _enclosingScope;
799 if (cachedScope == null) {
800 return;
801 }
802 _enclosingScope = null;
803 if (children.isNotEmpty) {
804 for (final FocusNode child in children) {
805 if (identical(cachedScope, child._enclosingScope)) {
806 child._clearEnclosingScopeCache();
807 }
808 }
809 }
810 }
811
812 /// Returns the nearest enclosing scope node above this node, or null if the
813 /// node has not yet be added to the focus tree.
814 ///
815 /// If this node is itself a scope, this will only return ancestors of this
816 /// scope.
817 ///
818 /// Use [nearestScope] to start at this node instead of above it.
819 FocusScopeNode? get enclosingScope {
820 final FocusScopeNode? enclosingScope = _enclosingScope ??= parent?.nearestScope;
821 assert(
822 enclosingScope == parent?.nearestScope,
823 '$this has invalid scope cache: $_enclosingScope != ${parent?.nearestScope}',
824 );
825 return enclosingScope;
826 }
827
828 /// Returns the size of the attached widget's [RenderObject], in logical
829 /// units.
830 ///
831 /// Size is the size of the transformed widget in global coordinates.
832 Size get size => rect.size;
833
834 /// Returns the global offset to the upper left corner of the attached
835 /// widget's [RenderObject], in logical units.
836 ///
837 /// Offset is the offset of the transformed widget in global coordinates.
838 Offset get offset {
839 assert(
840 context != null,
841 "Tried to get the offset of a focus node that didn't have its context set yet.\n"
842 'The context needs to be set before trying to evaluate traversal policies. '
843 'Setting the context is typically done with the attach method.',
844 );
845 final RenderObject object = context!.findRenderObject()!;
846 return MatrixUtils.transformPoint(object.getTransformTo(null), object.semanticBounds.topLeft);
847 }
848
849 /// Returns the global rectangle of the attached widget's [RenderObject], in
850 /// logical units.
851 ///
852 /// Rect is the rectangle of the transformed widget in global coordinates.
853 Rect get rect {
854 assert(
855 context != null,
856 "Tried to get the bounds of a focus node that didn't have its context set yet.\n"
857 'The context needs to be set before trying to evaluate traversal policies. '
858 'Setting the context is typically done with the attach method.',
859 );
860 final RenderObject object = context!.findRenderObject()!;
861 final Offset topLeft = MatrixUtils.transformPoint(
862 object.getTransformTo(null),
863 object.semanticBounds.topLeft,
864 );
865 final Offset bottomRight = MatrixUtils.transformPoint(
866 object.getTransformTo(null),
867 object.semanticBounds.bottomRight,
868 );
869 return Rect.fromLTRB(topLeft.dx, topLeft.dy, bottomRight.dx, bottomRight.dy);
870 }
871
872 /// Removes the focus on this node by moving the primary focus to another node.
873 ///
874 /// This method removes focus from a node that has the primary focus, cancels
875 /// any outstanding requests to focus it, while setting the primary focus to
876 /// another node according to the `disposition`.
877 ///
878 /// It is safe to call regardless of whether this node has ever requested
879 /// focus or not. If this node doesn't have focus or primary focus, nothing
880 /// happens.
881 ///
882 /// The `disposition` argument determines which node will receive primary
883 /// focus after this one loses it.
884 ///
885 /// If `disposition` is set to [UnfocusDisposition.scope] (the default), then
886 /// the previously focused node history of the enclosing scope will be
887 /// cleared, and the primary focus will be moved to the nearest enclosing
888 /// scope ancestor that is enabled for focus, ignoring the
889 /// [FocusScopeNode.focusedChild] for that scope.
890 ///
891 /// If `disposition` is set to [UnfocusDisposition.previouslyFocusedChild],
892 /// then this node will be removed from the previously focused list in the
893 /// [enclosingScope], and the focus will be moved to the previously focused
894 /// node of the [enclosingScope], which (if it is a scope itself), will find
895 /// its focused child, etc., until a leaf focus node is found. If there is no
896 /// previously focused child, then the scope itself will receive focus, as if
897 /// [UnfocusDisposition.scope] were specified.
898 ///
899 /// If you want this node to lose focus and the focus to move to the next or
900 /// previous node in the enclosing [FocusTraversalGroup], call [nextFocus] or
901 /// [previousFocus] instead of calling [unfocus].
902 ///
903 /// {@tool dartpad}
904 /// This example shows the difference between the different [UnfocusDisposition]
905 /// values for [unfocus].
906 ///
907 /// Try setting focus on the four text fields by selecting them, and then
908 /// select "UNFOCUS" to see what happens when the current
909 /// [FocusManager.primaryFocus] is unfocused.
910 ///
911 /// Try pressing the TAB key after unfocusing to see what the next widget
912 /// chosen is.
913 ///
914 /// ** See code in examples/api/lib/widgets/focus_manager/focus_node.unfocus.0.dart **
915 /// {@end-tool}
916 void unfocus({UnfocusDisposition disposition = UnfocusDisposition.scope}) {
917 if (!hasFocus && (_manager == null || _manager!._markedForFocus != this)) {
918 return;
919 }
920 FocusScopeNode? scope = enclosingScope;
921 if (scope == null) {
922 // If the scope is null, then this is either the root node, or a node that
923 // is not yet in the tree, neither of which do anything when unfocused.
924 return;
925 }
926 switch (disposition) {
927 case UnfocusDisposition.scope:
928 // If it can't request focus, then don't modify its focused children.
929 if (scope.canRequestFocus) {
930 // Clearing the focused children here prevents re-focusing the node
931 // that we just unfocused if we immediately hit "next" after
932 // unfocusing, and also prevents choosing to refocus the next-to-last
933 // focused child if unfocus is called more than once.
934 scope._focusedChildren.clear();
935 }
936
937 while (!scope!.canRequestFocus) {
938 scope = scope.enclosingScope ?? _manager?.rootScope;
939 }
940 scope._doRequestFocus(findFirstFocus: false);
941 case UnfocusDisposition.previouslyFocusedChild:
942 // Select the most recent focused child from the nearest focusable scope
943 // and focus that. If there isn't one, focus the scope itself.
944 if (scope.canRequestFocus) {
945 scope._focusedChildren.remove(this);
946 }
947 while (!scope!.canRequestFocus) {
948 scope.enclosingScope?._focusedChildren.remove(scope);
949 scope = scope.enclosingScope ?? _manager?.rootScope;
950 }
951 scope._doRequestFocus(findFirstFocus: true);
952 }
953 assert(
954 _focusDebug(
955 () => 'Unfocused node:',
956 () => <Object>[
957 'primary focus was $this',
958 'next focus will be ${_manager?._markedForFocus}',
959 ],
960 ),
961 );
962 }
963
964 /// Removes the keyboard token from this focus node if it has one.
965 ///
966 /// This mechanism helps distinguish between an input control gaining focus by
967 /// default and gaining focus as a result of an explicit user action.
968 ///
969 /// When a focus node requests the focus (either via
970 /// [FocusScopeNode.requestFocus] or [FocusScopeNode.autofocus]), the focus
971 /// node receives a keyboard token if it does not already have one. Later,
972 /// when the focus node becomes focused, the widget that manages the
973 /// [TextInputConnection] should show the keyboard (i.e. call
974 /// [TextInputConnection.show]) only if it successfully consumes the keyboard
975 /// token from the focus node.
976 ///
977 /// Returns true if this method successfully consumes the keyboard token.
978 bool consumeKeyboardToken() {
979 if (!_hasKeyboardToken) {
980 return false;
981 }
982 _hasKeyboardToken = false;
983 return true;
984 }
985
986 // Marks the node as being the next to be focused, meaning that it will become
987 // the primary focus and notify listeners of a focus change the next time
988 // focus is resolved by the manager. If something else calls _markNextFocus
989 // before then, then that node will become the next focus instead of the
990 // previous one.
991 void _markNextFocus(FocusNode newFocus) {
992 if (_manager != null) {
993 // If we have a manager, then let it handle the focus change.
994 _manager!._markNextFocus(this);
995 return;
996 }
997 // If we don't have a manager, then change the focus locally.
998 newFocus._setAsFocusedChildForScope();
999 newFocus._notify();
1000 if (newFocus != this) {
1001 _notify();
1002 }
1003 }
1004
1005 // Removes the given FocusNode and its children as a child of this node.
1006 @mustCallSuper
1007 void _removeChild(FocusNode node, {bool removeScopeFocus = true}) {
1008 assert(_children.contains(node), "Tried to remove a node that wasn't a child.");
1009 assert(node._parent == this);
1010 assert(node._manager == _manager);
1011
1012 if (removeScopeFocus) {
1013 final FocusScopeNode? nodeScope = node.enclosingScope;
1014 if (nodeScope != null) {
1015 nodeScope._focusedChildren.remove(node);
1016 node.descendants
1017 .where((FocusNode descendant) {
1018 return descendant.enclosingScope == nodeScope;
1019 })
1020 .forEach(nodeScope._focusedChildren.remove);
1021 }
1022 }
1023
1024 node._parent = null;
1025 node._clearEnclosingScopeCache();
1026 _children.remove(node);
1027 for (final FocusNode ancestor in ancestors) {
1028 ancestor._descendants = null;
1029 }
1030 _descendants = null;
1031 assert(_manager == null || !_manager!.rootScope.descendants.contains(node));
1032 }
1033
1034 void _updateManager(FocusManager? manager) {
1035 _manager = manager;
1036 for (final FocusNode descendant in descendants) {
1037 descendant._manager = manager;
1038 descendant._ancestors = null;
1039 }
1040 }
1041
1042 // Used by FocusAttachment.reparent to perform the actual parenting operation.
1043 @mustCallSuper
1044 void _reparent(FocusNode child) {
1045 assert(child != this, 'Tried to make a child into a parent of itself.');
1046 if (child._parent == this) {
1047 assert(
1048 _children.contains(child),
1049 "Found a node that says it's a child, but doesn't appear in the child list.",
1050 );
1051 // The child is already a child of this parent.
1052 return;
1053 }
1054 assert(
1055 _manager == null || child != _manager!.rootScope,
1056 "Reparenting the root node isn't allowed.",
1057 );
1058 assert(
1059 !ancestors.contains(child),
1060 'The supplied child is already an ancestor of this node. Loops are not allowed.',
1061 );
1062 final FocusScopeNode? oldScope = child.enclosingScope;
1063 final bool hadFocus = child.hasFocus;
1064 child._parent?._removeChild(child, removeScopeFocus: oldScope != nearestScope);
1065 _children.add(child);
1066 child._parent = this;
1067 child._ancestors = null;
1068 child._updateManager(_manager);
1069 for (final FocusNode ancestor in child.ancestors) {
1070 ancestor._descendants = null;
1071 }
1072 if (hadFocus) {
1073 // Update the focus chain for the current focus without changing it.
1074 _manager?.primaryFocus?._setAsFocusedChildForScope();
1075 }
1076 if (oldScope != null && child.context != null && child.enclosingScope != oldScope) {
1077 FocusTraversalGroup.maybeOf(child.context!)?.changedScope(node: child, oldScope: oldScope);
1078 }
1079 if (child._requestFocusWhenReparented) {
1080 child._doRequestFocus(findFirstFocus: true);
1081 child._requestFocusWhenReparented = false;
1082 }
1083 }
1084
1085 /// Called by the _host_ [StatefulWidget] to attach a [FocusNode] to the
1086 /// widget tree.
1087 ///
1088 /// In order to attach a [FocusNode] to the widget tree, call [attach],
1089 /// typically from the [StatefulWidget]'s [State.initState] method.
1090 ///
1091 /// If the focus node in the host widget is swapped out, the new node will
1092 /// need to be attached. [FocusAttachment.detach] should be called on the old
1093 /// node, and then [attach] called on the new node. This typically happens in
1094 /// the [State.didUpdateWidget] method.
1095 ///
1096 /// To receive key events that focuses on this node, pass a listener to
1097 /// `onKeyEvent`.
1098 @mustCallSuper
1099 FocusAttachment attach(
1100 BuildContext? context, {
1101 FocusOnKeyEventCallback? onKeyEvent,
1102 @Deprecated(
1103 'Use onKeyEvent instead. '
1104 'This feature was deprecated after v3.18.0-2.0.pre.',
1105 )
1106 FocusOnKeyCallback? onKey,
1107 }) {
1108 _context = context;
1109 this.onKey = onKey ?? this.onKey;
1110 this.onKeyEvent = onKeyEvent ?? this.onKeyEvent;
1111 _attachment = FocusAttachment._(this);
1112 return _attachment!;
1113 }
1114
1115 @override
1116 void dispose() {
1117 // Detaching will also unfocus and clean up the manager's data structures.
1118 _attachment?.detach();
1119 super.dispose();
1120 }
1121
1122 @mustCallSuper
1123 void _notify() {
1124 if (_parent == null) {
1125 // no longer part of the tree, so don't notify.
1126 return;
1127 }
1128 if (hasPrimaryFocus) {
1129 _setAsFocusedChildForScope();
1130 }
1131 notifyListeners();
1132 }
1133
1134 /// Requests the primary focus for this node, or for a supplied [node], which
1135 /// will also give focus to its [ancestors].
1136 ///
1137 /// If called without a node, request focus for this node. If the node hasn't
1138 /// been added to the focus tree yet, then defer the focus request until it
1139 /// is, allowing newly created widgets to request focus as soon as they are
1140 /// added.
1141 ///
1142 /// If the given [node] is not yet a part of the focus tree, then this method
1143 /// will add the [node] as a child of this node before requesting focus.
1144 ///
1145 /// If the given [node] is a [FocusScopeNode] and that focus scope node has a
1146 /// non-null [FocusScopeNode.focusedChild], then request the focus for the
1147 /// focused child. This process is recursive and continues until it encounters
1148 /// either a focus scope node with a null focused child or an ordinary
1149 /// (non-scope) [FocusNode] is found.
1150 ///
1151 /// The node is notified that it has received the primary focus in a
1152 /// microtask, so notification may lag the request by up to one frame.
1153 void requestFocus([FocusNode? node]) {
1154 if (node != null) {
1155 if (node._parent == null) {
1156 _reparent(node);
1157 }
1158 assert(
1159 node.ancestors.contains(this),
1160 'Focus was requested for a node that is not a descendant of the scope from which it was requested.',
1161 );
1162 node._doRequestFocus(findFirstFocus: true);
1163 return;
1164 }
1165 _doRequestFocus(findFirstFocus: true);
1166 }
1167
1168 // This is overridden in FocusScopeNode.
1169 void _doRequestFocus({required bool findFirstFocus}) {
1170 if (!canRequestFocus) {
1171 assert(
1172 _focusDebug(() => 'Node NOT requesting focus because canRequestFocus is false: $this'),
1173 );
1174 return;
1175 }
1176 // If the node isn't part of the tree, then we just defer the focus request
1177 // until the next time it is reparented, so that it's possible to focus
1178 // newly added widgets.
1179 if (_parent == null) {
1180 _requestFocusWhenReparented = true;
1181 return;
1182 }
1183 _setAsFocusedChildForScope();
1184 if (hasPrimaryFocus &&
1185 (_manager!._markedForFocus == null || _manager!._markedForFocus == this)) {
1186 return;
1187 }
1188 _hasKeyboardToken = true;
1189 assert(_focusDebug(() => 'Node requesting focus: $this'));
1190 _markNextFocus(this);
1191 }
1192
1193 // If set to true, the node will request focus on this node the next time
1194 // this node is reparented in the focus tree.
1195 //
1196 // Once requestFocus has been called at the next reparenting, this value
1197 // will be reset to false.
1198 //
1199 // This will only force a call to requestFocus for the node once the next time
1200 // the node is reparented. After that, _requestFocusWhenReparented would need
1201 // to be set to true again to have it be focused again on the next
1202 // reparenting.
1203 //
1204 // This is used when requestFocus is called and there is no parent yet.
1205 bool _requestFocusWhenReparented = false;
1206
1207 /// Sets this node as the [FocusScopeNode.focusedChild] of the enclosing
1208 /// scope.
1209 ///
1210 /// Sets this node as the focused child for the enclosing scope, and that
1211 /// scope as the focused child for the scope above it, etc., until it reaches
1212 /// the root node. It doesn't change the primary focus, it just changes what
1213 /// node would be focused if the enclosing scope receives focus, and keeps
1214 /// track of previously focused children in that scope, so that if the focused
1215 /// child in that scope is removed, the previous focus returns.
1216 void _setAsFocusedChildForScope() {
1217 FocusNode scopeFocus = this;
1218 for (final FocusScopeNode ancestor in ancestors.whereType<FocusScopeNode>()) {
1219 assert(scopeFocus != ancestor, 'Somehow made a loop by setting focusedChild to its scope.');
1220 assert(
1221 _focusDebug(
1222 () => 'Setting $scopeFocus as focused child for scope:',
1223 () => <Object>[ancestor],
1224 ),
1225 );
1226 // Remove it anywhere in the focused child history.
1227 ancestor._focusedChildren.remove(scopeFocus);
1228 // Add it to the end of the list, which is also the top of the queue: The
1229 // end of the list represents the currently focused child.
1230 ancestor._focusedChildren.add(scopeFocus);
1231 scopeFocus = ancestor;
1232 }
1233 }
1234
1235 /// Request to move the focus to the next focus node, by calling the
1236 /// [FocusTraversalPolicy.next] method.
1237 ///
1238 /// Returns true if it successfully found a node and requested focus.
1239 bool nextFocus() => FocusTraversalGroup.of(context!).next(this);
1240
1241 /// Request to move the focus to the previous focus node, by calling the
1242 /// [FocusTraversalPolicy.previous] method.
1243 ///
1244 /// Returns true if it successfully found a node and requested focus.
1245 bool previousFocus() => FocusTraversalGroup.of(context!).previous(this);
1246
1247 /// Request to move the focus to the nearest focus node in the given
1248 /// direction, by calling the [FocusTraversalPolicy.inDirection] method.
1249 ///
1250 /// Returns true if it successfully found a node and requested focus.
1251 bool focusInDirection(TraversalDirection direction) =>
1252 FocusTraversalGroup.of(context!).inDirection(this, direction);
1253
1254 @override
1255 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1256 super.debugFillProperties(properties);
1257 properties.add(DiagnosticsProperty<BuildContext>('context', context, defaultValue: null));
1258 properties.add(
1259 FlagProperty(
1260 'descendantsAreFocusable',
1261 value: descendantsAreFocusable,
1262 ifFalse: 'DESCENDANTS UNFOCUSABLE',
1263 defaultValue: true,
1264 ),
1265 );
1266 properties.add(
1267 FlagProperty(
1268 'descendantsAreTraversable',
1269 value: descendantsAreTraversable,
1270 ifFalse: 'DESCENDANTS UNTRAVERSABLE',
1271 defaultValue: true,
1272 ),
1273 );
1274 properties.add(
1275 FlagProperty(
1276 'canRequestFocus',
1277 value: canRequestFocus,
1278 ifFalse: 'NOT FOCUSABLE',
1279 defaultValue: true,
1280 ),
1281 );
1282 properties.add(
1283 FlagProperty(
1284 'hasFocus',
1285 value: hasFocus && !hasPrimaryFocus,
1286 ifTrue: 'IN FOCUS PATH',
1287 defaultValue: false,
1288 ),
1289 );
1290 properties.add(
1291 FlagProperty(
1292 'hasPrimaryFocus',
1293 value: hasPrimaryFocus,
1294 ifTrue: 'PRIMARY FOCUS',
1295 defaultValue: false,
1296 ),
1297 );
1298 }
1299
1300 @override
1301 List<DiagnosticsNode> debugDescribeChildren() {
1302 int count = 1;
1303 return _children.map<DiagnosticsNode>((FocusNode child) {
1304 return child.toDiagnosticsNode(name: 'Child ${count++}');
1305 }).toList();
1306 }
1307
1308 @override
1309 String toStringShort() {
1310 final bool hasDebugLabel = debugLabel != null && debugLabel!.isNotEmpty;
1311 final String extraData =
1312 '${hasDebugLabel ? debugLabel : ''}'
1313 '${hasFocus && hasDebugLabel ? ' ' : ''}'
1314 '${hasFocus && !hasPrimaryFocus ? '[IN FOCUS PATH]' : ''}'
1315 '${hasPrimaryFocus ? '[PRIMARY FOCUS]' : ''}';
1316 return '${describeIdentity(this)}${extraData.isNotEmpty ? '($extraData)' : ''}';
1317 }
1318}
1319
1320/// A subclass of [FocusNode] that acts as a scope for its descendants,
1321/// maintaining information about which descendant is currently or was last
1322/// focused.
1323///
1324/// _Please see the [FocusScope] and [Focus] widgets, which are utility widgets
1325/// that manage their own [FocusScopeNode]s and [FocusNode]s, respectively. If
1326/// they aren't appropriate, [FocusScopeNode]s can be managed directly._
1327///
1328/// [FocusScopeNode] organizes [FocusNode]s into _scopes_. Scopes form sub-trees
1329/// of nodes that can be traversed as a group. Within a scope, the most recent
1330/// nodes to have focus are remembered, and if a node is focused and then
1331/// removed, the original node receives focus again.
1332///
1333/// From a [FocusScopeNode], calling [setFirstFocus], sets the given focus scope
1334/// as the [focusedChild] of this node, adopting if it isn't already part of the
1335/// focus tree.
1336///
1337/// {@macro flutter.widgets.FocusNode.lifecycle}
1338/// {@macro flutter.widgets.FocusNode.keyEvents}
1339///
1340/// See also:
1341///
1342/// * [Focus], a widget that manages a [FocusNode] and provides access to focus
1343/// information and actions to its descendant widgets.
1344/// * [FocusManager], a singleton that manages the primary focus and
1345/// distributes key events to focused nodes.
1346class FocusScopeNode extends FocusNode {
1347 /// Creates a [FocusScopeNode].
1348 ///
1349 /// All parameters are optional.
1350 FocusScopeNode({
1351 super.debugLabel,
1352 super.onKeyEvent,
1353 @Deprecated(
1354 'Use onKeyEvent instead. '
1355 'This feature was deprecated after v3.18.0-2.0.pre.',
1356 )
1357 super.onKey,
1358 super.skipTraversal,
1359 super.canRequestFocus,
1360 this.traversalEdgeBehavior = TraversalEdgeBehavior.closedLoop,
1361 this.directionalTraversalEdgeBehavior = TraversalEdgeBehavior.stop,
1362 }) : super(descendantsAreFocusable: true);
1363
1364 @override
1365 FocusScopeNode get nearestScope => this;
1366
1367 @override
1368 bool get descendantsAreFocusable => _canRequestFocus && super.descendantsAreFocusable;
1369
1370 /// Controls the transfer of focus beyond the first and the last items of a
1371 /// [FocusScopeNode].
1372 ///
1373 /// Changing this field value has no immediate effect on the UI. Instead, next time
1374 /// focus traversal takes place [FocusTraversalPolicy] will read this value
1375 /// and apply the new behavior.
1376 TraversalEdgeBehavior traversalEdgeBehavior;
1377
1378 /// Controls the directional transfer of focus when the focus is on the first or last item.
1379 ///
1380 /// Changing this field value has no immediate effect on the UI. Instead, next time
1381 /// focus traversal takes place [FocusTraversalPolicy] will read this value
1382 /// and apply the new behavior.
1383 TraversalEdgeBehavior directionalTraversalEdgeBehavior;
1384
1385 /// Returns true if this scope is the focused child of its parent scope.
1386 bool get isFirstFocus => enclosingScope!.focusedChild == this;
1387
1388 /// Returns the child of this node that should receive focus if this scope
1389 /// node receives focus.
1390 ///
1391 /// If [hasFocus] is true, then this points to the child of this node that is
1392 /// currently focused.
1393 ///
1394 /// Returns null if there is no currently focused child.
1395 FocusNode? get focusedChild {
1396 assert(
1397 _focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this,
1398 '$debugLabel: Focused child does not have the same idea of its enclosing scope '
1399 '(${_focusedChildren.lastOrNull?.enclosingScope}) as the scope does.',
1400 );
1401 return _focusedChildren.lastOrNull;
1402 }
1403
1404 // A stack of the children that have been set as the focusedChild, most recent
1405 // last (which is the top of the stack).
1406 final List<FocusNode> _focusedChildren = <FocusNode>[];
1407
1408 /// An iterator over the children that are allowed to be traversed by the
1409 /// [FocusTraversalPolicy].
1410 ///
1411 /// Will return an empty iterable if this scope node is not focusable, or if
1412 /// [descendantsAreFocusable] is false.
1413 ///
1414 /// See also:
1415 ///
1416 /// * [traversalDescendants], which traverses all of the node's descendants,
1417 /// not just the immediate children.
1418 @override
1419 Iterable<FocusNode> get traversalChildren {
1420 if (!canRequestFocus) {
1421 return const Iterable<FocusNode>.empty();
1422 }
1423 return super.traversalChildren;
1424 }
1425
1426 /// Returns all descendants which do not have the [skipTraversal] and do have
1427 /// the [canRequestFocus] flag set.
1428 ///
1429 /// Will return an empty iterable if this scope node is not focusable, or if
1430 /// [descendantsAreFocusable] is false.
1431 @override
1432 Iterable<FocusNode> get traversalDescendants {
1433 if (!canRequestFocus) {
1434 return const Iterable<FocusNode>.empty();
1435 }
1436 return super.traversalDescendants;
1437 }
1438
1439 /// Make the given [scope] the active child scope for this scope.
1440 ///
1441 /// If the given [scope] is not yet a part of the focus tree, then add it to
1442 /// the tree as a child of this scope. If it is already part of the focus
1443 /// tree, the given scope must be a descendant of this scope.
1444 void setFirstFocus(FocusScopeNode scope) {
1445 assert(scope != this, 'Unexpected self-reference in setFirstFocus.');
1446 assert(
1447 _focusDebug(() => 'Setting scope as first focus in $this to node:', () => <Object>[scope]),
1448 );
1449 if (scope._parent == null) {
1450 _reparent(scope);
1451 }
1452 assert(
1453 scope.ancestors.contains(this),
1454 '$FocusScopeNode $scope must be a child of $this to set it as first focus.',
1455 );
1456 if (hasFocus) {
1457 scope._doRequestFocus(findFirstFocus: true);
1458 } else {
1459 scope._setAsFocusedChildForScope();
1460 }
1461 }
1462
1463 /// If this scope lacks a focus, request that the given node become the focus.
1464 ///
1465 /// If the given node is not yet part of the focus tree, then add it as a
1466 /// child of this node.
1467 ///
1468 /// Useful for widgets that wish to grab the focus if no other widget already
1469 /// has the focus.
1470 ///
1471 /// The node is notified that it has received the primary focus in a
1472 /// microtask, so notification may lag the request by up to one frame.
1473 void autofocus(FocusNode node) {
1474 // Attach the node to the tree first, so in _applyFocusChange if the node
1475 // is detached we don't add it back to the tree.
1476 if (node._parent == null) {
1477 _reparent(node);
1478 }
1479
1480 assert(_manager != null);
1481 assert(_focusDebug(() => 'Autofocus scheduled for $node: scope $this'));
1482 _manager?._pendingAutofocuses.add(_Autofocus(scope: this, autofocusNode: node));
1483 _manager?._markNeedsUpdate();
1484 }
1485
1486 /// Requests that the scope itself receive focus, without trying to find
1487 /// a descendant that should receive focus.
1488 ///
1489 /// This is used only if you want to park the focus on a scope itself.
1490 void requestScopeFocus() {
1491 _doRequestFocus(findFirstFocus: false);
1492 }
1493
1494 @override
1495 void _doRequestFocus({required bool findFirstFocus}) {
1496 // It is possible that a previously focused child is no longer focusable, so
1497 // clean out the list if so.
1498 while (_focusedChildren.isNotEmpty &&
1499 (!_focusedChildren.last.canRequestFocus || _focusedChildren.last.enclosingScope == null)) {
1500 _focusedChildren.removeLast();
1501 }
1502
1503 final FocusNode? focusedChild = this.focusedChild;
1504 // If findFirstFocus is false, then the request is to make this scope the
1505 // focus instead of looking for the ultimate first focus for this scope and
1506 // its descendants.
1507 if (!findFirstFocus || focusedChild == null) {
1508 if (canRequestFocus) {
1509 _setAsFocusedChildForScope();
1510 _markNextFocus(this);
1511 }
1512 return;
1513 }
1514
1515 focusedChild._doRequestFocus(findFirstFocus: true);
1516 }
1517
1518 @override
1519 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1520 super.debugFillProperties(properties);
1521 if (_focusedChildren.isEmpty) {
1522 return;
1523 }
1524 final List<String> childList =
1525 _focusedChildren.reversed.map<String>((FocusNode child) {
1526 return child.toStringShort();
1527 }).toList();
1528 properties.add(
1529 IterableProperty<String>(
1530 'focusedChildren',
1531 childList,
1532 defaultValue: const Iterable<String>.empty(),
1533 ),
1534 );
1535 properties.add(
1536 DiagnosticsProperty<TraversalEdgeBehavior>(
1537 'traversalEdgeBehavior',
1538 traversalEdgeBehavior,
1539 defaultValue: TraversalEdgeBehavior.closedLoop,
1540 ),
1541 );
1542 }
1543}
1544
1545/// An enum to describe which kind of focus highlight behavior to use when
1546/// displaying focus information.
1547enum FocusHighlightMode {
1548 /// Touch interfaces will not show the focus highlight except for controls
1549 /// which bring up the soft keyboard.
1550 ///
1551 /// If a device that uses a traditional mouse and keyboard has a touch screen
1552 /// attached, it can also enter `touch` mode if the user is using the touch
1553 /// screen.
1554 touch,
1555
1556 /// Traditional interfaces (keyboard and mouse) will show the currently
1557 /// focused control via a focus highlight of some sort.
1558 ///
1559 /// If a touch device (like a mobile phone) has a keyboard and/or mouse
1560 /// attached, it also can enter `traditional` mode if the user is using these
1561 /// input devices.
1562 traditional,
1563}
1564
1565/// An enum to describe how the current value of [FocusManager.highlightMode] is
1566/// determined. The strategy is set on [FocusManager.highlightStrategy].
1567enum FocusHighlightStrategy {
1568 /// Automatic switches between the various highlight modes based on the last
1569 /// kind of input that was received. This is the default.
1570 automatic,
1571
1572 /// [FocusManager.highlightMode] always returns [FocusHighlightMode.touch].
1573 alwaysTouch,
1574
1575 /// [FocusManager.highlightMode] always returns [FocusHighlightMode.traditional].
1576 alwaysTraditional,
1577}
1578
1579// By extending the WidgetsBindingObserver class,
1580// we can add a listener object to FocusManager as a private member.
1581class _AppLifecycleListener extends WidgetsBindingObserver {
1582 _AppLifecycleListener(this.onLifecycleStateChanged);
1583
1584 final void Function(AppLifecycleState) onLifecycleStateChanged;
1585
1586 @override
1587 void didChangeAppLifecycleState(AppLifecycleState state) => onLifecycleStateChanged(state);
1588}
1589
1590/// Manages the focus tree.
1591///
1592/// The focus tree is a separate, sparser, tree from the widget tree that
1593/// maintains the hierarchical relationship between focusable widgets in the
1594/// widget tree.
1595///
1596/// The focus manager is responsible for tracking which [FocusNode] has the
1597/// primary input focus (the [primaryFocus]), holding the [FocusScopeNode] that
1598/// is the root of the focus tree (the [rootScope]), and what the current
1599/// [highlightMode] is. It also distributes [KeyEvent]s to the nodes in the
1600/// focus tree.
1601///
1602/// The singleton [FocusManager] instance is held by the [WidgetsBinding] as
1603/// [WidgetsBinding.focusManager], and can be conveniently accessed using the
1604/// [FocusManager.instance] static accessor.
1605///
1606/// To find the [FocusNode] for a given [BuildContext], use [Focus.of]. To find
1607/// the [FocusScopeNode] for a given [BuildContext], use [FocusScope.of].
1608///
1609/// If you would like notification whenever the [primaryFocus] changes, register
1610/// a listener with [addListener]. When you no longer want to receive these
1611/// events, as when your object is about to be disposed, you must unregister
1612/// with [removeListener] to avoid memory leaks. Removing listeners is typically
1613/// done in [State.dispose] on stateful widgets.
1614///
1615/// The [highlightMode] describes how focus highlights should be displayed on
1616/// components in the UI. The [highlightMode] changes are notified separately
1617/// via [addHighlightModeListener] and removed with
1618/// [removeHighlightModeListener]. The highlight mode changes when the user
1619/// switches from a mouse to a touch interface, or vice versa.
1620///
1621/// The widgets that are used to manage focus in the widget tree are:
1622///
1623/// * [Focus], a widget that manages a [FocusNode] in the focus tree so that
1624/// the focus tree reflects changes in the widget hierarchy.
1625/// * [FocusScope], a widget that manages a [FocusScopeNode] in the focus tree,
1626/// creating a new scope for restricting focus to a set of focus nodes.
1627/// * [FocusTraversalGroup], a widget that groups together nodes that should be
1628/// traversed using an order described by a given [FocusTraversalPolicy].
1629///
1630/// See also:
1631///
1632/// * [FocusNode], which is a node in the focus tree that can receive focus.
1633/// * [FocusScopeNode], which is a node in the focus tree used to collect
1634/// subtrees into groups and restrict focus to them.
1635/// * The [primaryFocus] global accessor, for convenient access from anywhere
1636/// to the current focus manager state.
1637class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
1638 /// Creates an object that manages the focus tree.
1639 ///
1640 /// This constructor is rarely called directly. To access the [FocusManager],
1641 /// consider using the [FocusManager.instance] accessor instead (which gets it
1642 /// from the [WidgetsBinding] singleton).
1643 ///
1644 /// This newly constructed focus manager does not have the necessary event
1645 /// handlers registered to allow it to manage focus. To register those event
1646 /// handlers, callers must call [registerGlobalHandlers]. See the
1647 /// documentation in that method for caveats to watch out for.
1648 FocusManager() {
1649 if (kFlutterMemoryAllocationsEnabled) {
1650 ChangeNotifier.maybeDispatchObjectCreation(this);
1651 }
1652 if (_respondToLifecycleChange) {
1653 _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange);
1654 WidgetsBinding.instance.addObserver(_appLifecycleListener!);
1655 }
1656 rootScope._manager = this;
1657 }
1658
1659 /// It appears that some Android keyboard implementations can cause
1660 /// app lifecycle state changes: adding the app lifecycle listener would
1661 /// cause the text field to unfocus as the user is trying to type.
1662 ///
1663 /// Additionally, on iOS, input fields aren't automatically populated
1664 /// with relevant data when using autofill.
1665 ///
1666 /// Until these are resolved, we won't be adding the listener to mobile platforms.
1667 /// https://github.com/flutter/flutter/issues/148475#issuecomment-2118407411
1668 /// https://github.com/flutter/flutter/pull/142930#issuecomment-1981750069
1669 bool get _respondToLifecycleChange =>
1670 kIsWeb ||
1671 switch (defaultTargetPlatform) {
1672 TargetPlatform.android || TargetPlatform.iOS => false,
1673 TargetPlatform.fuchsia || TargetPlatform.linux => true,
1674 TargetPlatform.windows || TargetPlatform.macOS => true,
1675 };
1676
1677 /// Registers global input event handlers that are needed to manage focus.
1678 ///
1679 /// This calls the [HardwareKeyboard.addHandler] on the shared instance of
1680 /// [HardwareKeyboard] and adds a route to the global entry in the gesture
1681 /// routing table. As such, only one [FocusManager] instance should register
1682 /// its global handlers.
1683 ///
1684 /// When this focus manager is no longer needed, calling [dispose] on it will
1685 /// unregister these handlers.
1686 void registerGlobalHandlers() => _highlightManager.registerGlobalHandlers();
1687
1688 @override
1689 void dispose() {
1690 if (_appLifecycleListener != null) {
1691 WidgetsBinding.instance.removeObserver(_appLifecycleListener!);
1692 }
1693 _highlightManager.dispose();
1694 rootScope.dispose();
1695 super.dispose();
1696 }
1697
1698 /// Provides convenient access to the current [FocusManager] singleton from
1699 /// the [WidgetsBinding] instance.
1700 static FocusManager get instance => WidgetsBinding.instance.focusManager;
1701
1702 final _HighlightModeManager _highlightManager = _HighlightModeManager();
1703
1704 /// Sets the strategy by which [highlightMode] is determined.
1705 ///
1706 /// If set to [FocusHighlightStrategy.automatic], then the highlight mode will
1707 /// change depending upon the interaction mode used last. For instance, if the
1708 /// last interaction was a touch interaction, then [highlightMode] will return
1709 /// [FocusHighlightMode.touch], and focus highlights will only appear on
1710 /// widgets that bring up a soft keyboard. If the last interaction was a
1711 /// non-touch interaction (hardware keyboard press, mouse click, etc.), then
1712 /// [highlightMode] will return [FocusHighlightMode.traditional], and focus
1713 /// highlights will appear on all widgets.
1714 ///
1715 /// If set to [FocusHighlightStrategy.alwaysTouch] or
1716 /// [FocusHighlightStrategy.alwaysTraditional], then [highlightMode] will
1717 /// always return [FocusHighlightMode.touch] or
1718 /// [FocusHighlightMode.traditional], respectively, regardless of the last UI
1719 /// interaction type.
1720 ///
1721 /// The initial value of [highlightMode] depends upon the value of
1722 /// [defaultTargetPlatform] and [MouseTracker.mouseIsConnected] of
1723 /// [RendererBinding.mouseTracker], making a guess about which interaction is
1724 /// most appropriate for the initial interaction mode.
1725 ///
1726 /// Defaults to [FocusHighlightStrategy.automatic].
1727 FocusHighlightStrategy get highlightStrategy => _highlightManager.strategy;
1728 set highlightStrategy(FocusHighlightStrategy value) {
1729 if (_highlightManager.strategy == value) {
1730 return;
1731 }
1732 _highlightManager.strategy = value;
1733 }
1734
1735 /// Indicates the current interaction mode for focus highlights.
1736 ///
1737 /// The value returned depends upon the [highlightStrategy] used, and possibly
1738 /// (depending on the value of [highlightStrategy]) the most recent
1739 /// interaction mode that they user used.
1740 ///
1741 /// If [highlightMode] returns [FocusHighlightMode.touch], then widgets should
1742 /// not draw their focus highlight unless they perform text entry.
1743 ///
1744 /// If [highlightMode] returns [FocusHighlightMode.traditional], then widgets should
1745 /// draw their focus highlight whenever they are focused.
1746 // Don't want to set _highlightMode here, since it's possible for the target
1747 // platform to change (especially in tests).
1748 FocusHighlightMode get highlightMode => _highlightManager.highlightMode;
1749
1750 /// Register a closure to be called when the [FocusManager] notifies its listeners
1751 /// that the value of [highlightMode] has changed.
1752 void addHighlightModeListener(ValueChanged<FocusHighlightMode> listener) =>
1753 _highlightManager.addListener(listener);
1754
1755 /// Remove a previously registered closure from the list of closures that the
1756 /// [FocusManager] notifies.
1757 void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) =>
1758 _highlightManager.removeListener(listener);
1759
1760 /// {@template flutter.widgets.focus_manager.FocusManager.addEarlyKeyEventHandler}
1761 /// Adds a key event handler to a set of handlers that are called before any
1762 /// key event handlers in the focus tree are called.
1763 ///
1764 /// All of the handlers in the set will be called for every key event the
1765 /// [FocusManager] receives. If any one of the handlers returns
1766 /// [KeyEventResult.handled] or [KeyEventResult.skipRemainingHandlers], then
1767 /// none of the handlers in the focus tree will be called.
1768 ///
1769 /// If handlers are added while the existing callbacks are being invoked, they
1770 /// will not be called until the next key event occurs.
1771 ///
1772 /// See also:
1773 ///
1774 /// * [removeEarlyKeyEventHandler], which removes handlers added by this
1775 /// function.
1776 /// * [addLateKeyEventHandler], which is a similar mechanism for adding
1777 /// handlers that are invoked after the focus tree has had a chance to
1778 /// handle an event.
1779 /// {@endtemplate}
1780 void addEarlyKeyEventHandler(OnKeyEventCallback handler) {
1781 _highlightManager.addEarlyKeyEventHandler(handler);
1782 }
1783
1784 /// {@template flutter.widgets.focus_manager.FocusManager.removeEarlyKeyEventHandler}
1785 /// Removes a key handler added by calling [addEarlyKeyEventHandler].
1786 ///
1787 /// If handlers are removed while the existing callbacks are being invoked,
1788 /// they will continue to be called until the next key event is received.
1789 ///
1790 /// See also:
1791 ///
1792 /// * [addEarlyKeyEventHandler], which adds the handlers removed by this
1793 /// function.
1794 /// {@endtemplate}
1795 void removeEarlyKeyEventHandler(OnKeyEventCallback handler) {
1796 _highlightManager.removeEarlyKeyEventHandler(handler);
1797 }
1798
1799 /// {@template flutter.widgets.focus_manager.FocusManager.addLateKeyEventHandler}
1800 /// Adds a key event handler to a set of handlers that are called if none of
1801 /// the key event handlers in the focus tree handle the event.
1802 ///
1803 /// If the event reaches the root of the focus tree without being handled,
1804 /// then all of the handlers in the set will be called. If any of them returns
1805 /// [KeyEventResult.handled] or [KeyEventResult.skipRemainingHandlers], then
1806 /// event propagation to the platform will be stopped.
1807 ///
1808 /// If handlers are added while the existing callbacks are being invoked, they
1809 /// will not be called until the next key event is not handled by the focus
1810 /// tree.
1811 ///
1812 /// See also:
1813 ///
1814 /// * [removeLateKeyEventHandler], which removes handlers added by this
1815 /// function.
1816 /// * [addEarlyKeyEventHandler], which is a similar mechanism for adding
1817 /// handlers that are invoked before the focus tree has had a chance to
1818 /// handle an event.
1819 /// {@endtemplate}
1820 void addLateKeyEventHandler(OnKeyEventCallback handler) {
1821 _highlightManager.addLateKeyEventHandler(handler);
1822 }
1823
1824 /// {@template flutter.widgets.focus_manager.FocusManager.removeLateKeyEventHandler}
1825 /// Removes a key handler added by calling [addLateKeyEventHandler].
1826 ///
1827 /// If handlers are removed while the existing callbacks are being invoked,
1828 /// they will continue to be called until the next key event is received.
1829 ///
1830 /// See also:
1831 ///
1832 /// * [addLateKeyEventHandler], which adds the handlers removed by this
1833 /// function.
1834 /// {@endtemplate}
1835 void removeLateKeyEventHandler(OnKeyEventCallback handler) {
1836 _highlightManager.removeLateKeyEventHandler(handler);
1837 }
1838
1839 /// The root [FocusScopeNode] in the focus tree.
1840 ///
1841 /// This field is rarely used directly. To find the nearest [FocusScopeNode]
1842 /// for a given [FocusNode], call [FocusNode.nearestScope].
1843 final FocusScopeNode rootScope = FocusScopeNode(debugLabel: 'Root Focus Scope');
1844
1845 /// The node that currently has the primary focus.
1846 FocusNode? get primaryFocus => _primaryFocus;
1847 FocusNode? _primaryFocus;
1848
1849 // The set of nodes that need to notify their listeners of changes at the next
1850 // update.
1851 final Set<FocusNode> _dirtyNodes = <FocusNode>{};
1852
1853 // Allows FocusManager to respond to app lifecycle state changes,
1854 // temporarily suspending the primaryFocus when the app is inactive.
1855 _AppLifecycleListener? _appLifecycleListener;
1856
1857 // Stores the node that was focused before the app lifecycle changed.
1858 // Will be restored as the primary focus once app is resumed.
1859 FocusNode? _suspendedNode;
1860
1861 void _appLifecycleChange(AppLifecycleState state) {
1862 if (state == AppLifecycleState.resumed) {
1863 if (_primaryFocus != rootScope) {
1864 assert(_focusDebug(() => 'focus changed while app was paused, ignoring $_suspendedNode'));
1865 _suspendedNode = null;
1866 } else if (_suspendedNode != null) {
1867 assert(_focusDebug(() => 'requesting focus for $_suspendedNode'));
1868 _suspendedNode!.requestFocus();
1869 _suspendedNode = null;
1870 }
1871 } else if (_primaryFocus != rootScope) {
1872 assert(_focusDebug(() => 'suspending $_primaryFocus'));
1873 _markedForFocus = rootScope;
1874 _suspendedNode = _primaryFocus;
1875 applyFocusChangesIfNeeded();
1876 }
1877 }
1878
1879 // The node that has requested to have the primary focus, but hasn't been
1880 // given it yet.
1881 FocusNode? _markedForFocus;
1882
1883 void _markDetached(FocusNode node) {
1884 // The node has been removed from the tree, so it no longer needs to be
1885 // notified of changes.
1886 assert(_focusDebug(() => 'Node was detached: $node'));
1887 if (_primaryFocus == node) {
1888 _primaryFocus = null;
1889 }
1890 if (_suspendedNode == node) {
1891 _suspendedNode = null;
1892 }
1893 _dirtyNodes.remove(node);
1894 }
1895
1896 void _markPropertiesChanged(FocusNode node) {
1897 _markNeedsUpdate();
1898 assert(_focusDebug(() => 'Properties changed for node $node.'));
1899 _dirtyNodes.add(node);
1900 }
1901
1902 void _markNextFocus(FocusNode node) {
1903 if (_primaryFocus == node) {
1904 // The caller asked for the current focus to be the next focus, so just
1905 // pretend that didn't happen.
1906 _markedForFocus = null;
1907 } else {
1908 _markedForFocus = node;
1909 _markNeedsUpdate();
1910 }
1911 }
1912
1913 // The list of autofocus requests made since the last _applyFocusChange call.
1914 final List<_Autofocus> _pendingAutofocuses = <_Autofocus>[];
1915
1916 // True indicates that there is an update pending.
1917 bool _haveScheduledUpdate = false;
1918
1919 // Request that an update be scheduled, optionally requesting focus for the
1920 // given newFocus node.
1921 void _markNeedsUpdate() {
1922 assert(
1923 _focusDebug(
1924 () =>
1925 'Scheduling update, current focus is $_primaryFocus, next focus will be $_markedForFocus',
1926 ),
1927 );
1928 if (_haveScheduledUpdate) {
1929 return;
1930 }
1931 _haveScheduledUpdate = true;
1932 scheduleMicrotask(applyFocusChangesIfNeeded);
1933 }
1934
1935 /// Applies any pending focus changes and notifies listeners that the focus
1936 /// has changed.
1937 ///
1938 /// Must not be called during the build phase. This method is meant to be
1939 /// called in a post-frame callback or microtask when the pending focus
1940 /// changes need to be resolved before something else occurs.
1941 ///
1942 /// It can't be called during the build phase because not all listeners are
1943 /// safe to be called with an update during a build.
1944 ///
1945 /// Typically, this is called automatically by the [FocusManager], but
1946 /// sometimes it is necessary to ensure that no focus changes are pending
1947 /// before executing an action. For example, the [MenuAnchor] class uses this
1948 /// to make sure that the previous focus has been restored before executing a
1949 /// menu callback when a menu item is selected.
1950 ///
1951 /// It is safe to call this if no focus changes are pending.
1952 void applyFocusChangesIfNeeded() {
1953 assert(
1954 SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks,
1955 'applyFocusChangesIfNeeded() should not be called during the build phase.',
1956 );
1957
1958 _haveScheduledUpdate = false;
1959 final FocusNode? previousFocus = _primaryFocus;
1960
1961 for (final _Autofocus autofocus in _pendingAutofocuses) {
1962 autofocus.applyIfValid(this);
1963 }
1964 _pendingAutofocuses.clear();
1965
1966 if (_primaryFocus == null && _markedForFocus == null) {
1967 // If we don't have any current focus, and nobody has asked to focus yet,
1968 // then revert to the root scope.
1969 _markedForFocus = rootScope;
1970 }
1971 assert(_focusDebug(() => 'Refreshing focus state. Next focus will be $_markedForFocus'));
1972 // A node has requested to be the next focus, and isn't already the primary
1973 // focus.
1974 if (_markedForFocus != null && _markedForFocus != _primaryFocus) {
1975 final Set<FocusNode> previousPath = previousFocus?.ancestors.toSet() ?? <FocusNode>{};
1976 final Set<FocusNode> nextPath = _markedForFocus!.ancestors.toSet();
1977 // Notify nodes that are newly focused.
1978 _dirtyNodes.addAll(nextPath.difference(previousPath));
1979 // Notify nodes that are no longer focused
1980 _dirtyNodes.addAll(previousPath.difference(nextPath));
1981
1982 _primaryFocus = _markedForFocus;
1983 _markedForFocus = null;
1984 }
1985 assert(_markedForFocus == null);
1986 if (previousFocus != _primaryFocus) {
1987 assert(_focusDebug(() => 'Updating focus from $previousFocus to $_primaryFocus'));
1988 if (previousFocus != null) {
1989 _dirtyNodes.add(previousFocus);
1990 }
1991 if (_primaryFocus != null) {
1992 _dirtyNodes.add(_primaryFocus!);
1993 }
1994 }
1995 for (final FocusNode node in _dirtyNodes) {
1996 node._notify();
1997 }
1998 assert(_focusDebug(() => 'Notified ${_dirtyNodes.length} dirty nodes:', () => _dirtyNodes));
1999 _dirtyNodes.clear();
2000 if (previousFocus != _primaryFocus) {
2001 notifyListeners();
2002 }
2003 assert(() {
2004 if (debugFocusChanges) {
2005 debugDumpFocusTree();
2006 }
2007 return true;
2008 }());
2009 }
2010
2011 /// Enables this [FocusManager] to listen to changes of the application
2012 /// lifecycle if it does not already have an application lifecycle listener
2013 /// active, and the app isn't running on a native mobile platform.
2014 ///
2015 /// Typically, the application lifecycle listener for this [FocusManager] is
2016 /// setup at construction, but sometimes it is necessary to manually initialize
2017 /// it when the [FocusManager] does not have the relevant platform context in
2018 /// [defaultTargetPlatform] at the time of construction. This can happen in
2019 /// a test environment where the [BuildOwner] which initializes its own
2020 /// [FocusManager], may not have the accurate platform context during its
2021 /// initialization. In this case it is necessary for the test framework to call
2022 /// this method after it has set up the test variant for a given test, so the
2023 /// [FocusManager] can accurately listen to application lifecycle changes, if
2024 /// supported.
2025 @visibleForTesting
2026 void listenToApplicationLifecycleChangesIfSupported() {
2027 if (_appLifecycleListener == null && _respondToLifecycleChange) {
2028 _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange);
2029 WidgetsBinding.instance.addObserver(_appLifecycleListener!);
2030 }
2031 }
2032
2033 @override
2034 List<DiagnosticsNode> debugDescribeChildren() {
2035 return <DiagnosticsNode>[rootScope.toDiagnosticsNode(name: 'rootScope')];
2036 }
2037
2038 @override
2039 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2040 properties.add(
2041 FlagProperty('haveScheduledUpdate', value: _haveScheduledUpdate, ifTrue: 'UPDATE SCHEDULED'),
2042 );
2043 properties.add(
2044 DiagnosticsProperty<FocusNode>('primaryFocus', primaryFocus, defaultValue: null),
2045 );
2046 properties.add(
2047 DiagnosticsProperty<FocusNode>('nextFocus', _markedForFocus, defaultValue: null),
2048 );
2049 final Element? element = primaryFocus?.context as Element?;
2050 if (element != null) {
2051 properties.add(
2052 DiagnosticsProperty<String>('primaryFocusCreator', element.debugGetCreatorChain(20)),
2053 );
2054 }
2055 }
2056}
2057
2058// A class to detect and manage the highlight mode transitions. An instance of
2059// this is owned by the FocusManager.
2060//
2061// This doesn't extend ChangeNotifier because the callback passes the updated
2062// value, and ChangeNotifier requires using VoidCallback.
2063class _HighlightModeManager {
2064 _HighlightModeManager() {
2065 assert(debugMaybeDispatchCreated('widgets', '_HighlightModeManager', this));
2066 }
2067
2068 // If null, no interactions have occurred yet and the default highlight mode for the current
2069 // platform applies.
2070 bool? _lastInteractionRequiresTraditionalHighlights;
2071
2072 FocusHighlightMode get highlightMode => _highlightMode ?? _defaultModeForPlatform;
2073 FocusHighlightMode? _highlightMode;
2074
2075 FocusHighlightStrategy get strategy => _strategy;
2076 FocusHighlightStrategy _strategy = FocusHighlightStrategy.automatic;
2077 set strategy(FocusHighlightStrategy value) {
2078 if (_strategy == value) {
2079 return;
2080 }
2081 _strategy = value;
2082 updateMode();
2083 }
2084
2085 /// {@macro flutter.widgets.focus_manager.FocusManager.addEarlyKeyEventHandler}
2086 void addEarlyKeyEventHandler(OnKeyEventCallback callback) => _earlyKeyEventHandlers.add(callback);
2087
2088 /// {@macro flutter.widgets.focus_manager.FocusManager.removeEarlyKeyEventHandler}
2089 void removeEarlyKeyEventHandler(OnKeyEventCallback callback) =>
2090 _earlyKeyEventHandlers.remove(callback);
2091
2092 // The list of callbacks for early key handling.
2093 final HashedObserverList<OnKeyEventCallback> _earlyKeyEventHandlers =
2094 HashedObserverList<OnKeyEventCallback>();
2095
2096 /// {@macro flutter.widgets.focus_manager.FocusManager.addLateKeyEventHandler}
2097 void addLateKeyEventHandler(OnKeyEventCallback callback) => _lateKeyEventHandlers.add(callback);
2098
2099 /// {@macro flutter.widgets.focus_manager.FocusManager.removeLateKeyEventHandler}
2100 void removeLateKeyEventHandler(OnKeyEventCallback callback) =>
2101 _lateKeyEventHandlers.remove(callback);
2102
2103 // The list of callbacks for late key handling.
2104 final HashedObserverList<OnKeyEventCallback> _lateKeyEventHandlers =
2105 HashedObserverList<OnKeyEventCallback>();
2106
2107 /// Register a closure to be called when the [FocusManager] notifies its
2108 /// listeners that the value of [highlightMode] has changed.
2109 void addListener(ValueChanged<FocusHighlightMode> listener) => _listeners.add(listener);
2110
2111 /// Remove a previously registered closure from the list of closures that the
2112 /// [FocusManager] notifies.
2113 void removeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
2114
2115 // The list of listeners for [highlightMode] state changes.
2116 HashedObserverList<ValueChanged<FocusHighlightMode>> _listeners =
2117 HashedObserverList<ValueChanged<FocusHighlightMode>>();
2118
2119 void registerGlobalHandlers() {
2120 assert(ServicesBinding.instance.keyEventManager.keyMessageHandler == null);
2121 // TODO(gspencergoog): Remove this when the RawKeyEvent system is
2122 // deprecated, and replace it with registering a handler on the
2123 // HardwareKeyboard.
2124 ServicesBinding.instance.keyEventManager.keyMessageHandler = handleKeyMessage;
2125 GestureBinding.instance.pointerRouter.addGlobalRoute(handlePointerEvent);
2126 SemanticsBinding.instance.addSemanticsActionListener(handleSemanticsAction);
2127 }
2128
2129 @mustCallSuper
2130 void dispose() {
2131 assert(debugMaybeDispatchDisposed(this));
2132 if (ServicesBinding.instance.keyEventManager.keyMessageHandler == handleKeyMessage) {
2133 GestureBinding.instance.pointerRouter.removeGlobalRoute(handlePointerEvent);
2134 ServicesBinding.instance.keyEventManager.keyMessageHandler = null;
2135 SemanticsBinding.instance.removeSemanticsActionListener(handleSemanticsAction);
2136 }
2137 _listeners = HashedObserverList<ValueChanged<FocusHighlightMode>>();
2138 }
2139
2140 @pragma('vm:notify-debugger-on-exception')
2141 void notifyListeners() {
2142 if (_listeners.isEmpty) {
2143 return;
2144 }
2145 final List<ValueChanged<FocusHighlightMode>> localListeners =
2146 List<ValueChanged<FocusHighlightMode>>.of(_listeners);
2147 for (final ValueChanged<FocusHighlightMode> listener in localListeners) {
2148 try {
2149 if (_listeners.contains(listener)) {
2150 listener(highlightMode);
2151 }
2152 } catch (exception, stack) {
2153 InformationCollector? collector;
2154 assert(() {
2155 collector =
2156 () => <DiagnosticsNode>[
2157 DiagnosticsProperty<_HighlightModeManager>(
2158 'The $runtimeType sending notification was',
2159 this,
2160 style: DiagnosticsTreeStyle.errorProperty,
2161 ),
2162 ];
2163 return true;
2164 }());
2165 FlutterError.reportError(
2166 FlutterErrorDetails(
2167 exception: exception,
2168 stack: stack,
2169 library: 'widgets library',
2170 context: ErrorDescription('while dispatching notifications for $runtimeType'),
2171 informationCollector: collector,
2172 ),
2173 );
2174 }
2175 }
2176 }
2177
2178 void handlePointerEvent(PointerEvent event) {
2179 switch (event.kind) {
2180 case PointerDeviceKind.touch:
2181 case PointerDeviceKind.stylus:
2182 case PointerDeviceKind.invertedStylus:
2183 if (_lastInteractionRequiresTraditionalHighlights != true) {
2184 _lastInteractionRequiresTraditionalHighlights = true;
2185 updateMode();
2186 }
2187 case PointerDeviceKind.mouse:
2188 case PointerDeviceKind.trackpad:
2189 case PointerDeviceKind.unknown:
2190 }
2191 }
2192
2193 bool handleKeyMessage(KeyMessage message) {
2194 // ignore: use_if_null_to_convert_nulls_to_bools
2195 if (_lastInteractionRequiresTraditionalHighlights != false) {
2196 // Update highlightMode first, since things responding to the keys might
2197 // look at the highlight mode, and it should be accurate.
2198 _lastInteractionRequiresTraditionalHighlights = false;
2199 updateMode();
2200 }
2201
2202 assert(_focusDebug(() => 'Received key event $message'));
2203 if (FocusManager.instance.primaryFocus == null) {
2204 assert(_focusDebug(() => 'No primary focus for key event, ignored: $message'));
2205 return false;
2206 }
2207
2208 bool handled = false;
2209 // Check to see if any of the early handlers handle the key. If so, then
2210 // return early.
2211 if (_earlyKeyEventHandlers.isNotEmpty) {
2212 final List<KeyEventResult> results = <KeyEventResult>[
2213 // Make a copy to prevent problems if the list is modified during iteration.
2214 for (final OnKeyEventCallback callback in _earlyKeyEventHandlers.toList())
2215 for (final KeyEvent event in message.events) callback(event),
2216 ];
2217 final KeyEventResult result = combineKeyEventResults(results);
2218 switch (result) {
2219 case KeyEventResult.ignored:
2220 break;
2221 case KeyEventResult.handled:
2222 assert(_focusDebug(() => 'Key event $message handled by early key event callback.'));
2223 handled = true;
2224 case KeyEventResult.skipRemainingHandlers:
2225 assert(
2226 _focusDebug(
2227 () => 'Key event $message propagation stopped by early key event callback.',
2228 ),
2229 );
2230 handled = false;
2231 }
2232 }
2233 if (handled) {
2234 return true;
2235 }
2236
2237 // Walk the current focus from the leaf to the root, calling each node's
2238 // onKeyEvent on the way up, and if one responds that they handled it or
2239 // want to stop propagation, stop.
2240 for (final FocusNode node in <FocusNode>[
2241 FocusManager.instance.primaryFocus!,
2242 ...FocusManager.instance.primaryFocus!.ancestors,
2243 ]) {
2244 final List<KeyEventResult> results = <KeyEventResult>[
2245 if (node.onKeyEvent != null)
2246 for (final KeyEvent event in message.events) node.onKeyEvent!(node, event),
2247 if (node.onKey != null && message.rawEvent != null) node.onKey!(node, message.rawEvent!),
2248 ];
2249 final KeyEventResult result = combineKeyEventResults(results);
2250 switch (result) {
2251 case KeyEventResult.ignored:
2252 continue;
2253 case KeyEventResult.handled:
2254 assert(_focusDebug(() => 'Node $node handled key event $message.'));
2255 handled = true;
2256 case KeyEventResult.skipRemainingHandlers:
2257 assert(_focusDebug(() => 'Node $node stopped key event propagation: $message.'));
2258 handled = false;
2259 }
2260 // Only KeyEventResult.ignored will continue the for loop. All other
2261 // options will stop the event propagation.
2262 assert(result != KeyEventResult.ignored);
2263 break;
2264 }
2265
2266 // Check to see if any late key event handlers want to handle the event.
2267 if (!handled && _lateKeyEventHandlers.isNotEmpty) {
2268 final List<KeyEventResult> results = <KeyEventResult>[
2269 // Make a copy to prevent problems if the list is modified during iteration.
2270 for (final OnKeyEventCallback callback in _lateKeyEventHandlers.toList())
2271 for (final KeyEvent event in message.events) callback(event),
2272 ];
2273 final KeyEventResult result = combineKeyEventResults(results);
2274 switch (result) {
2275 case KeyEventResult.ignored:
2276 break;
2277 case KeyEventResult.handled:
2278 assert(_focusDebug(() => 'Key event $message handled by late key event callback.'));
2279 handled = true;
2280 case KeyEventResult.skipRemainingHandlers:
2281 assert(
2282 _focusDebug(() => 'Key event $message propagation stopped by late key event callback.'),
2283 );
2284 handled = false;
2285 }
2286 }
2287 if (!handled) {
2288 assert(_focusDebug(() => 'Key event not handled by focus system: $message.'));
2289 }
2290 return handled;
2291 }
2292
2293 void handleSemanticsAction(SemanticsActionEvent semanticsActionEvent) {
2294 if (kIsWeb &&
2295 semanticsActionEvent.type == SemanticsAction.focus &&
2296 _lastInteractionRequiresTraditionalHighlights != true) {
2297 _lastInteractionRequiresTraditionalHighlights = true;
2298 updateMode();
2299 }
2300 }
2301
2302 // Update function to be called whenever the state relating to highlightMode
2303 // changes.
2304 void updateMode() {
2305 final FocusHighlightMode newMode;
2306 switch (strategy) {
2307 case FocusHighlightStrategy.automatic:
2308 if (_lastInteractionRequiresTraditionalHighlights == null) {
2309 // If we don't have any information about the last interaction yet,
2310 // then just rely on the default value for the platform, which will be
2311 // determined based on the target platform if _highlightMode is not
2312 // set.
2313 return;
2314 }
2315 if (_lastInteractionRequiresTraditionalHighlights!) {
2316 newMode = FocusHighlightMode.touch;
2317 } else {
2318 newMode = FocusHighlightMode.traditional;
2319 }
2320 case FocusHighlightStrategy.alwaysTouch:
2321 newMode = FocusHighlightMode.touch;
2322 case FocusHighlightStrategy.alwaysTraditional:
2323 newMode = FocusHighlightMode.traditional;
2324 }
2325 // We can't just compare newMode with _highlightMode here, since
2326 // _highlightMode could be null, so we want to compare with the return value
2327 // for the getter, since that's what clients will be looking at.
2328 final FocusHighlightMode oldMode = highlightMode;
2329 _highlightMode = newMode;
2330 if (highlightMode != oldMode) {
2331 notifyListeners();
2332 }
2333 }
2334
2335 static FocusHighlightMode get _defaultModeForPlatform {
2336 // Assume that if we're on one of the mobile platforms, and there's no mouse
2337 // connected, that the initial interaction will be touch-based, and that
2338 // it's traditional mouse and keyboard on all other platforms.
2339 //
2340 // This only affects the initial value: the ongoing value is updated to a
2341 // known correct value as soon as any pointer/keyboard events are received.
2342 switch (defaultTargetPlatform) {
2343 case TargetPlatform.android:
2344 case TargetPlatform.fuchsia:
2345 case TargetPlatform.iOS:
2346 if (WidgetsBinding.instance.mouseTracker.mouseIsConnected) {
2347 return FocusHighlightMode.traditional;
2348 }
2349 return FocusHighlightMode.touch;
2350 case TargetPlatform.linux:
2351 case TargetPlatform.macOS:
2352 case TargetPlatform.windows:
2353 return FocusHighlightMode.traditional;
2354 }
2355 }
2356}
2357
2358/// Provides convenient access to the current [FocusManager.primaryFocus] from
2359/// the [WidgetsBinding] instance.
2360FocusNode? get primaryFocus => WidgetsBinding.instance.focusManager.primaryFocus;
2361
2362/// Returns a text representation of the current focus tree, along with the
2363/// current attributes on each node.
2364///
2365/// Will return an empty string in release builds.
2366String debugDescribeFocusTree() {
2367 String? result;
2368 assert(() {
2369 result = FocusManager.instance.toStringDeep();
2370 return true;
2371 }());
2372 return result ?? '';
2373}
2374
2375/// Prints a text representation of the current focus tree, along with the
2376/// current attributes on each node.
2377///
2378/// Will do nothing in release builds.
2379void debugDumpFocusTree() {
2380 assert(() {
2381 debugPrint(debugDescribeFocusTree());
2382 return true;
2383 }());
2384}
2385