blob: cde83cbbe3b945dd4ac14cd1d382cca6d188fa0f [file] [log] [blame]
// 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)}';
}
}