@@ -82,9 +82,6 @@ enum TraversalDirection {
82
82
/// This direction is unaffected by the [Directionality] of the current
83
83
/// context.
84
84
left,
85
-
86
- // TODO(gspencer): Add diagonal traversal directions used by TV remotes and
87
- // game controllers when we support them.
88
85
}
89
86
90
87
/// An object used to specify a focus traversal policy used for configuring a
@@ -547,6 +544,46 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy {
547
544
return null ;
548
545
}
549
546
547
+ static int _verticalCompare (Offset target, Offset a, Offset b) {
548
+ return (a.dy - target.dy).abs ().compareTo ((b.dy - target.dy).abs ());
549
+ }
550
+
551
+ static int _horizontalCompare (Offset target, Offset a, Offset b) {
552
+ return (a.dx - target.dx).abs ().compareTo ((b.dx - target.dx).abs ());
553
+ }
554
+
555
+ // Sort the ones that are closest to target vertically first, and if two are
556
+ // the same vertical distance, pick the one that is closest horizontally.
557
+ static Iterable <FocusNode > _sortByDistancePreferVertical (Offset target, Iterable <FocusNode > nodes) {
558
+ final List <FocusNode > sorted = nodes.toList ();
559
+ mergeSort <FocusNode >(sorted, compare: (FocusNode nodeA, FocusNode nodeB) {
560
+ final Offset a = nodeA.rect.center;
561
+ final Offset b = nodeB.rect.center;
562
+ final int vertical = _verticalCompare (target, a, b);
563
+ if (vertical == 0 ) {
564
+ return _horizontalCompare (target, a, b);
565
+ }
566
+ return vertical;
567
+ });
568
+ return sorted;
569
+ }
570
+
571
+ // Sort the ones that are closest horizontally first, and if two are the same
572
+ // horizontal distance, pick the one that is closest vertically.
573
+ static Iterable <FocusNode > _sortByDistancePreferHorizontal (Offset target, Iterable <FocusNode > nodes) {
574
+ final List <FocusNode > sorted = nodes.toList ();
575
+ mergeSort <FocusNode >(sorted, compare: (FocusNode nodeA, FocusNode nodeB) {
576
+ final Offset a = nodeA.rect.center;
577
+ final Offset b = nodeB.rect.center;
578
+ final int horizontal = _horizontalCompare (target, a, b);
579
+ if (horizontal == 0 ) {
580
+ return _verticalCompare (target, a, b);
581
+ }
582
+ return horizontal;
583
+ });
584
+ return sorted;
585
+ }
586
+
550
587
// Sorts nodes from left to right horizontally, and removes nodes that are
551
588
// either to the right of the left side of the target node if we're going
552
589
// left, or to the left of the right side of the target node if we're going
@@ -555,52 +592,54 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy {
555
592
// This doesn't need to take into account directionality because it is
556
593
// typically intending to actually go left or right, not in a reading
557
594
// direction.
558
- Iterable <FocusNode >? _sortAndFilterHorizontally (
595
+ Iterable <FocusNode > _sortAndFilterHorizontally (
559
596
TraversalDirection direction,
560
597
Rect target,
561
- FocusNode nearestScope ,
598
+ Iterable < FocusNode > nodes ,
562
599
) {
563
600
assert (direction == TraversalDirection .left || direction == TraversalDirection .right);
564
- final Iterable <FocusNode > nodes = nearestScope.traversalDescendants;
565
- assert (! nodes.contains (nearestScope));
566
- final List <FocusNode > sorted = nodes.toList ();
567
- mergeSort <FocusNode >(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dx.compareTo (b.rect.center.dx));
568
- Iterable <FocusNode >? result;
601
+ final Iterable <FocusNode > filtered;
569
602
switch (direction) {
570
603
case TraversalDirection .left:
571
- result = sorted .where ((FocusNode node) => node.rect != target && node.rect.center.dx <= target.left);
604
+ filtered = nodes .where ((FocusNode node) => node.rect != target && node.rect.center.dx <= target.left);
572
605
break ;
573
606
case TraversalDirection .right:
574
- result = sorted .where ((FocusNode node) => node.rect != target && node.rect.center.dx >= target.right);
607
+ filtered = nodes .where ((FocusNode node) => node.rect != target && node.rect.center.dx >= target.right);
575
608
break ;
576
609
case TraversalDirection .up:
577
610
case TraversalDirection .down:
578
- break ;
611
+ throw ArgumentError ( 'Invalid direction $ direction ' ) ;
579
612
}
580
- return result;
613
+ final List <FocusNode > sorted = filtered.toList ();
614
+ // Sort all nodes from left to right.
615
+ mergeSort <FocusNode >(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dx.compareTo (b.rect.center.dx));
616
+ return sorted;
581
617
}
582
618
583
619
// Sorts nodes from top to bottom vertically, and removes nodes that are
584
620
// either below the top of the target node if we're going up, or above the
585
621
// bottom of the target node if we're going down.
586
- Iterable <FocusNode >? _sortAndFilterVertically (
622
+ Iterable <FocusNode > _sortAndFilterVertically (
587
623
TraversalDirection direction,
588
624
Rect target,
589
625
Iterable <FocusNode > nodes,
590
626
) {
591
- final List < FocusNode > sorted = nodes. toList ( );
592
- mergeSort <FocusNode >(sorted, compare : ( FocusNode a, FocusNode b) => a.rect.center.dy. compareTo (b.rect.center.dy)) ;
627
+ assert (direction == TraversalDirection .up || direction == TraversalDirection .down );
628
+ final Iterable <FocusNode > filtered ;
593
629
switch (direction) {
594
630
case TraversalDirection .up:
595
- return sorted.where ((FocusNode node) => node.rect != target && node.rect.center.dy <= target.top);
631
+ filtered = nodes.where ((FocusNode node) => node.rect != target && node.rect.center.dy <= target.top);
632
+ break ;
596
633
case TraversalDirection .down:
597
- return sorted.where ((FocusNode node) => node.rect != target && node.rect.center.dy >= target.bottom);
634
+ filtered = nodes.where ((FocusNode node) => node.rect != target && node.rect.center.dy >= target.bottom);
635
+ break ;
598
636
case TraversalDirection .left:
599
637
case TraversalDirection .right:
600
- break ;
638
+ throw ArgumentError ( 'Invalid direction $ direction ' ) ;
601
639
}
602
- assert (direction == TraversalDirection .up || direction == TraversalDirection .down);
603
- return null ;
640
+ final List <FocusNode > sorted = filtered.toList ();
641
+ mergeSort <FocusNode >(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dy.compareTo (b.rect.center.dy));
642
+ return sorted;
604
643
}
605
644
606
645
// Updates the policy data to keep the previously visited node so that we can
@@ -745,71 +784,55 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy {
745
784
switch (direction) {
746
785
case TraversalDirection .down:
747
786
case TraversalDirection .up:
748
- Iterable <FocusNode >? eligibleNodes = _sortAndFilterVertically (
749
- direction,
750
- focusedChild.rect,
751
- nearestScope.traversalDescendants,
752
- );
787
+ Iterable <FocusNode > eligibleNodes = _sortAndFilterVertically (direction, focusedChild.rect, nearestScope.traversalDescendants);
788
+ if (eligibleNodes.isEmpty) {
789
+ break ;
790
+ }
753
791
if (focusedScrollable != null && ! focusedScrollable.position.atEdge) {
754
- final Iterable <FocusNode > filteredEligibleNodes = eligibleNodes! .where ((FocusNode node) => Scrollable .maybeOf (node.context! ) == focusedScrollable);
792
+ final Iterable <FocusNode > filteredEligibleNodes = eligibleNodes.where ((FocusNode node) => Scrollable .maybeOf (node.context! ) == focusedScrollable);
755
793
if (filteredEligibleNodes.isNotEmpty) {
756
794
eligibleNodes = filteredEligibleNodes;
757
795
}
758
796
}
759
- if (eligibleNodes! .isEmpty) {
760
- break ;
761
- }
762
- List <FocusNode > sorted = eligibleNodes.toList ();
763
797
if (direction == TraversalDirection .up) {
764
- sorted = sorted.reversed. toList ();
798
+ eligibleNodes = eligibleNodes. toList ().reversed ;
765
799
}
766
800
// Find any nodes that intersect the band of the focused child.
767
801
final Rect band = Rect .fromLTRB (focusedChild.rect.left, - double .infinity, focusedChild.rect.right, double .infinity);
768
- final Iterable <FocusNode > inBand = sorted .where ((FocusNode node) => ! node.rect.intersect (band).isEmpty);
802
+ final Iterable <FocusNode > inBand = eligibleNodes .where ((FocusNode node) => ! node.rect.intersect (band).isEmpty);
769
803
if (inBand.isNotEmpty) {
770
- // The inBand list is already sorted by horizontal distance, so pick
771
- // the closest one.
772
- found = inBand.first;
804
+ found = _sortByDistancePreferVertical (focusedChild.rect.center, inBand).first;
773
805
break ;
774
806
}
775
- // Only out-of-band targets remain, so pick the one that is closest the
776
- // to the center line horizontally.
777
- mergeSort <FocusNode >(sorted, compare: (FocusNode a, FocusNode b) {
778
- return (a.rect.center.dx - focusedChild.rect.center.dx).abs ().compareTo ((b.rect.center.dx - focusedChild.rect.center.dx).abs ());
779
- });
780
- found = sorted.first;
807
+ // Only out-of-band targets are eligible, so pick the one that is
808
+ // closest the to the center line horizontally.
809
+ found = _sortByDistancePreferHorizontal (focusedChild.rect.center, eligibleNodes).first;
781
810
break ;
782
811
case TraversalDirection .right:
783
812
case TraversalDirection .left:
784
- Iterable <FocusNode >? eligibleNodes = _sortAndFilterHorizontally (direction, focusedChild.rect, nearestScope);
813
+ Iterable <FocusNode > eligibleNodes = _sortAndFilterHorizontally (direction, focusedChild.rect, nearestScope.traversalDescendants);
814
+ if (eligibleNodes.isEmpty) {
815
+ break ;
816
+ }
785
817
if (focusedScrollable != null && ! focusedScrollable.position.atEdge) {
786
- final Iterable <FocusNode > filteredEligibleNodes = eligibleNodes! .where ((FocusNode node) => Scrollable .maybeOf (node.context! ) == focusedScrollable);
818
+ final Iterable <FocusNode > filteredEligibleNodes = eligibleNodes.where ((FocusNode node) => Scrollable .maybeOf (node.context! ) == focusedScrollable);
787
819
if (filteredEligibleNodes.isNotEmpty) {
788
820
eligibleNodes = filteredEligibleNodes;
789
821
}
790
822
}
791
- if (eligibleNodes! .isEmpty) {
792
- break ;
793
- }
794
- List <FocusNode > sorted = eligibleNodes.toList ();
795
823
if (direction == TraversalDirection .left) {
796
- sorted = sorted.reversed. toList ();
824
+ eligibleNodes = eligibleNodes. toList ().reversed ;
797
825
}
798
826
// Find any nodes that intersect the band of the focused child.
799
827
final Rect band = Rect .fromLTRB (- double .infinity, focusedChild.rect.top, double .infinity, focusedChild.rect.bottom);
800
- final Iterable <FocusNode > inBand = sorted .where ((FocusNode node) => ! node.rect.intersect (band).isEmpty);
828
+ final Iterable <FocusNode > inBand = eligibleNodes .where ((FocusNode node) => ! node.rect.intersect (band).isEmpty);
801
829
if (inBand.isNotEmpty) {
802
- // The inBand list is already sorted by vertical distance, so pick the
803
- // closest one.
804
- found = inBand.first;
830
+ found = _sortByDistancePreferHorizontal (focusedChild.rect.center, inBand).first;
805
831
break ;
806
832
}
807
- // Only out-of-band targets remain , so pick the one that is closest the
833
+ // Only out-of-band targets are eligible , so pick the one that is
808
834
// to the center line vertically.
809
- mergeSort <FocusNode >(sorted, compare: (FocusNode a, FocusNode b) {
810
- return (a.rect.center.dy - focusedChild.rect.center.dy).abs ().compareTo ((b.rect.center.dy - focusedChild.rect.center.dy).abs ());
811
- });
812
- found = sorted.first;
835
+ found = _sortByDistancePreferVertical (focusedChild.rect.center, eligibleNodes).first;
813
836
break ;
814
837
}
815
838
if (found != null ) {
@@ -892,8 +915,8 @@ class _ReadingOrderSortData with Diagnosticable {
892
915
}
893
916
if (common! .isEmpty) {
894
917
// If there is no common ancestor, then arbitrarily pick the
895
- // directionality of the first group, which is the equivalent of the "first
896
- // strongly typed" item in a bidi algorithm.
918
+ // directionality of the first group, which is the equivalent of the
919
+ // "first strongly typed" item in a bidirectional algorithm.
897
920
return list.first.directionality;
898
921
}
899
922
// Find the closest common ancestor. The memberAncestors list contains the
0 commit comments