Skip to content

Commit 7be0bd1

Browse files
authored
Move geometric sort for semantics to the framework side. (flutter#14539)
This adds geometric sort ordering back in for semantics nodes that don't have a sort order defined. With this change, widgets that either have no sort order, or have an equivalent sort order, will be compared geometrically. The comparison is between the two starting corners, so it is TextDirection-aware: parent nodes that are set to have LTR text will compare upper left corners of their children, and upper right when set to RTL. Also fixed a bug in the Transform widget that didn't mark modified nodes as needing a semantics update.
1 parent c54f0bc commit 7be0bd1

File tree

3 files changed

+373
-53
lines changed

3 files changed

+373
-53
lines changed

packages/flutter/lib/src/rendering/proxy_box.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1895,7 +1895,7 @@ class RenderTransform extends RenderProxyBox {
18951895
this.origin = origin;
18961896
}
18971897

1898-
/// The origin of the coordinate system (relative to the upper left corder of
1898+
/// The origin of the coordinate system (relative to the upper left corner of
18991899
/// this render object) in which to apply the matrix.
19001900
///
19011901
/// Setting an origin is equivalent to conjugating the transform matrix by a
@@ -1907,6 +1907,7 @@ class RenderTransform extends RenderProxyBox {
19071907
return;
19081908
_origin = value;
19091909
markNeedsPaint();
1910+
markNeedsSemanticsUpdate();
19101911
}
19111912

19121913
/// The alignment of the origin, relative to the size of the box.
@@ -1927,6 +1928,7 @@ class RenderTransform extends RenderProxyBox {
19271928
return;
19281929
_alignment = value;
19291930
markNeedsPaint();
1931+
markNeedsSemanticsUpdate();
19301932
}
19311933

19321934
/// The text direction with which to resolve [alignment].
@@ -1940,6 +1942,7 @@ class RenderTransform extends RenderProxyBox {
19401942
return;
19411943
_textDirection = value;
19421944
markNeedsPaint();
1945+
markNeedsSemanticsUpdate();
19431946
}
19441947

19451948
/// When set to true, hit tests are performed based on the position of the
@@ -1960,42 +1963,49 @@ class RenderTransform extends RenderProxyBox {
19601963
return;
19611964
_transform = new Matrix4.copy(value);
19621965
markNeedsPaint();
1966+
markNeedsSemanticsUpdate();
19631967
}
19641968

19651969
/// Sets the transform to the identity matrix.
19661970
void setIdentity() {
19671971
_transform.setIdentity();
19681972
markNeedsPaint();
1973+
markNeedsSemanticsUpdate();
19691974
}
19701975

19711976
/// Concatenates a rotation about the x axis into the transform.
19721977
void rotateX(double radians) {
19731978
_transform.rotateX(radians);
19741979
markNeedsPaint();
1980+
markNeedsSemanticsUpdate();
19751981
}
19761982

19771983
/// Concatenates a rotation about the y axis into the transform.
19781984
void rotateY(double radians) {
19791985
_transform.rotateY(radians);
19801986
markNeedsPaint();
1987+
markNeedsSemanticsUpdate();
19811988
}
19821989

19831990
/// Concatenates a rotation about the z axis into the transform.
19841991
void rotateZ(double radians) {
19851992
_transform.rotateZ(radians);
19861993
markNeedsPaint();
1994+
markNeedsSemanticsUpdate();
19871995
}
19881996

19891997
/// Concatenates a translation by (x, y, z) into the transform.
19901998
void translate(double x, [double y = 0.0, double z = 0.0]) {
19911999
_transform.translate(x, y, z);
19922000
markNeedsPaint();
2001+
markNeedsSemanticsUpdate();
19932002
}
19942003

19952004
/// Concatenates a scale into the transform.
19962005
void scale(double x, [double y, double z]) {
19972006
_transform.scale(x, y, z);
19982007
markNeedsPaint();
2008+
markNeedsSemanticsUpdate();
19992009
}
20002010

20012011
Matrix4 get _effectiveTransform {

packages/flutter/lib/src/semantics/semantics.dart

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,18 +1431,74 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
14311431
}
14321432
}
14331433

1434-
/// A helper class to contain a semantics node and the effective
1435-
/// sort order of that node during the traversal of the tree. This is
1436-
/// what is actually sorted when semantics nodes are sorted.
1434+
/// This class defines the comparison that is used to sort [SemanticsNode]s
1435+
/// before sending them to the platform side.
1436+
///
1437+
/// This is a helper class used to contain a [node], the effective
1438+
/// [order], the globally transformed starting corner [globalStartCorner],
1439+
/// and the containing node's [containerTextDirection] during the traversal of
1440+
/// the semantics node tree. A null value is allowed for [containerTextDirection],
1441+
/// because in that case we want to fall back to ordering by child insertion
1442+
/// order for nodes that are equal after sorting from top to bottom.
14371443
class _TraversalSortNode implements Comparable<_TraversalSortNode> {
1438-
_TraversalSortNode(this.node, this.order);
1444+
_TraversalSortNode(this.node, this.order, this.containerTextDirection, Matrix4 transform)
1445+
: assert(node != null) {
1446+
// When containerTextDirection is null, this is set to topLeft, but the x
1447+
// coordinate is also ignored when doing the comparison in that case, so
1448+
// this isn't actually expressing a directionality opinion.
1449+
globalStartCorner = _transformPoint(
1450+
containerTextDirection == TextDirection.rtl ? node.rect.topRight : node.rect.topLeft,
1451+
transform,
1452+
);
1453+
}
1454+
1455+
/// The node that this sort node represents.
14391456
SemanticsNode node;
1457+
1458+
/// The effective text direction for this node is the directionality that
1459+
/// its container has.
1460+
TextDirection containerTextDirection;
1461+
1462+
/// This is the effective sort order for this node, taking into account its
1463+
/// parents.
14401464
SemanticsSortOrder order;
14411465

1466+
/// The is the starting corner for the rectangle on this semantics node in
1467+
/// global coordinates. When the container has the directionality
1468+
/// [TextDirection.ltr], this is the upper left corner. When the container
1469+
/// has the directionality [TextDirection.rtl], this is the upper right
1470+
/// corner. When the container has no directionality, this is set, but the
1471+
/// x coordinate is ignored.
1472+
Offset globalStartCorner;
1473+
1474+
static Offset _transformPoint(Offset point, Matrix4 matrix) {
1475+
final Vector3 result = matrix.transform3(new Vector3(point.dx, point.dy, 0.0));
1476+
return new Offset(result.x, result.y);
1477+
}
1478+
1479+
/// Compares the node's start corner with that of `other`.
1480+
///
1481+
/// Sorts top to bottom, and then start to end.
1482+
///
1483+
/// This takes into account the container text direction, since the
1484+
/// coordinate system has zero on the left, and we need to compare
1485+
/// differently for different text directions.
1486+
///
1487+
/// If no text direction is available (i.e. [containerTextDirection] is
1488+
/// null), then we sort by vertical position first, and then by child
1489+
/// insertion order.
14421490
int _compareGeometry(_TraversalSortNode other) {
1443-
// TODO(gspencer): Move the geometric comparison from the platform side to here.
1444-
// This involves calculating the globally-transformed quad for the semantics node rect
1445-
// and then sorting by its bounding box, based on the container's directionality.
1491+
final int verticalDiff = globalStartCorner.dy.compareTo(other.globalStartCorner.dy);
1492+
if (verticalDiff != 0) {
1493+
return verticalDiff;
1494+
}
1495+
switch (containerTextDirection) {
1496+
case TextDirection.rtl:
1497+
return other.globalStartCorner.dx.compareTo(globalStartCorner.dx);
1498+
case TextDirection.ltr:
1499+
return globalStartCorner.dx.compareTo(other.globalStartCorner.dx);
1500+
}
1501+
// In case containerTextDirection is null we fall back to child insertion order.
14461502
return 0;
14471503
}
14481504

@@ -1490,17 +1546,34 @@ class SemanticsOwner extends ChangeNotifier {
14901546
void _updateTraversalOrder() {
14911547
final List<_TraversalSortNode> nodesInSemanticsTraversalOrder = <_TraversalSortNode>[];
14921548
SemanticsSortOrder currentSortOrder = new SemanticsSortOrder(keys: <SemanticsSortKey>[]);
1549+
Matrix4 currentTransform = new Matrix4.identity();
1550+
TextDirection currentTextDirection = rootSemanticsNode.textDirection;
14931551
bool visitor(SemanticsNode node) {
14941552
final SemanticsSortOrder previousOrder = currentSortOrder;
1553+
final Matrix4 previousTransform = currentTransform.clone();
14951554
if (node.sortOrder != null) {
14961555
currentSortOrder = currentSortOrder.merge(node.sortOrder);
14971556
}
1498-
final _TraversalSortNode traversalNode = new _TraversalSortNode(node, currentSortOrder);
1557+
if (node.transform != null) {
1558+
currentTransform.multiply(node.transform);
1559+
}
1560+
final _TraversalSortNode traversalNode = new _TraversalSortNode(
1561+
node,
1562+
currentSortOrder,
1563+
currentTextDirection,
1564+
currentTransform,
1565+
);
1566+
// The text direction in force here is the parent's text direction.
14991567
nodesInSemanticsTraversalOrder.add(traversalNode);
15001568
if (node.hasChildren) {
1569+
final TextDirection previousTextDirection = currentTextDirection;
1570+
currentTextDirection = node.textDirection;
1571+
// Now visit the children with this node's text direction in force.
15011572
node.visitChildren(visitor);
1573+
currentTextDirection = previousTextDirection;
15021574
}
15031575
currentSortOrder = previousOrder;
1576+
currentTransform = previousTransform;
15041577
return true;
15051578
}
15061579
rootSemanticsNode.visitChildren(visitor);
@@ -2522,7 +2595,9 @@ String _concatStrings({
25222595
/// of keys can co-exist at the same level and not interfere with each other,
25232596
/// allowing for sorting into groups. Keys that evaluate as equal, or when
25242597
/// compared with Widgets that don't have [Semantics], fall back to the default
2525-
/// upper-start-to-lower-end geometric ordering.
2598+
/// upper-start-to-lower-end geometric ordering if a text directionality
2599+
/// exists, and they sort from top to bottom followed by child insertion order
2600+
/// when no directionality is present.
25262601
///
25272602
/// Since widgets are globally sorted by their sort key, the order does not have
25282603
/// to conform to the widget hierarchy.

0 commit comments

Comments
 (0)