focus_manager.dart 57.2 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:io';
7
import 'dart:ui';
8 9

import 'package:flutter/foundation.dart';
10
import 'package:flutter/gestures.dart';
11 12
import 'package:flutter/painting.dart';
import 'package:flutter/services.dart';
13

14 15
import 'binding.dart';
import 'focus_scope.dart';
16
import 'focus_traversal.dart';
17 18
import 'framework.dart';

19 20 21 22
// Used for debugging focus code. Set to true to see highly verbose debug output
// when focus changes occur.
const bool _kDebugFocus = false;

23 24 25 26 27 28
bool _focusDebug(String message, [Iterable<String> details]) {
  if (_kDebugFocus) {
    debugPrint('FOCUS: $message');
    if (details != null && details.isNotEmpty) {
      for (String detail in details) {
        debugPrint('    $detail');
29 30
      }
    }
31 32
  }
  return true;
33 34
}

35 36
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
/// to receive key events.
37
///
38 39 40 41
/// The [node] is the node that received the event.
typedef FocusOnKeyCallback = bool Function(FocusNode node, RawKeyEvent event);

/// An attachment point for a [FocusNode].
42
///
43 44 45 46 47 48
/// Once created, a [FocusNode] must be attached to the widget tree by its
/// _host_ [StatefulWidget] via a [FocusAttachment] object. [FocusAttachment]s
/// are owned by the [StatefulWidget] that hosts a [FocusNode] or
/// [FocusScopeNode]. There can be multiple [FocusAttachment]s for each
/// [FocusNode], but the node will only ever be attached to one of them at a
/// time.
49
///
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
/// This attachment is created by calling [FocusNode.attach], usually from the
/// host widget's [State.initState] method. If the widget is updated to have a
/// different focus node, then the new node needs to be attached in
/// [State.didUpdateWidget], after calling [detach] on the previous
/// [FocusAttachment]. Once detached, the attachment is defunct and will no
/// longer make changes to the [FocusNode] through [reparent].
///
/// Without these attachment points, it would be possible for a focus node to
/// simultaneously be attached to more than one part of the widget tree during
/// the build stage.
class FocusAttachment {
  /// A private constructor, because [FocusAttachment]s are only to be created
  /// by [FocusNode.attach].
  FocusAttachment._(this._node) : assert(_node != null);

  // The focus node that this attachment manages an attachment for. The node may
  // not yet have a parent, or may have been detached from this attachment, so
  // don't count on this node being in a usable state.
  final FocusNode _node;

  /// Returns true if the associated node is attached to this attachment.
  ///
  /// It is possible to be attached to the widget tree, but not be placed in
  /// the focus tree (i.e. to not have a parent yet in the focus tree).
  bool get isAttached => _node._attachment == this;

  /// Detaches the [FocusNode] this attachment point is associated with from the
  /// focus tree, and disconnects it from this attachment point.
  ///
  /// Calling [FocusNode.dispose] will also automatically detach the node.
  void detach() {
    assert(_node != null);
82
    assert(_focusDebug('Detaching node:', <String>[_node.toString(), 'With enclosing scope ${_node.enclosingScope}']));
83
    if (isAttached) {
84 85
      if (_node.hasPrimaryFocus || (_node._manager != null && _node._manager._nextFocus == _node)) {
        _node.unfocus(focusPrevious: true);
86
      }
87 88 89
      assert(_node._manager?._nextFocus != _node);
      assert(!_node.hasPrimaryFocus);
      _node._manager?._dirtyNodes?.remove(_node);
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
      _node._parent?._removeChild(_node);
      _node._attachment = null;
    }
    assert(!isAttached);
  }

  /// Ensures that the [FocusNode] attached at this attachment point has the
  /// proper parent node, changing it if necessary.
  ///
  /// If given, ensures that the given [parent] node is the parent of the node
  /// that is attached at this attachment point, changing it if necessary.
  /// However, it is usually not necessary to supply an explicit parent, since
  /// [reparent] will use [Focus.of] to determine the correct parent node for
  /// the context given in [FocusNode.attach].
  ///
  /// If [isAttached] is false, then calling this method does nothing.
  ///
  /// Should be called whenever the associated widget is rebuilt in order to
  /// maintain the focus hierarchy.
  ///
  /// A [StatefulWidget] that hosts a [FocusNode] should call this method on the
  /// node it hosts during its [State.build] or [State.didChangeDependencies]
  /// methods in case the widget is moved from one location in the tree to
  /// another location that has a different [FocusScope] or context.
  ///
  /// The optional [parent] argument must be supplied when not using [Focus] and
  /// [FocusScope] widgets to build the focus tree, or if there is a need to
  /// supply the parent explicitly (which are both uncommon).
  void reparent({FocusNode parent}) {
    assert(_node != null);
    if (isAttached) {
      assert(_node.context != null);
122 123
      parent ??= Focus.of(_node.context, nullOk: true, scopeOk: true);
      parent ??= _node.context.owner.focusManager.rootScope;
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
      assert(parent != null);
      parent._reparent(_node);
    }
  }
}

/// An object that can be used by a stateful widget to obtain the keyboard focus
/// and to handle keyboard events.
///
/// _Please see the [Focus] and [FocusScope] widgets, which are utility widgets
/// that manage their own [FocusNode]s and [FocusScopeNode]s, respectively. If
/// they aren't appropriate, [FocusNode]s can be managed directly._
///
/// [FocusNode]s are persistent objects that form a _focus tree_ that is a
/// representation of the widgets in the hierarchy that are interested in focus.
/// A focus node might need to be created if it is passed in from an ancestor of
/// a [Focus] widget to control the focus of the children from the ancestor, or
/// a widget might need to host one if the widget subsystem is not being used,
/// or if the [Focus] and [FocusScope] widgets provide insufficient control.
///
/// [FocusNodes] are organized into _scopes_ (see [FocusScopeNode]), which form
/// sub-trees of nodes that can be traversed as a group. Within a scope, the
/// most recent nodes to have focus are remembered, and if a node is focused and
/// then removed, the previous node receives focus again.
///
/// The focus node hierarchy can be traversed using the [parent], [children],
/// [ancestors] and [descendants] accessors.
///
/// [FocusNode]s are [ChangeNotifier]s, so a listener can be registered to
/// receive a notification when the focus changes. If the [Focus] and
/// [FocusScope] widgets are being used to manage the nodes, consider
/// establishing an [InheritedWidget] dependency on them by calling [Focus.of]
/// or [FocusScope.of] instead. [Focus.hasFocus] can also be used to establish a
/// similar dependency, especially if all that is needed is to determine whether
/// or not the widget is focused at build time.
///
/// To see the focus tree in the debug console, call [debugDumpFocusTree]. To
/// get the focus tree as a string, call [debugDescribeFocusTree].
///
/// {@template flutter.widgets.focus_manager.focus.lifecycle}
/// ## Lifecycle
///
/// There are several actors involved in the lifecycle of a
/// [FocusNode]/[FocusScopeNode]. They are created and disposed by their
/// _owner_, attached, detached, and reparented using a [FocusAttachment] by
/// their _host_ (which must be owned by the [State] of a [StatefulWidget]), and
/// they are managed by the [FocusManager]. Different parts of the [FocusNode]
/// API are intended for these different actors.
///
/// [FocusNode]s (and hence [FocusScopeNode]s) are persistent objects that form
/// part of a _focus tree_ that is a sparse representation of the widgets in the
/// hierarchy that are interested in receiving keyboard events. They must be
/// managed like other persistent state, which is typically done by a
/// [StatefulWidget] that owns the node. A stateful widget that owns a focus
/// scope node must call [dispose] from its [State.dispose] method.
///
/// Once created, a [FocusNode] must be attached to the widget tree via a
/// [FocusAttachment] object. This attachment is created by calling [attach],
/// usually from the [State.initState] method. If the hosting widget is updated
/// to have a different focus node, then the updated node needs to be attached
/// in [State.didUpdateWidget], after calling [detach] on the previous
/// [FocusAttachment].
///
/// Because [FocusNode]s form a sparse representation of the widget tree,
/// they must be updated whenever the widget tree is rebuilt. This is done by
/// calling [FocusAttachment.reparent], usually from the [State.build] or
/// [State.didChangeDependencies] methods of the widget that represents the
/// focused region, so that the [BuildContext] assigned to the [FocusScopeNode]
/// can be tracked (the context is used to obtain the [RenderObject], from which
/// the geometry of focused regions can be determined).
///
/// Creating a [FocusNode] each time [State.build] is invoked will cause the
/// focus to be lost each time the widget is built, which is usually not desired
/// behavior (call [unfocus] if losing focus is desired).
///
/// If, as is common, the hosting [StatefulWidget] is also the owner of the
/// focus node, then it will also call [dispose] from its [State.dispose] (in
/// which case the [detach] may be skipped, since dispose will automatically
/// detach). If another object owns the focus node, then it must call [dispose]
/// when the node is done being used.
/// {@endtemplate}
///
/// {@template flutter.widgets.focus_manager.focus.keyEvents}
/// ## Key Event Propagation
///
/// The [FocusManager] receives all key events and will pass them to the focused
/// nodes. It starts with the node with the primary focus, and will call the
/// [onKey] callback for that node. If the callback returns false, indicating
/// that it did not handle the event, the [FocusManager] will move to the parent
/// of that node and call its [onKey]. If that [onKey] returns true, then it
/// will stop propagating the event. If it reaches the root [FocusScopeNode],
/// [FocusManager.rootScope], the event is discarded.
/// {@endtemplate}
///
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
/// ## Focus Traversal
///
/// The term _traversal_, sometimes called _tab traversal_, refers to moving the
/// focus from one widget to the next in a particular order (also sometimes
/// referred to as the _tab order_, since the TAB key is often bound to the
/// action to move to the next widget).
///
/// To give focus to the logical _next_ or _previous_ widget in the UI, call the
/// [nextFocus] or [previousFocus] methods. To give the focus to a widget in a
/// particular direction, call the [focusInDirection] method.
///
/// The policy for what the _next_ or _previous_ widget is, or the widget in a
/// particular direction, is determined by the [FocusTraversalPolicy] in force.
///
/// The ambient policy is determined by looking up the widget hierarchy for a
/// [DefaultFocusTraversal] widget, and obtaining the focus traversal policy
/// from it. Different focus nodes can inherit difference policies, so part of
/// the app can go in widget order, and part can go in reading order, depending
/// upon the use case.
///
/// Predefined policies include [WidgetOrderFocusTraversalPolicy],
/// [ReadingOrderTraversalPolicy], and [DirectionalFocusTraversalPolicyMixin],
/// but custom policies can be built based upon these policies.
///
242 243 244 245 246 247 248
/// {@tool snippet --template=stateless_widget_scaffold}
/// This example shows how a FocusNode should be managed if not using the
/// [Focus] or [FocusScope] widgets. See the [Focus] widget for a similar
/// example using [Focus] and [FocusScope] widgets.
///
/// ```dart imports
/// import 'package:flutter/services.dart';
249
/// ```
Adam Barth's avatar
Adam Barth committed
250
///
251 252 253 254 255 256 257
/// ```dart preamble
/// class ColorfulButton extends StatefulWidget {
///   ColorfulButton({Key key}) : super(key: key);
///
///   @override
///   _ColorfulButtonState createState() => _ColorfulButtonState();
/// }
Adam Barth's avatar
Adam Barth committed
258
///
259 260
/// class _ColorfulButtonState extends State<ColorfulButton> {
///   FocusNode _node;
261
///   bool _focused = false;
262 263
///   FocusAttachment _nodeAttachment;
///   Color _color = Colors.white;
264
///
265 266 267 268
///   @override
///   void initState() {
///     super.initState();
///     _node = FocusNode(debugLabel: 'Button');
269
///     _node.addListener(_handleFocusChange);
270 271 272
///     _nodeAttachment = _node.attach(context, onKey: _handleKeyPress);
///   }
///
273 274 275 276 277 278 279 280
///   void _handleFocusChange() {
///     if (_node.hasFocus != _focused) {
///       setState(() {
///         _focused = _node.hasFocus;
///       });
///     }
///   }
///
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
///   bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
///     if (event is RawKeyDownEvent) {
///       print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
///       if (event.logicalKey == LogicalKeyboardKey.keyR) {
///         print('Changing color to red.');
///         setState(() {
///           _color = Colors.red;
///         });
///         return true;
///       } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
///         print('Changing color to green.');
///         setState(() {
///           _color = Colors.green;
///         });
///         return true;
///       } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
///         print('Changing color to blue.');
///         setState(() {
///           _color = Colors.blue;
///         });
///         return true;
///       }
///     }
///     return false;
///   }
///
///   @override
///   void dispose() {
309
///     _node.removeListener(_handleFocusChange);
310 311 312 313 314 315 316 317 318 319
///     // The attachment will automatically be detached in dispose().
///     _node.dispose();
///     super.dispose();
///   }
///
///   @override
///   Widget build(BuildContext context) {
///     _nodeAttachment.reparent();
///     return GestureDetector(
///       onTap: () {
320
///         if (_focused) {
321 322
///             _node.unfocus();
///         } else {
323
///            _node.requestFocus();
324 325 326 327 328 329
///         }
///       },
///       child: Center(
///         child: Container(
///           width: 400,
///           height: 100,
330
///           color: _focused ? _color : Colors.white,
331 332
///           alignment: Alignment.center,
///           child: Text(
333
///               _focused ? "I'm in color! Press R,G,B!" : 'Press to focus'),
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
///         ),
///       ),
///     );
///   }
/// }
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
///   final TextTheme textTheme = Theme.of(context).textTheme;
///   return DefaultTextStyle(
///     style: textTheme.display1,
///     child: ColorfulButton(),
///   );
/// }
/// ```
/// {@end-tool}
351
///
352
/// See also:
Adam Barth's avatar
Adam Barth committed
353
///
354 355 356 357 358 359 360 361
///   * [Focus], a widget that manages a [FocusNode] and provides access to
///     focus information and actions to its descendant widgets.
///   * [FocusScope], a widget that manages a [FocusScopeNode] and provides
///     access to scope information and actions to its descendant widgets.
///   * [FocusAttachment], a widget that connects a [FocusScopeNode] to the
///     widget tree.
///   * [FocusManager], a singleton that manages the focus and distributes key
///     events to focused nodes.
362 363 364 365
///   * [FocusTraversalPolicy], a class used to determine how to move the focus
///     to other nodes.
///   * [DefaultFocusTraversal], a widget used to configure the default focus
///     traversal policy for a widget subtree.
366 367 368 369 370 371 372
class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
  /// Creates a focus node.
  ///
  /// The [debugLabel] is ignored on release builds.
  FocusNode({
    String debugLabel,
    FocusOnKeyCallback onKey,
373 374
    bool skipTraversal = false,
    bool canRequestFocus = true,
375
  })  : assert(skipTraversal != null),
376 377 378
        assert(canRequestFocus != null),
        _skipTraversal = skipTraversal,
        _canRequestFocus = canRequestFocus,
379
        _onKey = onKey {
380 381 382 383
    // Set it via the setter so that it does nothing on release builds.
    this.debugLabel = debugLabel;
  }

384 385 386 387 388 389
  /// If true, tells the focus traversal policy to skip over this node for
  /// purposes of the traversal algorithm.
  ///
  /// This may be used to place nodes in the focus tree that may be focused, but
  /// not traversed, allowing them to receive key events as part of the focus
  /// chain, but not be traversed to via focus traversal.
390 391 392 393 394 395 396 397 398
  ///
  /// This is different from [canRequestFocus] because it only implies that the
  /// node can't be reached via traversal, not that it can't be focused. It may
  /// still be focused explicitly.
  bool get skipTraversal => _skipTraversal;
  bool _skipTraversal;
  set skipTraversal(bool value) {
    if (value != _skipTraversal) {
      _skipTraversal = value;
399 400
      _manager?._dirtyNodes?.add(this);
      _manager?._markNeedsUpdate();
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
    }
  }

  /// If true, this focus node may request the primary focus.
  ///
  /// Defaults to true.  Set to false if you want this node to do nothing when
  /// [requestFocus] is called on it. Does not affect the children of this node,
  /// and [hasFocus] can still return true if this node is the ancestor of a
  /// node with primary focus.
  ///
  /// This is different than [skipTraversal] because [skipTraversal] still
  /// allows the node to be focused, just not traversed to via the
  /// [FocusTraversalPolicy]
  ///
  /// Setting [canRequestFocus] to false implies that the node will also be
  /// skipped for traversal purposes.
  ///
  /// See also:
  ///
  ///   - [DefaultFocusTraversal], a widget that sets the traversal policy for
  ///     its descendants.
  ///   - [FocusTraversalPolicy], a class that can be extended to describe a
  ///     traversal policy.
424 425 426 427
  bool get canRequestFocus {
    final FocusScopeNode scope = enclosingScope;
    return _canRequestFocus && (scope == null || scope.canRequestFocus);
  }
428
  bool _canRequestFocus;
429
  @mustCallSuper
430 431
  set canRequestFocus(bool value) {
    if (value != _canRequestFocus) {
432 433
      if (!value) {
        unfocus(focusPrevious: true);
434
      }
435
      _canRequestFocus = value;
436 437
      _manager?._dirtyNodes?.add(this);
      _manager?._markNeedsUpdate();
438 439
    }
  }
440

441 442 443 444 445 446 447 448 449 450 451 452 453 454
  /// The context that was supplied to [attach].
  ///
  /// This is typically the context for the widget that is being focused, as it
  /// is used to determine the bounds of the widget.
  BuildContext get context => _context;
  BuildContext _context;

  /// Called if this focus node receives a key event while focused (i.e. when
  /// [hasFocus] returns true).
  ///
  /// {@macro flutter.widgets.focus_manager.focus.keyEvents}
  FocusOnKeyCallback get onKey => _onKey;
  FocusOnKeyCallback _onKey;

455
  FocusManager _manager;
456 457
  List<FocusNode> _ancestors;
  List<FocusNode> _descendants;
458
  bool _hasKeyboardToken = false;
459

460
  /// Returns the parent node for this object.
Adam Barth's avatar
Adam Barth committed
461
  ///
462 463 464 465 466 467 468 469 470 471
  /// All nodes except for the root [FocusScopeNode] ([FocusManager.rootScope])
  /// will be given a parent when they are added to the focus tree, which is
  /// done using [FocusAttachment.reparent].
  FocusNode get parent => _parent;
  FocusNode _parent;

  /// An iterator over the children of this node.
  Iterable<FocusNode> get children => _children;
  final List<FocusNode> _children = <FocusNode>[];

472 473
  /// An iterator over the children that are allowed to be traversed by the
  /// [FocusTraversalPolicy].
474
  Iterable<FocusNode> get traversalChildren {
475 476 477
    if (!canRequestFocus) {
      return const <FocusNode>[];
    }
478
    return children.where(
479
      (FocusNode node) => !node.skipTraversal && node.canRequestFocus,
480 481
    );
  }
482

483
  /// A debug label that is used for diagnostic output.
Adam Barth's avatar
Adam Barth committed
484
  ///
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
  /// Will always return null in release builds.
  String get debugLabel => _debugLabel;
  String _debugLabel;
  set debugLabel(String value) {
    assert(() {
      // Only set the value in debug builds.
      _debugLabel = value;
      return true;
    }());
  }

  FocusAttachment _attachment;

  /// An [Iterable] over the hierarchy of children below this one, in
  /// depth-first order.
500 501 502 503 504 505 506 507
  Iterable<FocusNode> get descendants {
    if (_descendants == null) {
      final List<FocusNode> result = <FocusNode>[];
      for (FocusNode child in _children) {
        result.addAll(child.descendants);
        result.add(child);
      }
      _descendants = result;
508
    }
509
    return _descendants;
510 511
  }

512
  /// Returns all descendants which do not have the [skipTraversal] flag set.
513
  Iterable<FocusNode> get traversalDescendants => descendants.where((FocusNode node) => !node.skipTraversal && node.canRequestFocus);
514

515
  /// An [Iterable] over the ancestors of this node.
Adam Barth's avatar
Adam Barth committed
516
  ///
517 518 519
  /// Iterates the ancestors of this node starting at the parent and iterating
  /// over successively more remote ancestors of this node, ending at the root
  /// [FocusScope] ([FocusManager.rootScope]).
520 521 522 523 524 525 526 527 528
  Iterable<FocusNode> get ancestors {
    if (_ancestors == null) {
      final List<FocusNode> result = <FocusNode>[];
      FocusNode parent = _parent;
      while (parent != null) {
        result.add(parent);
        parent = parent._parent;
      }
      _ancestors = result;
529
    }
530
    return _ancestors;
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
  }

  /// Whether this node has input focus.
  ///
  /// A [FocusNode] has focus when it is an ancestor of a node that returns true
  /// from [hasPrimaryFocus], or it has the primary focus itself.
  ///
  /// The [hasFocus] accessor is different from [hasPrimaryFocus] in that
  /// [hasFocus] is true if the node is anywhere in the focus chain, but for
  /// [hasPrimaryFocus] the node must to be at the end of the chain to return
  /// true.
  ///
  /// A node that returns true for [hasFocus] will receive key events if none of
  /// its focused descendants returned true from their [onKey] handler.
  ///
  /// This object is a [ChangeNotifier], and notifies its [Listenable] listeners
  /// (registered via [addListener]) whenever this value changes.
  ///
  /// See also:
  ///
  ///   * [Focus.isAt], which is a static method that will return the focus
  ///     state of the nearest ancestor [Focus] widget's focus node.
  bool get hasFocus {
554
    if (_manager?.primaryFocus == null) {
555 556 557 558 559
      return false;
    }
    if (hasPrimaryFocus) {
      return true;
    }
560
    return _manager.primaryFocus.ancestors.contains(this);
561 562 563 564 565 566 567 568 569 570 571 572 573 574
  }

  /// Returns true if this node currently has the application-wide input focus.
  ///
  /// A [FocusNode] has the primary focus when the node is focused in its
  /// nearest ancestor [FocusScopeNode] and [hasFocus] is true for all its
  /// ancestor nodes, but none of its descendants.
  ///
  /// This is different from [hasFocus] in that [hasFocus] is true if the node
  /// is anywhere in the focus chain, but here the node has to be at the end of
  /// the chain to return true.
  ///
  /// A node that returns true for [hasPrimaryFocus] will be the first node to
  /// receive key events through its [onKey] handler.
Adam Barth's avatar
Adam Barth committed
575
  ///
576
  /// This object notifies its listeners whenever this value changes.
577
  bool get hasPrimaryFocus => _manager?.primaryFocus == this;
578

579
  /// Returns the [FocusHighlightMode] that is currently in effect for this node.
580
  FocusHighlightMode get highlightMode => FocusManager.instance.highlightMode;
581

582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
  /// Returns the nearest enclosing scope node above this node, including
  /// this node, if it's a scope.
  ///
  /// Returns null if no scope is found.
  ///
  /// Use [enclosingScope] to look for scopes above this node.
  FocusScopeNode get nearestScope => enclosingScope;

  /// Returns the nearest enclosing scope node above this node, or null if the
  /// node has not yet be added to the focus tree.
  ///
  /// If this node is itself a scope, this will only return ancestors of this
  /// scope.
  ///
  /// Use [nearestScope] to start at this node instead of above it.
  FocusScopeNode get enclosingScope {
    return ancestors.firstWhere((FocusNode node) => node is FocusScopeNode, orElse: () => null);
  }

  /// Returns the size of the attached widget's [RenderObject], in logical
  /// units.
  Size get size {
    assert(
        context != null,
        "Tried to get the size of a focus node that didn't have its context set yet.\n"
        'The context needs to be set before trying to evaluate traversal policies. This '
        'is typically done with the attach method.');
    return context.findRenderObject().semanticBounds.size;
  }

  /// Returns the global offset to the upper left corner of the attached
  /// widget's [RenderObject], in logical units.
  Offset get offset {
    assert(
        context != null,
        "Tried to get the offset of a focus node that didn't have its context set yet.\n"
        'The context needs to be set before trying to evaluate traversal policies. This '
        'is typically done with the attach method.');
    final RenderObject object = context.findRenderObject();
    return MatrixUtils.transformPoint(object.getTransformTo(null), object.semanticBounds.topLeft);
  }

  /// Returns the global rectangle of the attached widget's [RenderObject], in
  /// logical units.
  Rect get rect {
    assert(
        context != null,
        "Tried to get the bounds of a focus node that didn't have its context set yet.\n"
        'The context needs to be set before trying to evaluate traversal policies. This '
        'is typically done with the attach method.');
    final RenderObject object = context.findRenderObject();
    final Offset globalOffset = MatrixUtils.transformPoint(object.getTransformTo(null), object.semanticBounds.topLeft);
    return globalOffset & object.semanticBounds.size;
  }

  /// Removes focus from a node that has the primary focus, and cancels any
  /// outstanding requests to focus it.
  ///
  /// Calling [requestFocus] sends a request to the [FocusManager] to make that
  /// node the primary focus, which schedules a microtask to resolve the latest
  /// request into an update of the focus state on the tree. Calling [unfocus]
  /// cancels a request that has been requested, but not yet acted upon.
  ///
  /// This method is safe to call regardless of whether this node has ever
  /// requested focus.
  ///
  /// Has no effect on nodes that return true from [hasFocus], but false from
  /// [hasPrimaryFocus].
650 651 652 653 654 655 656 657
  ///
  /// if [focusPrevious] is true, then rather than losing all focus, the focus
  /// will be moved to the node that the [enclosingScope] thinks should have it,
  /// based on its history of nodes that were set as first focus on it using
  /// [FocusScopeNode.setFirstFocus].
  void unfocus({ bool focusPrevious = false }) {
    assert(focusPrevious != null);
    if (!hasFocus && (_manager != null && _manager._nextFocus != this)) {
658 659
      return;
    }
660
    if (!hasPrimaryFocus) {
661 662
      // If we are in the focus chain, but not the primary focus, then unfocus
      // the primary instead.
663 664 665 666 667 668 669 670 671
      _manager?.primaryFocus?.unfocus(focusPrevious: focusPrevious);
    }
    _manager?._willUnfocusNode(this);
    final FocusScopeNode scope = enclosingScope;
    if (scope != null) {
      scope._focusedChildren.remove(this);
      if (focusPrevious) {
        scope._doRequestFocus();
      }
672
    }
673
  }
674

675 676 677 678 679 680 681 682 683
  /// 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
684
  /// [TextInputConnection] should show the keyboard (i.e. call
685 686 687
  /// [TextInputConnection.show]) only if it successfully consumes the keyboard
  /// token from the focus node.
  ///
688
  /// Returns true if this method successfully consumes the keyboard token.
689
  bool consumeKeyboardToken() {
690
    if (!_hasKeyboardToken) {
691
      return false;
692
    }
693 694 695 696
    _hasKeyboardToken = false;
    return true;
  }

697 698 699 700 701 702
  // Marks the node as dirty, meaning that it needs to notify listeners of a
  // focus change the next time focus is resolved by the manager.
  void _markAsDirty({FocusNode newFocus}) {
    if (_manager != null) {
      // If we have a manager, then let it handle the focus change.
      _manager._markNeedsUpdate(newFocus: newFocus);
703
      _manager._dirtyNodes?.add(this);
704 705 706 707 708 709 710 711
    } else {
      // If we don't have a manager, then change the focus locally.
      newFocus?._setAsFocusedChild();
      newFocus?._notify();
      if (newFocus != this) {
        _notify();
      }
    }
712 713
  }

714 715
  // Removes the given FocusNode and its children as a child of this node.
  @mustCallSuper
716
  void _removeChild(FocusNode node, {bool removeScopeFocus = true}) {
717
    assert(node != null);
718 719 720
    assert(_children.contains(node), "Tried to remove a node that wasn't a child.");
    assert(node._parent == this);
    assert(node._manager == _manager);
721

722 723 724
    if (removeScopeFocus) {
      node.enclosingScope?._focusedChildren?.remove(node);
    }
725

726 727
    node._parent = null;
    _children.remove(node);
728 729 730 731
    for (FocusNode ancestor in ancestors) {
      ancestor._descendants = null;
    }
    _descendants = null;
732
    assert(_manager == null || !_manager.rootScope.descendants.contains(node));
733
  }
734

735
  void _updateManager(FocusManager manager) {
736 737 738
    _manager = manager;
    for (FocusNode descendant in descendants) {
      descendant._manager = manager;
739
      descendant._ancestors = null;
740 741 742
    }
  }

743 744 745 746 747 748 749 750 751 752 753 754
  // Used by FocusAttachment.reparent to perform the actual parenting operation.
  @mustCallSuper
  void _reparent(FocusNode child) {
    assert(child != null);
    assert(child != this, 'Tried to make a child into a parent of itself.');
    if (child._parent == this) {
      assert(_children.contains(child), "Found a node that says it's a child, but doesn't appear in the child list.");
      // The child is already a child of this parent.
      return;
    }
    assert(_manager == null || child != _manager.rootScope, "Reparenting the root node isn't allowed.");
    assert(!ancestors.contains(child), 'The supplied child is already an ancestor of this node. Loops are not allowed.');
755
    final FocusScopeNode oldScope = child.enclosingScope;
756
    final bool hadFocus = child.hasFocus;
757
    child._parent?._removeChild(child, removeScopeFocus: oldScope != nearestScope);
758 759
    _children.add(child);
    child._parent = this;
760
    child._ancestors = null;
761
    child._updateManager(_manager);
762 763 764
    for (FocusNode ancestor in child.ancestors) {
      ancestor._descendants = null;
    }
765 766
    if (hadFocus) {
      // Update the focus chain for the current focus without changing it.
767
      _manager?.primaryFocus?._setAsFocusedChild();
768
    }
769 770 771
    if (oldScope != null && child.context != null && child.enclosingScope != oldScope) {
      DefaultFocusTraversal.of(child.context, nullOk: true)?.changedScope(node: child, oldScope: oldScope);
    }
772 773
  }

774 775 776 777 778 779 780 781 782 783 784 785 786
  /// Called by the _host_ [StatefulWidget] to attach a [FocusNode] to the
  /// widget tree.
  ///
  /// In order to attach a [FocusNode] to the widget tree, call [attach],
  /// typically from the [StatefulWidget]'s [State.initState] method.
  ///
  /// If the focus node in the host widget is swapped out, the new node will
  /// need to be attached. [FocusAttachment.detach] should be called on the old
  /// node, and then [attach] called on the new node. This typically happens in
  /// the [State.didUpdateWidget] method.
  @mustCallSuper
  FocusAttachment attach(BuildContext context, {FocusOnKeyCallback onKey}) {
    _context = context;
787
    _onKey = onKey ?? _onKey;
788 789
    _attachment = FocusAttachment._(this);
    return _attachment;
790 791
  }

792 793
  @override
  void dispose() {
794
    // Detaching will also unfocus and clean up the manager's data structures.
795 796
    _attachment?.detach();
    super.dispose();
797 798
  }

799 800 801 802 803
  @mustCallSuper
  void _notify() {
    if (_parent == null) {
      // no longer part of the tree, so don't notify.
      return;
804
    }
805 806
    if (hasPrimaryFocus) {
      _setAsFocusedChild();
807
    }
808
    notifyListeners();
809 810
  }

811 812
  /// Requests the primary focus for this node, or for a supplied [node], which
  /// will also give focus to its [ancestors].
Adam Barth's avatar
Adam Barth committed
813
  ///
814
  /// If called without a node, request focus for this node.
815
  ///
816 817
  /// If the given [node] is not yet a part of the focus tree, then this method
  /// will add the [node] as a child of this node before requesting focus.
Adam Barth's avatar
Adam Barth committed
818
  ///
819 820 821 822 823
  /// If the given [node] is a [FocusScopeNode] and that focus scope node has a
  /// non-null [focusedChild], then request the focus for the focused child.
  /// This process is recursive and continues until it encounters either a focus
  /// scope node with a null focused child or an ordinary (non-scope)
  /// [FocusNode] is found.
Adam Barth's avatar
Adam Barth committed
824
  ///
825 826 827 828 829 830 831
  /// The node is notified that it has received the primary focus in a
  /// microtask, so notification may lag the request by up to one frame.
  void requestFocus([FocusNode node]) {
    if (node != null) {
      if (node._parent == null) {
        _reparent(node);
      }
832
      assert(node.ancestors.contains(this), 'Focus was requested for a node that is not a descendant of the scope from which it was requested.');
833
      node._doRequestFocus();
834
      return;
835
    }
836
    _doRequestFocus();
837 838
  }

839
  // Note that this is overridden in FocusScopeNode.
840
  void _doRequestFocus() {
841
    if (!canRequestFocus) {
842
      assert(_focusDebug('Node NOT requesting focus because canRequestFocus is false: $this'));
843 844
      return;
    }
845 846
    _setAsFocusedChild();
    if (hasPrimaryFocus) {
847
      return;
848 849
    }
    _hasKeyboardToken = true;
850
    assert(_focusDebug('Node requesting focus: $this'));
851
    _markAsDirty(newFocus: this);
852 853
  }

854 855 856 857 858 859 860 861 862
  /// Sets this node as the [FocusScopeNode.focusedChild] of the enclosing
  /// scope.
  ///
  /// Sets this node as the focused child for the enclosing scope, and that
  /// scope as the focused child for the scope above it, etc., until it reaches
  /// the root node. It doesn't change the primary focus, it just changes what
  /// node would be focused if the enclosing scope receives focus, and keeps
  /// track of previously focused children in that scope, so that if the focused
  /// child in that scope is removed, the previous focus returns.
863 864 865 866
  void _setAsFocusedChild() {
    FocusNode scopeFocus = this;
    for (FocusScopeNode ancestor in ancestors.whereType<FocusScopeNode>()) {
      assert(scopeFocus != ancestor, 'Somehow made a loop by setting focusedChild to its scope.');
867
      assert(_focusDebug('Setting $scopeFocus as focused child for scope:', <String>[ancestor.toString()]));
868 869 870 871 872 873 874
      // Remove it anywhere in the focused child history.
      ancestor._focusedChildren.remove(scopeFocus);
      // Add it to the end of the list, which is also the top of the queue: The
      // end of the list represents the currently focused child.
      ancestor._focusedChildren.add(scopeFocus);
      scopeFocus = ancestor;
    }
875 876
  }

877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
  /// Request to move the focus to the next focus node, by calling the
  /// [FocusTraversalPolicy.next] method.
  ///
  /// Returns true if it successfully found a node and requested focus.
  bool nextFocus() => DefaultFocusTraversal.of(context).next(this);

  /// Request to move the focus to the previous focus node, by calling the
  /// [FocusTraversalPolicy.previous] method.
  ///
  /// Returns true if it successfully found a node and requested focus.
  bool previousFocus() => DefaultFocusTraversal.of(context).previous(this);

  /// Request to move the focus to the nearest focus node in the given
  /// direction, by calling the [FocusTraversalPolicy.inDirection] method.
  ///
  /// Returns true if it successfully found a node and requested focus.
  bool focusInDirection(TraversalDirection direction) => DefaultFocusTraversal.of(context).inDirection(this, direction);

895 896 897 898
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<BuildContext>('context', context, defaultValue: null));
899
    properties.add(FlagProperty('canRequestFocus', value: canRequestFocus, ifFalse: 'NOT FOCUSABLE', defaultValue: true));
900 901
    properties.add(FlagProperty('hasFocus', value: hasFocus && !hasPrimaryFocus, ifTrue: 'IN FOCUS PATH', defaultValue: false));
    properties.add(FlagProperty('hasPrimaryFocus', value: hasPrimaryFocus, ifTrue: 'PRIMARY FOCUS', defaultValue: false));
902 903
  }

904 905 906 907 908 909
  @override
  List<DiagnosticsNode> debugDescribeChildren() {
    int count = 1;
    return _children.map<DiagnosticsNode>((FocusNode child) {
      return child.toDiagnosticsNode(name: 'Child ${count++}');
    }).toList();
910
  }
911 912 913 914 915

  @override
  String toStringShort() {
    return '${describeIdentity(this)}${debugLabel != null && debugLabel.isNotEmpty ? '($debugLabel)' : ''}';
  }
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
}

/// A subclass of [FocusNode] that acts as a scope for its descendants,
/// maintaining information about which descendant is currently or was last
/// focused.
///
/// _Please see the [FocusScope] and [Focus] widgets, which are utility widgets
/// that manage their own [FocusScopeNode]s and [FocusNode]s, respectively. If
/// they aren't appropriate, [FocusScopeNode]s can be managed directly._
///
/// [FocusScopeNode] organizes [FocusNodes] into _scopes_. Scopes form sub-trees
/// of nodes that can be traversed as a group. Within a scope, the most recent
/// nodes to have focus are remembered, and if a node is focused and then
/// removed, the original node receives focus again.
///
/// From a [FocusScopeNode], calling [setFirstFocus], sets the given focus scope
/// as the [focusedChild] of this node, adopting if it isn't already part of the
/// focus tree.
///
935
/// {@macro flutter.widgets.focus_manager.focus.lifecycle}
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
/// {@macro flutter.widgets.focus_manager.focus.keyEvents}
///
/// See also:
///
///   * [Focus], a widget that manages a [FocusNode] and provides access to
///     focus information and actions to its descendant widgets.
///   * [FocusScope], a widget that manages a [FocusScopeNode] and provides
///     access to scope information and actions to its descendant widgets.
///   * [FocusAttachment], a widget that connects a [FocusScopeNode] to the
///     focus tree.
///   * [FocusManager], a singleton that manages the focus and distributes key
///     events to focused nodes.
class FocusScopeNode extends FocusNode {
  /// Creates a FocusScope node.
  ///
  /// All parameters are optional.
  FocusScopeNode({
    String debugLabel,
    FocusOnKeyCallback onKey,
955 956 957 958 959 960 961 962 963 964
    bool skipTraversal = false,
    bool canRequestFocus = true,
  })  : assert(skipTraversal != null),
        assert(canRequestFocus != null),
        super(
          debugLabel: debugLabel,
          onKey: onKey,
          canRequestFocus: canRequestFocus,
          skipTraversal: skipTraversal,
        );
965 966 967

  @override
  FocusScopeNode get nearestScope => this;
968

969 970 971 972 973
  /// Returns true if this scope is the focused child of its parent scope.
  bool get isFirstFocus => enclosingScope.focusedChild == this;

  /// Returns the child of this node that should receive focus if this scope
  /// node receives focus.
974
  ///
975 976
  /// If [hasFocus] is true, then this points to the child of this node that is
  /// currently focused.
977
  ///
978 979
  /// Returns null if there is no currently focused child.
  FocusNode get focusedChild {
980
    assert(_focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this, 'Focused child does not have the same idea of its enclosing scope as the scope does.');
981 982 983 984 985 986 987 988 989 990 991 992 993 994
    return _focusedChildren.isNotEmpty ? _focusedChildren.last : null;
  }

  // A stack of the children that have been set as the focusedChild, most recent
  // last (which is the top of the stack).
  final List<FocusNode> _focusedChildren = <FocusNode>[];

  /// Make the given [scope] the active child scope for this scope.
  ///
  /// If the given [scope] is not yet a part of the focus tree, then add it to
  /// the tree as a child of this scope. If it is already part of the focus
  /// tree, the given scope must be a descendant of this scope.
  void setFirstFocus(FocusScopeNode scope) {
    assert(scope != null);
995
    assert(scope != this, 'Unexpected self-reference in setFirstFocus.');
996
    assert(_focusDebug('Setting scope as first focus in $this to node:', <String>[scope.toString()]));
997 998 999 1000
    if (scope._parent == null) {
      _reparent(scope);
    }
    assert(scope.ancestors.contains(this), '$FocusScopeNode $scope must be a child of $this to set it as first focus.');
1001
    if (hasFocus) {
1002
      scope._doRequestFocus();
1003 1004
    } else {
      scope._setAsFocusedChild();
1005
    }
1006 1007
  }

1008
  /// If this scope lacks a focus, request that the given node become the focus.
1009
  ///
1010 1011
  /// If the given node is not yet part of the focus tree, then add it as a
  /// child of this node.
1012
  ///
1013 1014 1015 1016 1017 1018
  /// 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 primary focus in a
  /// microtask, so notification may lag the request by up to one frame.
  void autofocus(FocusNode node) {
1019
    assert(_focusDebug('Node autofocusing: $node'));
1020 1021 1022 1023
    if (focusedChild == null) {
      if (node._parent == null) {
        _reparent(node);
      }
1024
      assert(node.ancestors.contains(this), 'Autofocus was requested for a node that is not a descendant of the scope from which it was requested.');
1025
      node._doRequestFocus();
1026
    }
1027 1028
  }

1029
  @override
1030
  void _doRequestFocus() {
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
    // Start with the primary focus as the focused child of this scope, if there
    // is one. Otherwise start with this node itself.
    FocusNode primaryFocus = focusedChild ?? this;
    // Keep going down through scopes until the ultimately focusable item is
    // found, a scope doesn't have a focusedChild, or a non-scope is
    // encountered.
    while (primaryFocus is FocusScopeNode && primaryFocus.focusedChild != null) {
      final FocusScopeNode scope = primaryFocus;
      primaryFocus = scope.focusedChild;
    }
1041 1042 1043 1044 1045 1046 1047
    if (identical(primaryFocus, this)) {
      // We didn't find a FocusNode at the leaf, so we're focusing the scope, if
      // allowed.
      if (primaryFocus.canRequestFocus) {
        _setAsFocusedChild();
        _markAsDirty(newFocus: this);
      }
1048
    } else {
1049 1050 1051
      // We found a FocusScope at the leaf, so ask it to focus itself instead of
      // this scope. That will cause this scope to return true from hasFocus,
      // but false from hasPrimaryFocus.
1052
      primaryFocus._doRequestFocus();
1053
    }
1054 1055 1056
  }

  @override
1057 1058
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1059 1060 1061 1062
    if (_focusedChildren.isEmpty) {
      return;
    }
    final List<String> childList = _focusedChildren.reversed.map<String>((FocusNode child) {
1063
      return child.toStringShort();
1064 1065
    }).toList();
    properties.add(IterableProperty<String>('focusedChildren', childList, defaultValue: <String>[]));
1066 1067 1068
  }
}

1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
/// An enum to describe which kind of focus highlight behavior to use when
/// displaying focus information.
enum FocusHighlightMode {
  /// Touch interfaces will not show the focus highlight except for controls
  /// which bring up the soft keyboard.
  ///
  /// If a device that uses a traditional mouse and keyboard has a touch screen
  /// attached, it can also enter `touch` mode if the user is using the touch
  /// screen.
  touch,

  /// Traditional interfaces (keyboard and mouse) will show the currently
  /// focused control via a focus highlight of some sort.
  ///
  /// If a touch device (like a mobile phone) has a keyboard and/or mouse
  /// attached, it also can enter `traditional` mode if the user is using these
  /// input devices.
  traditional,
}

/// An enum to describe how the current value of [FocusManager.highlightMode] is
/// determined. The strategy is set on [FocusManager.highlightStrategy].
enum FocusHighlightStrategy {
  /// Automatic switches between the various highlight modes based on the last
  /// kind of input that was received. This is the default.
  automatic,

  /// [FocusManager.highlightMode] always returns [FocusHighlightMode.touch].
  alwaysTouch,

  /// [FocusManager.highlightMode] always returns [FocusHighlightMode.traditional].
  alwaysTraditional,
}

1103 1104
/// Manages the focus tree.
///
1105 1106 1107
/// The focus tree keeps track of which [FocusNode] is the user's current
/// keyboard focus. The widget that owns the [FocusNode] often listens for
/// keyboard events.
Adam Barth's avatar
Adam Barth committed
1108
///
1109 1110 1111
/// 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.
Adam Barth's avatar
Adam Barth committed
1112
///
1113
/// The [FocusManager] is held by the [WidgetsBinding] as
1114 1115 1116 1117 1118
/// [WidgetsBinding.focusManager], and can be conveniently accessed using the
/// [focusManager] global accessor.
///
/// To find the [FocusScopeNode] for a given [BuildContext], use
/// [FocusScope.of].
1119
///
1120
/// The [FocusManager] knows nothing about [FocusNode]s other than the one that
1121 1122 1123 1124 1125
/// is currently focused (accessible via the [primaryFocus] global accessor). If
/// a [FocusScopeNode] is removed, then the [FocusManager] will attempt to focus
/// the next [FocusScopeNode] in the focus tree that it maintains, but if the
/// current focus in that [FocusScopeNode] is null, it will stop there, and no
/// [FocusNode] will have focus.
1126
///
1127
/// See also:
Adam Barth's avatar
Adam Barth committed
1128
///
1129
///  * [FocusNode], which is a node in the focus tree that can receive focus.
1130
///  * [FocusScopeNode], which is a node in the focus tree used to collect
1131 1132
///    subtrees into groups.
///  * [Focus.of], which provides the nearest ancestor [FocusNode] for a given
1133
///    [BuildContext].
1134 1135
///  * [FocusScope.of], which provides the nearest ancestor [FocusScopeNode] for
///    a given [BuildContext].
1136 1137
///  * The [focusManager] and [primaryFocus] global accessors, for convenient
///    access from anywhere to the current focus manager state.
1138
class FocusManager with DiagnosticableTreeMixin {
1139
  /// Creates an object that manages the focus tree.
Adam Barth's avatar
Adam Barth committed
1140
  ///
1141
  /// This constructor is rarely called directly. To access the [FocusManager],
1142 1143
  /// consider using the [focusManager] accessor instead (which gets it from the
  /// [WidgetsBinding] singleton).
1144 1145
  FocusManager() {
    rootScope._manager = this;
1146
    RawKeyboard.instance.addListener(_handleRawKeyEvent);
1147 1148 1149
    GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
  }

1150 1151 1152 1153
  /// Provides convenient access to the current [FocusManager] singleton from
  /// the [WidgetsBinding] instance.
  static FocusManager get instance => WidgetsBinding.instance.focusManager;

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269
  bool _lastInteractionWasTouch = true;

  /// Sets the strategy by which [highlightMode] is determined.
  ///
  /// If set to [FocusHighlightStrategy.automatic], then the highlight mode will
  /// change depending upon the interaction mode used last. For instance, if the
  /// last interaction was a touch interaction, then [highlightMode] will return
  /// [FocusHighlightMode.touch], and focus highlights will only appear on
  /// widgets that bring up a soft keyboard. If the last interaction was a
  /// non-touch interaction (hardware keyboard press, mouse click, etc.), then
  /// [highlightMode] will return [FocusHighlightMode.traditional], and focus
  /// highlights will appear on all widgets.
  ///
  /// If set to [FocusHighlightStrategy.alwaysTouch] or
  /// [FocusHighlightStrategy.alwaysTraditional], then [highlightMode] will
  /// always return [FocusHighlightMode.touch] or
  /// [FocusHighlightMode.traditional], respectively, regardless of the last UI
  /// interaction type.
  ///
  /// The initial value of [highlightMode] depends upon the value of
  /// [defaultTargetPlatform] and
  /// [RendererBinding.instance.mouseTracker.mouseIsConnected], making a guess
  /// about which interaction is most appropriate for the initial interaction
  /// mode.
  ///
  /// Defaults to [FocusHighlightStrategy.automatic].
  FocusHighlightStrategy get highlightStrategy => _highlightStrategy;
  FocusHighlightStrategy _highlightStrategy = FocusHighlightStrategy.automatic;
  set highlightStrategy(FocusHighlightStrategy highlightStrategy) {
    _highlightStrategy = highlightStrategy;
    _updateHighlightMode();
  }

  /// Indicates the current interaction mode for focus highlights.
  ///
  /// The value returned depends upon the [highlightStrategy] used, and possibly
  /// (depending on the value of [highlightStrategy]) the most recent
  /// interaction mode that they user used.
  ///
  /// If [highlightMode] returns [FocusHighlightMode.touch], then widgets should
  /// not draw their focus highlight unless they perform text entry.
  ///
  /// If [highlightMode] returns [FocusHighlightMode.traditional], then widgets should
  /// draw their focus highlight whenever they are focused.
  FocusHighlightMode get highlightMode => _highlightMode;
  FocusHighlightMode _highlightMode = FocusHighlightMode.touch;

  // Update function to be called whenever the state relating to highlightMode
  // changes.
  void _updateHighlightMode() {
    // Assume that if we're on one of these mobile platforms, or if there's no
    // mouse connected, that the initial interaction will be touch-based, and
    // that it's traditional mouse and keyboard on all others.
    //
    // This only affects the initial value: the ongoing value is updated as soon
    // as any input events are received.
    _lastInteractionWasTouch ??= Platform.isAndroid || Platform.isIOS || !WidgetsBinding.instance.mouseTracker.mouseIsConnected;
    FocusHighlightMode newMode;
    switch (highlightStrategy) {
      case FocusHighlightStrategy.automatic:
        if (_lastInteractionWasTouch) {
          newMode = FocusHighlightMode.touch;
        } else {
          newMode = FocusHighlightMode.traditional;
        }
        break;
      case FocusHighlightStrategy.alwaysTouch:
        newMode = FocusHighlightMode.touch;
        break;
      case FocusHighlightStrategy.alwaysTraditional:
        newMode = FocusHighlightMode.traditional;
        break;
    }
    if (newMode != _highlightMode) {
      _highlightMode = newMode;
      _notifyHighlightModeListeners();
    }
  }

  // The list of listeners for [highlightMode] state changes.
  final HashedObserverList<ValueChanged<FocusHighlightMode>> _listeners = HashedObserverList<ValueChanged<FocusHighlightMode>>();

  /// Register a closure to be called when the [FocusManager] notifies its listeners
  /// that the value of [highlightMode] has changed.
  void addHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.add(listener);

  /// Remove a previously registered closure from the list of closures that the
  /// [FocusManager] notifies.
  void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners?.remove(listener);

  void _notifyHighlightModeListeners() {
    if (_listeners.isEmpty) {
      return;
    }
    final List<ValueChanged<FocusHighlightMode>> localListeners = List<ValueChanged<FocusHighlightMode>>.from(_listeners);
    for (ValueChanged<FocusHighlightMode> listener in localListeners) {
      try {
        if (_listeners.contains(listener)) {
          listener(_highlightMode);
        }
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'widgets library',
          context: ErrorDescription('while dispatching notifications for $runtimeType'),
          informationCollector: () sync* {
            yield DiagnosticsProperty<FocusManager>(
              'The $runtimeType sending notification was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            );
          },
        ));
      }
    }
1270 1271 1272
  }

  /// The root [FocusScopeNode] in the focus tree.
Adam Barth's avatar
Adam Barth committed
1273
  ///
1274 1275 1276 1277
  /// This field is rarely used directly. To find the nearest [FocusScopeNode]
  /// for a given [FocusNode], call [FocusNode.nearestScope].
  final FocusScopeNode rootScope = FocusScopeNode(debugLabel: 'Root Focus Scope');

1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296
  void _handlePointerEvent(PointerEvent event) {
    bool currentInteractionIsTouch;
    switch (event.kind) {
      case PointerDeviceKind.touch:
      case PointerDeviceKind.stylus:
      case PointerDeviceKind.invertedStylus:
        currentInteractionIsTouch = true;
        break;
      case PointerDeviceKind.mouse:
      case PointerDeviceKind.unknown:
        currentInteractionIsTouch = false;
        break;
    }
    if (_lastInteractionWasTouch != currentInteractionIsTouch) {
      _lastInteractionWasTouch = currentInteractionIsTouch;
      _updateHighlightMode();
    }
  }

1297
  void _handleRawKeyEvent(RawKeyEvent event) {
1298 1299 1300 1301 1302 1303 1304
    // Update highlightMode first, since things responding to the keys might
    // look at the highlight mode, and it should be accurate.
    if (_lastInteractionWasTouch) {
      _lastInteractionWasTouch = false;
      _updateHighlightMode();
    }

1305
    assert(_focusDebug('Received key event ${event.logicalKey}'));
1306 1307
    // Walk the current focus from the leaf to the root, calling each one's
    // onKey on the way up, and if one responds that they handled it, stop.
1308
    if (_primaryFocus == null) {
1309
      assert(_focusDebug('No primary focus for key event, ignored: $event'));
1310 1311
      return;
    }
1312
    bool handled = false;
1313
    for (FocusNode node in <FocusNode>[_primaryFocus, ..._primaryFocus.ancestors]) {
1314
      if (node.onKey != null && node.onKey(node, event)) {
1315 1316
        assert(_focusDebug('Node $node handled key event $event.'));
        handled = true;
1317 1318 1319
        break;
      }
    }
1320 1321 1322
    if (!handled) {
      assert(_focusDebug('Key event not handled by anyone: $event.'));
    }
1323 1324
  }

1325 1326 1327 1328
  /// The node that currently has the primary focus.
  FocusNode get primaryFocus => _primaryFocus;
  FocusNode _primaryFocus;

1329 1330 1331 1332 1333 1334
  // The node that has requested to have the primary focus, but hasn't been
  // given it yet.
  FocusNode _nextFocus;
  // The set of nodes that need to notify their listeners of changes at the next
  // update.
  final Set<FocusNode> _dirtyNodes = <FocusNode>{};
1335

1336 1337 1338 1339
  // Called to indicate that the given node is being unfocused, and that any
  // pending request to be focused should be canceled.
  void _willUnfocusNode(FocusNode node) {
    assert(node != null);
1340
    assert(_focusDebug('Unfocusing node $node'));
1341 1342 1343 1344 1345 1346 1347
    if (_primaryFocus == node || _nextFocus == node) {
      if (_primaryFocus == node) {
        _primaryFocus = null;
      }
      if (_nextFocus == node) {
        _nextFocus = null;
      }
1348 1349 1350
      _dirtyNodes.add(node);
      _markNeedsUpdate();
    }
1351
    assert(_focusDebug('Unfocused node $node:', <String>['primary focus is $_primaryFocus', 'next focus will be $_nextFocus']));
1352 1353
  }

1354
  // True indicates that there is an update pending.
1355
  bool _haveScheduledUpdate = false;
1356 1357 1358 1359 1360 1361 1362

  // Request that an update be scheduled, optionally requesting focus for the
  // given newFocus node.
  void _markNeedsUpdate({FocusNode newFocus}) {
    // If newFocus isn't specified, then don't mess with _nextFocus, just
    // schedule the update.
    _nextFocus = newFocus ?? _nextFocus;
1363
    assert(_focusDebug('Scheduling update, next focus will be $_nextFocus'));
1364
    if (_haveScheduledUpdate) {
1365
      return;
1366
    }
1367
    _haveScheduledUpdate = true;
1368
    scheduleMicrotask(_applyFocusChange);
1369 1370
  }

1371
  void _applyFocusChange() {
1372
    _haveScheduledUpdate = false;
1373
    assert(_focusDebug('Refreshing focus state. Next focus will be $_nextFocus'));
1374 1375
    final FocusNode previousFocus = _primaryFocus;
    if (_primaryFocus == null && _nextFocus == null) {
1376
      // If we don't have any current focus, and nobody has asked to focus yet,
1377
      // then revert to the root scope.
1378 1379
      _nextFocus = rootScope;
    }
1380 1381
    if (_nextFocus != null && _nextFocus != _primaryFocus) {
      _primaryFocus = _nextFocus;
1382 1383 1384 1385 1386 1387 1388 1389
      final Set<FocusNode> previousPath = previousFocus?.ancestors?.toSet() ?? <FocusNode>{};
      final Set<FocusNode> nextPath = _nextFocus.ancestors.toSet();
      // Notify nodes that are newly focused.
      _dirtyNodes.addAll(nextPath.difference(previousPath));
      // Notify nodes that are no longer focused
      _dirtyNodes.addAll(previousPath.difference(nextPath));
      _nextFocus = null;
    }
1390
    if (previousFocus != _primaryFocus) {
1391
      assert(_focusDebug('Updating focus from $previousFocus to $_primaryFocus'));
1392 1393 1394
      if (previousFocus != null) {
        _dirtyNodes.add(previousFocus);
      }
1395 1396
      if (_primaryFocus != null) {
        _dirtyNodes.add(_primaryFocus);
1397 1398
      }
    }
1399
    assert(_focusDebug('Notifying ${_dirtyNodes.length} dirty nodes:', _dirtyNodes.toList().map<String>((FocusNode node) => node.toString())));
1400 1401 1402 1403
    for (FocusNode node in _dirtyNodes) {
      node._notify();
    }
    _dirtyNodes.clear();
1404 1405 1406 1407 1408 1409
    assert(() {
      if (_kDebugFocus) {
        debugDumpFocusTree();
      }
      return true;
    }());
1410 1411
  }

1412 1413 1414 1415 1416 1417
  @override
  List<DiagnosticsNode> debugDescribeChildren() {
    return <DiagnosticsNode>[
      rootScope.toDiagnosticsNode(name: 'rootScope'),
    ];
  }
1418

1419
  @override
1420 1421
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    properties.add(FlagProperty('haveScheduledUpdate', value: _haveScheduledUpdate, ifTrue: 'UPDATE SCHEDULED'));
1422
    properties.add(DiagnosticsProperty<FocusNode>('primaryFocus', primaryFocus, defaultValue: null));
1423
    properties.add(DiagnosticsProperty<FocusNode>('nextFocus', _nextFocus, defaultValue: null));
1424 1425 1426 1427
    final Element element = primaryFocus?.context;
    if (element != null) {
      properties.add(DiagnosticsProperty<String>('primaryFocusCreator', element.debugGetCreatorChain(20)));
    }
1428 1429
  }
}
1430

1431 1432 1433 1434
/// Provides convenient access to the current [FocusManager.primaryFocus] from the
/// [WidgetsBinding] instance.
FocusNode get primaryFocus => WidgetsBinding.instance.focusManager.primaryFocus;

1435 1436 1437 1438 1439 1440 1441 1442
/// Returns a text representation of the current focus tree, along with the
/// current attributes on each node.
///
/// Will return an empty string in release builds.
String debugDescribeFocusTree() {
  assert(WidgetsBinding.instance != null);
  String result;
  assert(() {
1443
    result = FocusManager.instance.toStringDeep();
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458
    return true;
  }());
  return result ?? '';
}

/// Prints a text representation of the current focus tree, along with the
/// current attributes on each node.
///
/// Will do nothing in release builds.
void debugDumpFocusTree() {
  assert(() {
    debugPrint(debugDescribeFocusTree());
    return true;
  }());
}