| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:flutter/foundation.dart'; |
| |
| /// A leaf node in the focus tree that can receive focus. |
| /// |
| /// The focus tree keeps track of which widget is the user's current focus. The |
| /// focused widget often listens for keyboard events. |
| /// |
| /// To request focus, find the [FocusScopeNode] for the current [BuildContext] |
| /// and call the [FocusScopeNode.requestFocus] method: |
| /// |
| /// ```dart |
| /// FocusScope.of(context).requestFocus(focusNode); |
| /// ``` |
| /// |
| /// If your widget requests focus, be sure to call |
| /// `FocusScope.of(context).reparentIfNeeded(focusNode);` in your `build` |
| /// method to reparent your [FocusNode] if your widget moves from one |
| /// location in the tree to another. |
| /// |
| /// ## Lifetime |
| /// |
| /// Focus nodes are long-lived objects. For example, if a stateful widget has a |
| /// focusable child widget, it should create a [FocusNode] in the |
| /// [State.initState] method, and [dispose] it in the [State.dispose] method, |
| /// providing the same [FocusNode] to the focusable child each time the |
| /// [State.build] method is run. In particular, creating a [FocusNode] each time |
| /// [State.build] is invoked will cause the focus to be lost each time the |
| /// widget is built. |
| /// |
| /// See also: |
| /// |
| /// * [FocusScopeNode], which is an interior node in the focus tree. |
| /// * [FocusScope.of], which provides the [FocusScopeNode] for a given |
| /// [BuildContext]. |
| class FocusNode extends ChangeNotifier { |
| FocusScopeNode _parent; |
| FocusManager _manager; |
| bool _hasKeyboardToken = false; |
| |
| /// Whether this node has the overall focus. |
| /// |
| /// A [FocusNode] has the overall focus when the node is focused in its |
| /// parent [FocusScopeNode] and [FocusScopeNode.isFirstFocus] is true for |
| /// that scope and all its ancestor scopes. |
| /// |
| /// To request focus, find the [FocusScopeNode] for the current [BuildContext] |
| /// and call the [FocusScopeNode.requestFocus] method: |
| /// |
| /// ```dart |
| /// FocusScope.of(context).requestFocus(focusNode); |
| /// ``` |
| /// |
| /// This object notifies its listeners whenever this value changes. |
| bool get hasFocus => _manager?._currentFocus == this; |
| |
| /// Removes the keyboard token from this focus node if it has one. |
| /// |
| /// This mechanism helps distinguish between an input control gaining focus by |
| /// default and gaining focus as a result of an explicit user action. |
| /// |
| /// When a focus node requests the focus (either via |
| /// [FocusScopeNode.requestFocus] or [FocusScopeNode.autofocus]), the focus |
| /// node receives a keyboard token if it does not already have one. Later, |
| /// when the focus node becomes focused, the widget that manages the |
| /// [TextInputConnection] should show the keyboard (i.e., call |
| /// [TextInputConnection.show]) only if it successfully consumes the keyboard |
| /// token from the focus node. |
| /// |
| /// Returns whether this function successfully consumes a keyboard token. |
| bool consumeKeyboardToken() { |
| if (!_hasKeyboardToken) |
| return false; |
| _hasKeyboardToken = false; |
| return true; |
| } |
| |
| /// Cancels any outstanding requests for focus. |
| /// |
| /// This method is safe to call regardless of whether this node has ever |
| /// requested focus. |
| void unfocus() { |
| _parent?._resignFocus(this); |
| assert(_parent == null); |
| assert(_manager == null); |
| } |
| |
| @override |
| void dispose() { |
| _manager?._willDisposeFocusNode(this); |
| _parent?._resignFocus(this); |
| assert(_parent == null); |
| assert(_manager == null); |
| super.dispose(); |
| } |
| |
| void _notify() { |
| notifyListeners(); |
| } |
| |
| @override |
| String toString() => '${describeIdentity(this)}${hasFocus ? '(FOCUSED)' : ''}'; |
| } |
| |
| /// An interior node in the focus tree. |
| /// |
| /// The focus tree keeps track of which widget is the user's current focus. The |
| /// focused widget often listens for keyboard events. |
| /// |
| /// The interior nodes in the focus tree cannot themselves be focused but |
| /// instead remember previous focus states. A scope is currently active in its |
| /// parent whenever [isFirstFocus] is true. If that scope is detached from its |
| /// parent, its previous sibling becomes the parent's first focus. |
| /// |
| /// A [FocusNode] has the overall focus when the node is focused in its |
| /// parent [FocusScopeNode] and [FocusScopeNode.isFirstFocus] is true for |
| /// that scope and all its ancestor scopes. |
| /// |
| /// See also: |
| /// |
| /// * [FocusNode], which is a leaf node in the focus tree that can receive |
| /// focus. |
| /// * [FocusScope.of], which provides the [FocusScopeNode] for a given |
| /// [BuildContext]. |
| /// * [FocusScope], which is a widget that associates a [FocusScopeNode] with |
| /// its location in the tree. |
| class FocusScopeNode extends Object with DiagnosticableTreeMixin { |
| FocusManager _manager; |
| FocusScopeNode _parent; |
| |
| FocusScopeNode _nextSibling; |
| FocusScopeNode _previousSibling; |
| |
| FocusScopeNode _firstChild; |
| FocusScopeNode _lastChild; |
| |
| FocusNode _focus; |
| |
| /// Whether this scope is currently active in its parent scope. |
| bool get isFirstFocus => _parent == null || _parent._firstChild == this; |
| |
| void _prepend(FocusScopeNode child) { |
| assert(child != this); |
| assert(child != _firstChild); |
| assert(child != _lastChild); |
| assert(child._parent == null); |
| assert(child._manager == null); |
| assert(child._nextSibling == null); |
| assert(child._previousSibling == null); |
| assert(() { |
| FocusScopeNode node = this; |
| while (node._parent != null) |
| node = node._parent; |
| assert(node != child); // indicates we are about to create a cycle |
| return true; |
| }()); |
| child._parent = this; |
| child._nextSibling = _firstChild; |
| if (_firstChild != null) |
| _firstChild._previousSibling = child; |
| _firstChild = child; |
| _lastChild ??= child; |
| child._updateManager(_manager); |
| } |
| |
| void _updateManager(FocusManager manager) { |
| void update(FocusScopeNode child) { |
| if (child._manager == manager) |
| return; |
| child._manager = manager; |
| // We don't proactively null out the manager for FocusNodes because the |
| // manager holds the currently active focus node until the end of the |
| // microtask, even if that node is detached from the focus tree. |
| if (manager != null) |
| child._focus?._manager = manager; |
| child._visitChildren(update); |
| } |
| |
| update(this); |
| } |
| |
| void _visitChildren(void visitor(FocusScopeNode child)) { |
| FocusScopeNode child = _firstChild; |
| while (child != null) { |
| visitor(child); |
| child = child._nextSibling; |
| } |
| } |
| |
| bool _debugUltimatePreviousSiblingOf(FocusScopeNode child, { FocusScopeNode equals }) { |
| while (child._previousSibling != null) { |
| assert(child._previousSibling != child); |
| child = child._previousSibling; |
| } |
| return child == equals; |
| } |
| |
| bool _debugUltimateNextSiblingOf(FocusScopeNode child, { FocusScopeNode equals }) { |
| while (child._nextSibling != null) { |
| assert(child._nextSibling != child); |
| child = child._nextSibling; |
| } |
| return child == equals; |
| } |
| |
| void _remove(FocusScopeNode child) { |
| assert(child._parent == this); |
| assert(child._manager == _manager); |
| assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); |
| assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); |
| if (child._previousSibling == null) { |
| assert(_firstChild == child); |
| _firstChild = child._nextSibling; |
| } else { |
| child._previousSibling._nextSibling = child._nextSibling; |
| } |
| if (child._nextSibling == null) { |
| assert(_lastChild == child); |
| _lastChild = child._previousSibling; |
| } else { |
| child._nextSibling._previousSibling = child._previousSibling; |
| } |
| child._previousSibling = null; |
| child._nextSibling = null; |
| child._parent = null; |
| child._updateManager(null); |
| } |
| |
| void _didChangeFocusChain() { |
| if (isFirstFocus) |
| _manager?._markNeedsUpdate(); |
| } |
| |
| /// Requests that the given node becomes the focus for this scope. |
| /// |
| /// If the given node is currently focused in another scope, the node will |
| /// first be unfocused in that scope. |
| /// |
| /// The node will receive the overall focus if this [isFirstFocus] is true |
| /// in this scope and all its ancestor scopes. The node is notified that it |
| /// has received the overall focus in a microtask. |
| void requestFocus(FocusNode node) { |
| assert(node != null); |
| if (_focus == node) |
| return; |
| _focus?.unfocus(); |
| node._hasKeyboardToken = true; |
| _setFocus(node); |
| } |
| |
| /// If this scope lacks a focus, request that the given node becomes the |
| /// focus. |
| /// |
| /// Useful for widgets that wish to grab the focus if no other widget already |
| /// has the focus. |
| /// |
| /// The node is notified that it has received the overall focus in a |
| /// microtask. |
| void autofocus(FocusNode node) { |
| assert(node != null); |
| if (_focus == null) { |
| node._hasKeyboardToken = true; |
| _setFocus(node); |
| } |
| } |
| |
| /// Adopts the given node if it is focused in another scope. |
| /// |
| /// A widget that requests that a node is focused should call this method |
| /// during its `build` method in case the widget is moved from one location |
| /// in the tree to another location that has a different focus scope. |
| void reparentIfNeeded(FocusNode node) { |
| assert(node != null); |
| if (node._parent == null || node._parent == this) |
| return; |
| node.unfocus(); |
| assert(node._parent == null); |
| if (_focus == null) |
| _setFocus(node); |
| } |
| |
| void _setFocus(FocusNode node) { |
| assert(node != null); |
| assert(node._parent == null); |
| assert(_focus == null); |
| _focus = node; |
| _focus._parent = this; |
| _focus._manager = _manager; |
| _focus._hasKeyboardToken = true; |
| _didChangeFocusChain(); |
| } |
| |
| void _resignFocus(FocusNode node) { |
| assert(node != null); |
| if (_focus != node) |
| return; |
| _focus._parent = null; |
| _focus._manager = null; |
| _focus = null; |
| _didChangeFocusChain(); |
| } |
| |
| /// Makes the given child the first focus of this scope. |
| /// |
| /// If the child has another parent scope, the child is first removed from |
| /// that scope. After this method returns [isFirstFocus] will be true for |
| /// the child. |
| void setFirstFocus(FocusScopeNode child) { |
| assert(child != null); |
| assert(child._parent == null || child._parent == this); |
| if (_firstChild == child) |
| return; |
| child.detach(); |
| _prepend(child); |
| assert(child._parent == this); |
| _didChangeFocusChain(); |
| } |
| |
| /// Adopts the given scope if it is the first focus of another scope. |
| /// |
| /// A widget that sets a scope as the first focus of another scope should |
| /// call this method during its `build` method in case the widget is moved |
| /// from one location in the tree to another location that has a different |
| /// focus scope. |
| /// |
| /// If the given scope is not the first focus of its old parent, the scope |
| /// is simply detached from its old parent. |
| void reparentScopeIfNeeded(FocusScopeNode child) { |
| assert(child != null); |
| if (child._parent == null || child._parent == this) |
| return; |
| if (child.isFirstFocus) |
| setFirstFocus(child); |
| else |
| child.detach(); |
| } |
| |
| /// Remove this scope from its parent child list. |
| /// |
| /// This method is safe to call even if this scope does not have a parent. |
| /// |
| /// A widget that sets a scope as the first focus of another scope should |
| /// call this method during [State.dispose] to avoid leaving dangling |
| /// children in their parent scope. |
| void detach() { |
| _didChangeFocusChain(); |
| _parent?._remove(this); |
| assert(_parent == null); |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| if (_focus != null) |
| properties.add(new DiagnosticsProperty<FocusNode>('focus', _focus)); |
| } |
| |
| @override |
| List<DiagnosticsNode> debugDescribeChildren() { |
| final List<DiagnosticsNode> children = <DiagnosticsNode>[]; |
| if (_firstChild != null) { |
| FocusScopeNode child = _firstChild; |
| int count = 1; |
| while (true) { |
| children.add(child.toDiagnosticsNode(name: 'child $count')); |
| if (child == _lastChild) |
| break; |
| child = child._nextSibling; |
| count += 1; |
| } |
| } |
| return children; |
| } |
| } |
| |
| /// Manages the focus tree. |
| /// |
| /// The focus tree keeps track of which widget is the user's current focus. The |
| /// focused widget often listens for keyboard events. |
| /// |
| /// The focus manager is responsible for holding the [FocusScopeNode] that is |
| /// the root of the focus tree and tracking which [FocusNode] has the overall |
| /// focus. |
| /// |
| /// The [FocusManager] is held by the [WidgetsBinding] as |
| /// [WidgetsBinding.focusManager]. The [FocusManager] is rarely accessed |
| /// directly. Instead, to find the [FocusScopeNode] for a given [BuildContext], |
| /// use [FocusScope.of]. |
| /// |
| /// See also: |
| /// |
| /// * [FocusNode], which is a leaf node in the focus tree that can receive |
| /// focus. |
| /// * [FocusScopeNode], which is an interior node in the focus tree. |
| /// * [FocusScope.of], which provides the [FocusScopeNode] for a given |
| /// [BuildContext]. |
| class FocusManager { |
| /// Creates an object that manages the focus tree. |
| /// |
| /// This constructor is rarely called directly. To access the [FocusManager], |
| /// consider using [WidgetsBinding.focusManager] instead. |
| FocusManager() { |
| rootScope._manager = this; |
| assert(rootScope._firstChild == null); |
| assert(rootScope._lastChild == null); |
| } |
| |
| /// The root [FocusScopeNode] in the focus tree. |
| /// |
| /// This field is rarely used direction. Instead, to find the |
| /// [FocusScopeNode] for a given [BuildContext], use [FocusScope.of]. |
| final FocusScopeNode rootScope = new FocusScopeNode(); |
| |
| FocusNode _currentFocus; |
| |
| void _willDisposeFocusNode(FocusNode node) { |
| assert(node != null); |
| if (_currentFocus == node) |
| _currentFocus = null; |
| } |
| |
| bool _haveScheduledUpdate = false; |
| void _markNeedsUpdate() { |
| if (_haveScheduledUpdate) |
| return; |
| _haveScheduledUpdate = true; |
| scheduleMicrotask(_update); |
| } |
| |
| FocusNode _findNextFocus() { |
| FocusScopeNode scope = rootScope; |
| while (scope._firstChild != null) |
| scope = scope._firstChild; |
| return scope._focus; |
| } |
| |
| void _update() { |
| _haveScheduledUpdate = false; |
| final FocusNode nextFocus = _findNextFocus(); |
| if (_currentFocus == nextFocus) |
| return; |
| final FocusNode previousFocus = _currentFocus; |
| _currentFocus = nextFocus; |
| previousFocus?._notify(); |
| _currentFocus?._notify(); |
| } |
| |
| @override |
| String toString() { |
| final String status = _haveScheduledUpdate ? ' UPDATE SCHEDULED' : ''; |
| const String indent = ' '; |
| return '${describeIdentity(this)}$status\n' |
| '${indent}currentFocus: $_currentFocus\n' |
| '${rootScope.toStringDeep(prefixLineOne: indent, prefixOtherLines: indent)}'; |
| } |
| } |