• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/painting.dart';
7
8import 'basic.dart';
9import 'binding.dart';
10import 'focus_manager.dart';
11import 'framework.dart';
12
13/// A direction along either the horizontal or vertical axes.
14///
15/// This is used by the [DirectionalFocusTraversalPolicyMixin] to indicate which
16/// direction to traverse in.
17enum TraversalDirection {
18  /// Indicates a direction above the currently focused widget.
19  up,
20
21  /// Indicates a direction to the right of the currently focused widget.
22  ///
23  /// This direction is unaffected by the [Directionality] of the current
24  /// context.
25  right,
26
27  /// Indicates a direction below the currently focused widget.
28  down,
29
30  /// Indicates a direction to the left of the currently focused widget.
31  ///
32  /// This direction is unaffected by the [Directionality] of the current
33  /// context.
34  left,
35
36  // TODO(gspencer): Add diagonal traversal directions used by TV remotes and
37  // game controllers when we support them.
38}
39
40/// An object used to specify a focus traversal policy used for configuring a
41/// [DefaultFocusTraversal] widget.
42///
43/// The focus traversal policy is what determines which widget is "next",
44/// "previous", or in a direction from the currently focused [FocusNode].
45///
46/// One of the pre-defined subclasses may be used, or define a custom policy to
47/// create a unique focus order.
48///
49/// See also:
50///
51///   * [FocusNode], for a description of the focus system.
52///   * [DefaultFocusTraversal], a widget that imposes a traversal policy on the
53///     [Focus] nodes below it in the widget hierarchy.
54///   * [FocusNode], which is affected by the traversal policy.
55///   * [WidgetOrderFocusTraversalPolicy], a policy that relies on the widget
56///     creation order to describe the order of traversal.
57///   * [ReadingOrderTraversalPolicy], a policy that describes the order as the
58///     natural "reading order" for the current [Directionality].
59///   * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
60///     focus traversal in a direction.
61abstract class FocusTraversalPolicy {
62  /// Returns the node that should receive focus if there is no current focus
63  /// in the [FocusScopeNode] that [currentNode] belongs to.
64  ///
65  /// This is used by [next]/[previous]/[inDirection] to determine which node to
66  /// focus if they are called, but no node is currently focused.
67  ///
68  /// It is also used by the [FocusManager] to know which node to focus
69  /// initially if no nodes are focused.
70  ///
71  /// If the [direction] is null, then it should find the appropriate first node
72  /// for next/previous, and if direction is non-null, should find the
73  /// appropriate first node in that direction.
74  ///
75  /// The [currentNode] argument must not be null.
76  FocusNode findFirstFocus(FocusNode currentNode);
77
78  /// Returns the node in the given [direction] that should receive focus if
79  /// there is no current focus in the scope to which the [currentNode] belongs.
80  ///
81  /// This is typically used by [inDirection] to determine which node to focus
82  /// if it is called, but no node is currently focused.
83  ///
84  /// All arguments must not be null.
85  FocusNode findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction);
86
87  /// Clears the data associated with the given [FocusScopeNode] for this object.
88  ///
89  /// This is used to indicate that the focus policy has changed its mode, and
90  /// so any cached policy data should be invalidated. For example, changing the
91  /// direction in which focus is moving, or changing from directional to
92  /// next/previous navigation modes.
93  ///
94  /// The default implementation does nothing.
95  @mustCallSuper
96  @protected
97  void invalidateScopeData(FocusScopeNode node) {}
98
99  /// This is called whenever the given [node] is reparented into a new scope,
100  /// so that the policy has a chance to update or invalidate any cached data
101  /// that it maintains per scope about the node.
102  ///
103  /// The [oldScope] is the previous scope that this node belonged to, if any.
104  ///
105  /// The default implementation does nothing.
106  @mustCallSuper
107  void changedScope({FocusNode node, FocusScopeNode oldScope}) {}
108
109  /// Focuses the next widget in the focus scope that contains the given
110  /// [currentNode].
111  ///
112  /// This should determine what the next node to receive focus should be by
113  /// inspecting the node tree, and then calling [FocusNode.requestFocus] on
114  /// the node that has been selected.
115  ///
116  /// Returns true if it successfully found a node and requested focus.
117  ///
118  /// The [currentNode] argument must not be null.
119  bool next(FocusNode currentNode);
120
121  /// Focuses the previous widget in the focus scope that contains the given
122  /// [currentNode].
123  ///
124  /// This should determine what the previous node to receive focus should be by
125  /// inspecting the node tree, and then calling [FocusNode.requestFocus] on
126  /// the node that has been selected.
127  ///
128  /// Returns true if it successfully found a node and requested focus.
129  ///
130  /// The [currentNode] argument must not be null.
131  bool previous(FocusNode currentNode);
132
133  /// Focuses the next widget in the given [direction] in the focus scope that
134  /// contains the given [currentNode].
135  ///
136  /// This should determine what the next node to receive focus in the given
137  /// [direction] should be by inspecting the node tree, and then calling
138  /// [FocusNode.requestFocus] on the node that has been selected.
139  ///
140  /// Returns true if it successfully found a node and requested focus.
141  ///
142  /// All arguments must not be null.
143  bool inDirection(FocusNode currentNode, TraversalDirection direction);
144}
145
146/// A policy data object for use by the [DirectionalFocusTraversalPolicyMixin]
147class _DirectionalPolicyDataEntry {
148  const _DirectionalPolicyDataEntry({@required this.direction, @required this.node})
149      : assert(direction != null),
150        assert(node != null);
151
152  final TraversalDirection direction;
153  final FocusNode node;
154}
155
156class _DirectionalPolicyData {
157  const _DirectionalPolicyData({@required this.history}) : assert(history != null);
158
159  /// A queue of entries that describe the path taken to the current node.
160  final List<_DirectionalPolicyDataEntry> history;
161}
162
163/// A mixin class that provides an implementation for finding a node in a
164/// particular direction.
165///
166/// This can be mixed in to other [FocusTraversalPolicy] implementations that
167/// only want to implement new next/previous policies.
168///
169/// Since hysteresis in the navigation order is undesirable, this implementation
170/// maintains a stack of previous locations that have been visited on the
171/// [policyData] for the affected [FocusScopeNode]. If the previous direction
172/// was the opposite of the current direction, then the this policy will request
173/// focus on the previously focused node. Change to another direction other than
174/// the current one or its opposite will clear the stack.
175///
176/// For instance, if the focus moves down, down, down, and then up, up, up, it
177/// will follow the same path through the widgets in both directions. However,
178/// if it moves down, down, down, left, right, and then up, up, up, it may not
179/// follow the same path on the way up as it did on the way down.
180///
181/// See also:
182///
183///   * [FocusNode], for a description of the focus system.
184///   * [DefaultFocusTraversal], a widget that imposes a traversal policy on the
185///     [Focus] nodes below it in the widget hierarchy.
186///   * [WidgetOrderFocusTraversalPolicy], a policy that relies on the widget
187///     creation order to describe the order of traversal.
188///   * [ReadingOrderTraversalPolicy], a policy that describes the order as the
189///     natural "reading order" for the current [Directionality].
190mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy {
191  final Map<FocusScopeNode, _DirectionalPolicyData> _policyData = <FocusScopeNode, _DirectionalPolicyData>{};
192
193  @override
194  void invalidateScopeData(FocusScopeNode node) {
195    super.invalidateScopeData(node);
196    _policyData.remove(node);
197  }
198
199  @override
200  void changedScope({FocusNode node, FocusScopeNode oldScope}) {
201    super.changedScope(node: node, oldScope: oldScope);
202    if (oldScope != null) {
203      _policyData[oldScope]?.history?.removeWhere((_DirectionalPolicyDataEntry entry) {
204        return entry.node == node;
205      });
206    }
207  }
208
209  @override
210  FocusNode findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) {
211    assert(direction != null);
212    assert(currentNode != null);
213    switch (direction) {
214      case TraversalDirection.up:
215        // Find the bottom-most node so we can go up from there.
216        return _sortAndFindInitial(currentNode, vertical: true, first: false);
217      case TraversalDirection.down:
218        // Find the top-most node so we can go down from there.
219        return _sortAndFindInitial(currentNode, vertical: true, first: true);
220      case TraversalDirection.left:
221        // Find the right-most node so we can go left from there.
222        return _sortAndFindInitial(currentNode, vertical: false, first: false);
223      case TraversalDirection.right:
224        // Find the left-most node so we can go right from there.
225        return _sortAndFindInitial(currentNode, vertical: false, first: true);
226    }
227    return null;
228  }
229
230  FocusNode _sortAndFindInitial(FocusNode currentNode, { bool vertical, bool first }) {
231    final Iterable<FocusNode> nodes = currentNode.nearestScope.traversalDescendants;
232    final List<FocusNode> sorted = nodes.toList();
233    sorted.sort((FocusNode a, FocusNode b) {
234      if (vertical) {
235        if (first) {
236          return a.rect.top.compareTo(b.rect.top);
237        } else {
238          return b.rect.bottom.compareTo(a.rect.bottom);
239        }
240      } else {
241        if (first) {
242          return a.rect.left.compareTo(b.rect.left);
243        } else {
244          return b.rect.right.compareTo(a.rect.right);
245        }
246      }
247    });
248    return sorted.first;
249  }
250
251  // Sorts nodes from left to right horizontally, and removes nodes that are
252  // either to the right of the left side of the target node if we're going
253  // left, or to the left of the right side of the target node if we're going
254  // right.
255  //
256  // This doesn't need to take into account directionality because it is
257  // typically intending to actually go left or right, not in a reading
258  // direction.
259  Iterable<FocusNode> _sortAndFilterHorizontally(
260    TraversalDirection direction,
261    Rect target,
262    FocusNode nearestScope,
263  ) {
264    assert(direction == TraversalDirection.left || direction == TraversalDirection.right);
265    final Iterable<FocusNode> nodes = nearestScope.traversalDescendants;
266    assert(!nodes.contains(nearestScope));
267    final List<FocusNode> sorted = nodes.toList();
268    sorted.sort((FocusNode a, FocusNode b) => a.rect.center.dx.compareTo(b.rect.center.dx));
269    Iterable<FocusNode> result;
270    switch (direction) {
271      case TraversalDirection.left:
272        result = sorted.where((FocusNode node) => node.rect != target && node.rect.center.dx <= target.left);
273        break;
274      case TraversalDirection.right:
275        result = sorted.where((FocusNode node) => node.rect != target && node.rect.center.dx >= target.right);
276        break;
277      case TraversalDirection.up:
278      case TraversalDirection.down:
279        break;
280    }
281    return result;
282  }
283
284  // Sorts nodes from top to bottom vertically, and removes nodes that are
285  // either below the top of the target node if we're going up, or above the
286  // bottom of the target node if we're going down.
287  Iterable<FocusNode> _sortAndFilterVertically(
288    TraversalDirection direction,
289    Rect target,
290    Iterable<FocusNode> nodes,
291  ) {
292    final List<FocusNode> sorted = nodes.toList();
293    sorted.sort((FocusNode a, FocusNode b) => a.rect.center.dy.compareTo(b.rect.center.dy));
294    switch (direction) {
295      case TraversalDirection.up:
296        return sorted.where((FocusNode node) => node.rect != target && node.rect.center.dy <= target.top);
297      case TraversalDirection.down:
298        return sorted.where((FocusNode node) => node.rect != target && node.rect.center.dy >= target.bottom);
299      case TraversalDirection.left:
300      case TraversalDirection.right:
301        break;
302    }
303    assert(direction == TraversalDirection.up || direction == TraversalDirection.down);
304    return null;
305  }
306
307  // Updates the policy data to keep the previously visited node so that we can
308  // avoid hysteresis when we change directions in navigation.
309  //
310  // Returns true if focus was requested on a previous node.
311  bool _popPolicyDataIfNeeded(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
312    final _DirectionalPolicyData policyData = _policyData[nearestScope];
313    if (policyData != null && policyData.history.isNotEmpty && policyData.history.first.direction != direction) {
314      if (policyData.history.last.node.parent == null) {
315        // If a node has been removed from the tree, then we should stop
316        // referencing it and reset the scope data so that we don't try and
317        // request focus on it. This can happen in slivers where the rendered node
318        // has been unmounted. This has the side effect that hysteresis might not
319        // be avoided when items that go off screen get unmounted.
320        invalidateScopeData(nearestScope);
321        return false;
322      }
323      switch (direction) {
324        case TraversalDirection.down:
325        case TraversalDirection.up:
326          switch (policyData.history.first.direction) {
327            case TraversalDirection.left:
328            case TraversalDirection.right:
329              // Reset the policy data if we change directions.
330              invalidateScopeData(nearestScope);
331              break;
332            case TraversalDirection.up:
333            case TraversalDirection.down:
334              policyData.history.removeLast().node.requestFocus();
335              return true;
336          }
337          break;
338        case TraversalDirection.left:
339        case TraversalDirection.right:
340          switch (policyData.history.first.direction) {
341            case TraversalDirection.left:
342            case TraversalDirection.right:
343              policyData.history.removeLast().node.requestFocus();
344              return true;
345            case TraversalDirection.up:
346            case TraversalDirection.down:
347              // Reset the policy data if we change directions.
348              invalidateScopeData(nearestScope);
349              break;
350          }
351      }
352    }
353    if (policyData != null && policyData.history.isEmpty) {
354      // Reset the policy data if we change directions.
355      invalidateScopeData(nearestScope);
356    }
357    return false;
358  }
359
360  void _pushPolicyData(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
361    final _DirectionalPolicyData policyData = _policyData[nearestScope];
362    if (policyData != null && policyData is! _DirectionalPolicyData) {
363      return;
364    }
365    final _DirectionalPolicyDataEntry newEntry = _DirectionalPolicyDataEntry(node: focusedChild, direction: direction);
366    if (policyData != null) {
367      policyData.history.add(newEntry);
368    } else {
369      _policyData[nearestScope] = _DirectionalPolicyData(history: <_DirectionalPolicyDataEntry>[newEntry]);
370    }
371  }
372
373  /// Focuses the next widget in the given [direction] in the [FocusScope] that
374  /// contains the [currentNode].
375  ///
376  /// This determines what the next node to receive focus in the given
377  /// [direction] will be by inspecting the node tree, and then calling
378  /// [FocusNode.requestFocus] on it.
379  ///
380  /// Returns true if it successfully found a node and requested focus.
381  ///
382  /// Maintains a stack of previous locations that have been visited on the
383  /// [policyData] for the affected [FocusScopeNode]. If the previous direction
384  /// was the opposite of the current direction, then the this policy will
385  /// request focus on the previously focused node. Change to another direction
386  /// other than the current one or its opposite will clear the stack.
387  ///
388  /// If this function returns true when called by a subclass, then the subclass
389  /// should return true and not request focus from any node.
390  @mustCallSuper
391  @override
392  bool inDirection(FocusNode currentNode, TraversalDirection direction) {
393    final FocusScopeNode nearestScope = currentNode.nearestScope;
394    final FocusNode focusedChild = nearestScope.focusedChild;
395    if (focusedChild == null) {
396      final FocusNode firstFocus = findFirstFocusInDirection(currentNode, direction);
397      (firstFocus ?? currentNode).requestFocus();
398      return true;
399    }
400    if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild)) {
401      return true;
402    }
403    FocusNode found;
404    switch (direction) {
405      case TraversalDirection.down:
406      case TraversalDirection.up:
407        final Iterable<FocusNode> eligibleNodes = _sortAndFilterVertically(
408          direction,
409          focusedChild.rect,
410          nearestScope.traversalDescendants,
411        );
412        if (eligibleNodes.isEmpty) {
413          break;
414        }
415        List<FocusNode> sorted = eligibleNodes.toList();
416        if (direction == TraversalDirection.up) {
417          sorted = sorted.reversed.toList();
418        }
419        // Find any nodes that intersect the band of the focused child.
420        final Rect band = Rect.fromLTRB(focusedChild.rect.left, -double.infinity, focusedChild.rect.right, double.infinity);
421        final Iterable<FocusNode> inBand = sorted.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
422        if (inBand.isNotEmpty) {
423          // The inBand list is already sorted by horizontal distance, so pick the closest one.
424          found = inBand.first;
425          break;
426        }
427        // Only out-of-band targets remain, so pick the one that is closest the to the center line horizontally.
428        sorted.sort((FocusNode a, FocusNode b) {
429          return (a.rect.center.dx - focusedChild.rect.center.dx).abs().compareTo((b.rect.center.dx - focusedChild.rect.center.dx).abs());
430        });
431        found = sorted.first;
432        break;
433      case TraversalDirection.right:
434      case TraversalDirection.left:
435        final Iterable<FocusNode> eligibleNodes = _sortAndFilterHorizontally(direction, focusedChild.rect, nearestScope);
436        if (eligibleNodes.isEmpty) {
437          break;
438        }
439        List<FocusNode> sorted = eligibleNodes.toList();
440        if (direction == TraversalDirection.left) {
441          sorted = sorted.reversed.toList();
442        }
443        // Find any nodes that intersect the band of the focused child.
444        final Rect band = Rect.fromLTRB(-double.infinity, focusedChild.rect.top, double.infinity, focusedChild.rect.bottom);
445        final Iterable<FocusNode> inBand = sorted.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
446        if (inBand.isNotEmpty) {
447          // The inBand list is already sorted by vertical distance, so pick the closest one.
448          found = inBand.first;
449          break;
450        }
451        // Only out-of-band targets remain, so pick the one that is closest the to the center line vertically.
452        sorted.sort((FocusNode a, FocusNode b) {
453          return (a.rect.center.dy - focusedChild.rect.center.dy).abs().compareTo((b.rect.center.dy - focusedChild.rect.center.dy).abs());
454        });
455        found = sorted.first;
456        break;
457    }
458    if (found != null) {
459      _pushPolicyData(direction, nearestScope, focusedChild);
460      found.requestFocus();
461      return true;
462    }
463    return false;
464  }
465}
466
467/// A [FocusTraversalPolicy] that traverses the focus order in widget hierarchy
468/// order.
469///
470/// This policy is used when the order desired is the order in which widgets are
471/// created in the widget hierarchy.
472///
473/// See also:
474///
475///   * [FocusNode], for a description of the focus system.
476///   * [DefaultFocusTraversal], a widget that imposes a traversal policy on the
477///     [Focus] nodes below it in the widget hierarchy.
478///   * [ReadingOrderTraversalPolicy], a policy that describes the order as the
479///     natural "reading order" for the current [Directionality].
480///   * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
481///     focus traversal in a direction.
482class WidgetOrderFocusTraversalPolicy extends FocusTraversalPolicy with DirectionalFocusTraversalPolicyMixin {
483  /// Creates a const [WidgetOrderFocusTraversalPolicy].
484  WidgetOrderFocusTraversalPolicy();
485
486  @override
487  FocusNode findFirstFocus(FocusNode currentNode) {
488    assert(currentNode != null);
489    final FocusScopeNode scope = currentNode.nearestScope;
490    // Start with the candidate focus as the focused child of this scope, if
491    // there is one. Otherwise start with this node itself. Keep going down
492    // through scopes until an ultimately focusable item is found, a scope
493    // doesn't have a focusedChild, or a non-scope is encountered.
494    FocusNode candidate = scope.focusedChild;
495    if (candidate == null) {
496      if (scope.traversalChildren.isNotEmpty) {
497        candidate = scope.traversalChildren.first;
498      } else {
499        candidate = currentNode;
500      }
501    }
502    while (candidate is FocusScopeNode && candidate.focusedChild != null) {
503      final FocusScopeNode candidateScope = candidate;
504      candidate = candidateScope.focusedChild;
505    }
506    return candidate;
507  }
508
509  // Moves the focus to the next or previous node, depending on whether forward
510  // is true or not.
511  bool _move(FocusNode currentNode, {@required bool forward}) {
512    if (currentNode == null) {
513      return false;
514    }
515    final FocusScopeNode nearestScope = currentNode.nearestScope;
516    invalidateScopeData(nearestScope);
517    final FocusNode focusedChild = nearestScope.focusedChild;
518    if (focusedChild == null) {
519      final FocusNode firstFocus = findFirstFocus(currentNode);
520      if (firstFocus != null) {
521        firstFocus.requestFocus();
522        return true;
523      }
524    }
525    FocusNode previousNode;
526    FocusNode firstNode;
527    FocusNode lastNode;
528    bool visit(FocusNode node) {
529      for (FocusNode visited in node.traversalChildren) {
530        firstNode ??= visited;
531        if (!visit(visited)) {
532          return false;
533        }
534        if (forward) {
535          if (previousNode == focusedChild) {
536            visited.requestFocus();
537            return false; // short circuit the traversal.
538          }
539        } else {
540          if (previousNode != null && visited == focusedChild) {
541            previousNode.requestFocus();
542            return false; // short circuit the traversal.
543          }
544        }
545        previousNode = visited;
546        lastNode = visited;
547      }
548      return true; // continue traversal
549    }
550
551    if (visit(nearestScope)) {
552      if (forward) {
553        if (firstNode != null) {
554          firstNode.requestFocus();
555          return true;
556        }
557      } else {
558        if (lastNode != null) {
559          lastNode.requestFocus();
560          return true;
561        }
562      }
563      return false;
564    }
565    return true;
566  }
567
568  @override
569  bool next(FocusNode currentNode) => _move(currentNode, forward: true);
570
571  @override
572  bool previous(FocusNode currentNode) => _move(currentNode, forward: false);
573}
574
575class _SortData {
576  _SortData(this.node) : rect = node.rect;
577
578  final Rect rect;
579  final FocusNode node;
580}
581
582/// Traverses the focus order in "reading order".
583///
584/// By default, reading order traversal goes in the reading direction, and then
585/// down, using this algorithm:
586///
587/// 1. Find the node rectangle that has the highest `top` on the screen.
588/// 2. Find any other nodes that intersect the infinite horizontal band defined
589///    by the highest rectangle's top and bottom edges.
590/// 3. Pick the closest to the beginning of the reading order from among the
591///    nodes discovered above.
592///
593/// It uses the ambient directionality in the context for the enclosing scope to
594/// determine which direction is "reading order".
595///
596/// See also:
597///
598///   * [FocusNode], for a description of the focus system.
599///   * [DefaultFocusTraversal], a widget that imposes a traversal policy on the
600///     [Focus] nodes below it in the widget hierarchy.
601///   * [WidgetOrderFocusTraversalPolicy], a policy that relies on the widget
602///     creation order to describe the order of traversal.
603///   * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
604///     focus traversal in a direction.
605class ReadingOrderTraversalPolicy extends FocusTraversalPolicy with DirectionalFocusTraversalPolicyMixin {
606  @override
607  FocusNode findFirstFocus(FocusNode currentNode) {
608    assert(currentNode != null);
609    final FocusScopeNode scope = currentNode.nearestScope;
610    FocusNode candidate = scope.focusedChild;
611    if (candidate == null && scope.traversalChildren.isNotEmpty) {
612      candidate = _sortByGeometry(scope).first;
613    }
614
615    // If we still didn't find any candidate, use the current node as a
616    // fallback.
617    candidate ??= currentNode;
618    candidate ??= WidgetsBinding.instance.focusManager.rootScope;
619    return candidate;
620  }
621
622  // Sorts the list of nodes based on their geometry into the desired reading
623  // order based on the directionality of the context for each node.
624  Iterable<FocusNode> _sortByGeometry(FocusScopeNode scope) {
625    final Iterable<FocusNode> nodes = scope.traversalDescendants;
626    if (nodes.length <= 1) {
627      return nodes;
628    }
629
630    Iterable<_SortData> inBand(_SortData current, Iterable<_SortData> candidates) {
631      final Rect wide = Rect.fromLTRB(double.negativeInfinity, current.rect.top, double.infinity, current.rect.bottom);
632      return candidates.where((_SortData item) {
633        return !item.rect.intersect(wide).isEmpty;
634      });
635    }
636
637    final TextDirection textDirection = scope.context == null ? TextDirection.ltr : Directionality.of(scope.context);
638    _SortData pickFirst(List<_SortData> candidates) {
639      int compareBeginningSide(_SortData a, _SortData b) {
640        return textDirection == TextDirection.ltr ? a.rect.left.compareTo(b.rect.left) : -a.rect.right.compareTo(b.rect.right);
641      }
642
643      int compareTopSide(_SortData a, _SortData b) {
644        return a.rect.top.compareTo(b.rect.top);
645      }
646
647      // Get the topmost
648      candidates.sort(compareTopSide);
649      final _SortData topmost = candidates.first;
650      // If there are any others in the band of the topmost, then pick the
651      // leftmost one.
652      final List<_SortData> inBandOfTop = inBand(topmost, candidates).toList();
653      inBandOfTop.sort(compareBeginningSide);
654      if (inBandOfTop.isNotEmpty) {
655        return inBandOfTop.first;
656      }
657      return topmost;
658    }
659
660    final List<_SortData> data = <_SortData>[];
661    for (FocusNode node in nodes) {
662      data.add(_SortData(node));
663    }
664
665    // Pick the initial widget as the one that is leftmost in the band of the
666    // topmost, or the topmost, if there are no others in its band.
667    final List<_SortData> sortedList = <_SortData>[];
668    final List<_SortData> unplaced = data.toList();
669    _SortData current = pickFirst(unplaced);
670    sortedList.add(current);
671    unplaced.remove(current);
672
673    while (unplaced.isNotEmpty) {
674      final _SortData next = pickFirst(unplaced);
675      current = next;
676      sortedList.add(current);
677      unplaced.remove(current);
678    }
679    return sortedList.map((_SortData item) => item.node);
680  }
681
682  // Moves the focus forward or backward in reading order, depending on the
683  // value of the forward argument.
684  bool _move(FocusNode currentNode, {@required bool forward}) {
685    final FocusScopeNode nearestScope = currentNode.nearestScope;
686    invalidateScopeData(nearestScope);
687    final FocusNode focusedChild = nearestScope.focusedChild;
688    if (focusedChild == null) {
689      final FocusNode firstFocus = findFirstFocus(currentNode);
690      if (firstFocus != null) {
691        firstFocus.requestFocus();
692        return true;
693      }
694    }
695    final List<FocusNode> sortedNodes = _sortByGeometry(nearestScope).toList();
696    if (forward && focusedChild == sortedNodes.last) {
697      sortedNodes.first.requestFocus();
698      return true;
699    }
700    if (!forward && focusedChild == sortedNodes.first) {
701      sortedNodes.last.requestFocus();
702      return true;
703    }
704
705    final Iterable<FocusNode> maybeFlipped = forward ? sortedNodes : sortedNodes.reversed;
706    FocusNode previousNode;
707    for (FocusNode node in maybeFlipped) {
708      if (previousNode == focusedChild) {
709        node.requestFocus();
710        return true;
711      }
712      previousNode = node;
713    }
714    return false;
715  }
716
717  @override
718  bool next(FocusNode currentNode) => _move(currentNode, forward: true);
719
720  @override
721  bool previous(FocusNode currentNode) => _move(currentNode, forward: false);
722}
723
724/// A widget that describes the inherited focus policy for focus traversal.
725///
726/// By default, traverses in widget order using
727/// [ReadingOrderFocusTraversalPolicy].
728///
729/// See also:
730///
731///   * [FocusNode], for a description of the focus system.
732///   * [WidgetOrderFocusTraversalPolicy], a policy that relies on the widget
733///     creation order to describe the order of traversal.
734///   * [ReadingOrderTraversalPolicy], a policy that describes the order as the
735///     natural "reading order" for the current [Directionality].
736///   * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
737///     focus traversal in a direction.
738class DefaultFocusTraversal extends InheritedWidget {
739  /// Creates a [DefaultFocusTraversal] object.
740  ///
741  /// The [child] argument must not be null.
742  const DefaultFocusTraversal({
743    Key key,
744    this.policy,
745    @required Widget child,
746  }) : super(key: key, child: child);
747
748  /// The policy used to move the focus from one focus node to another.
749  ///
750  /// If not specified, traverses in reading order using
751  /// [ReadingOrderTraversalPolicy].
752  ///
753  /// See also:
754  ///
755  ///  * [FocusTraversalPolicy] for the API used to impose traversal order
756  ///    policy.
757  ///  * [WidgetOrderFocusTraversalPolicy] for a traversal policy that traverses
758  ///    nodes in the order they are added to the widget tree.
759  ///  * [ReadingOrderTraversalPolicy] for a traversal policy that traverses
760  ///    nodes in the reading order defined in the widget tree, and then top to
761  ///    bottom.
762  final FocusTraversalPolicy policy;
763
764  /// Returns the [FocusTraversalPolicy] that most tightly encloses the given
765  /// [BuildContext].
766  ///
767  /// The [context] argument must not be null.
768  static FocusTraversalPolicy of(BuildContext context, { bool nullOk = false }) {
769    assert(context != null);
770    final DefaultFocusTraversal inherited = context.inheritFromWidgetOfExactType(DefaultFocusTraversal);
771    assert(() {
772      if (nullOk) {
773        return true;
774      }
775      if (inherited == null) {
776        throw FlutterError('Unable to find a DefaultFocusTraversal widget in the context.\n'
777            'DefaultFocusTraversal.of() was called with a context that does not contain a '
778            'DefaultFocusTraversal.\n'
779            'No DefaultFocusTraversal ancestor could be found starting from the context that was '
780            'passed to DefaultFocusTraversal.of(). This can happen because there is not a '
781            'WidgetsApp or MaterialApp widget (those widgets introduce a DefaultFocusTraversal), '
782            'or it can happen if the context comes from a widget above those widgets.\n'
783            'The context used was:\n'
784            '  $context');
785      }
786      return true;
787    }());
788    return inherited?.policy ?? ReadingOrderTraversalPolicy();
789  }
790
791  @override
792  bool updateShouldNotify(DefaultFocusTraversal oldWidget) => policy != oldWidget.policy;
793}
794