Skip to content

Commit f11bb25

Browse files
authored
SemanticsDebugger should use SemanticsNodes directly (flutter#6252)
Instead of reading the mojom serialization and re-inflating it, the SemanticsDebugger now shows the SemanticsNode objects directly.
1 parent bed0300 commit f11bb25

File tree

2 files changed

+324
-292
lines changed

2 files changed

+324
-292
lines changed

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

+167-20
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,52 @@ typedef void SemanticsAnnotator(SemanticsNode semantics);
4646
/// Return false to stop visiting nodes.
4747
typedef bool SemanticsNodeVisitor(SemanticsNode node);
4848

49+
/// Summary information about a [SemanticsNode] object.
50+
///
51+
/// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode],
52+
/// which means the individual fields on the semantics node don't fully describe
53+
/// the semantics at that node. This data structure contains the full semantics
54+
/// for the node.
55+
///
56+
/// Typically obtained from [SemanticsNode.getSemanticsData].
57+
class SemanticsData {
58+
/// Creates a semantics data object.
59+
///
60+
/// The [flags], [actions], [label], and [rect] arguments must not be null.
61+
const SemanticsData({
62+
@required this.flags,
63+
@required this.actions,
64+
@required this.label,
65+
@required this.rect,
66+
this.transform
67+
});
68+
69+
/// A bit field of [SemanticsFlags] that apply to this node.
70+
final int flags;
71+
72+
/// A bit field of [SemanticsActions] that apply to this node.
73+
final int actions;
74+
75+
/// A textual description of this node.
76+
final String label;
77+
78+
/// The bounding box for this node in its coordinate system.
79+
final Rect rect;
80+
81+
/// The transform from this node's coordinate system to its parent's coordinate system.
82+
///
83+
/// By default, the transform is null, which represents the identity
84+
/// transformation (i.e., that this node has the same coorinate system as its
85+
/// parent).
86+
final Matrix4 transform;
87+
88+
/// Whether [flags] contains the given flag.
89+
bool hasFlag(SemanticsFlags flag) => (flags & flag.index) != 0;
90+
91+
/// Whether [actions] contains the given action.
92+
bool hasAction(SemanticsAction action) => (actions & action.index) != 0;
93+
}
94+
4995
/// A node that represents some semantic data.
5096
///
5197
/// The semantics tree is maintained during the semantics phase of the pipeline
@@ -59,7 +105,7 @@ class SemanticsNode extends AbstractNode {
59105
/// is created.
60106
SemanticsNode({
61107
SemanticsActionHandler handler
62-
}) : _id = _generateNewId(),
108+
}) : id = _generateNewId(),
63109
_actionHandler = handler;
64110

65111
/// Creates a semantic node to represent the root of the semantics tree.
@@ -68,7 +114,7 @@ class SemanticsNode extends AbstractNode {
68114
SemanticsNode.root({
69115
SemanticsActionHandler handler,
70116
SemanticsOwner owner
71-
}) : _id = 0,
117+
}) : id = 0,
72118
_actionHandler = handler {
73119
attach(owner);
74120
}
@@ -79,9 +125,13 @@ class SemanticsNode extends AbstractNode {
79125
return _lastIdentifier;
80126
}
81127

82-
final int _id;
83-
final SemanticsActionHandler _actionHandler;
128+
/// The unique identifier for this node.
129+
///
130+
/// The root node has an id of zero. Other nodes are given a unique id when
131+
/// they are created.
132+
final int id;
84133

134+
final SemanticsActionHandler _actionHandler;
85135

86136
// GEOMETRY
87137
// These are automatically handled by RenderObject's own logic
@@ -151,7 +201,7 @@ class SemanticsNode extends AbstractNode {
151201
addAction(SemanticsAction.decrease);
152202
}
153203

154-
bool _hasAction(SemanticsAction action) {
204+
bool _canPerformAction(SemanticsAction action) {
155205
return _actionHandler != null && (_actions & action.index) != 0;
156206
}
157207

@@ -257,6 +307,20 @@ class SemanticsNode extends AbstractNode {
257307
bool get hasChildren => _children?.isNotEmpty ?? false;
258308
bool _dead = false;
259309

310+
/// Visits the immediate children of this node.
311+
///
312+
/// This function calls visitor for each child in a pre-order travseral
313+
/// until visitor returns false. Returns true if all the visitor calls
314+
/// returned true, otherwise returns false.
315+
void visitChildren(SemanticsNodeVisitor visitor) {
316+
if (_children != null) {
317+
for (SemanticsNode child in _children) {
318+
if (!visitor(child))
319+
return;
320+
}
321+
}
322+
}
323+
260324
/// Called during the compilation phase after all the children of this node have been compiled.
261325
///
262326
/// This function lets the semantic node respond to all the changes to its
@@ -323,9 +387,11 @@ class SemanticsNode extends AbstractNode {
323387
}
324388
}
325389

326-
// Visits all the descendants of this node, calling visitor for each one, until
327-
// visitor returns false. Returns true if all the visitor calls returned true,
328-
// otherwise returns false.
390+
/// Visit all the descendants of this node.
391+
///
392+
/// This function calls visitor for each descendant in a pre-order travseral
393+
/// until visitor returns false. Returns true if all the visitor calls
394+
/// returned true, otherwise returns false.
329395
bool _visitDescendants(SemanticsNodeVisitor visitor) {
330396
if (_children != null) {
331397
for (SemanticsNode child in _children) {
@@ -339,8 +405,8 @@ class SemanticsNode extends AbstractNode {
339405
@override
340406
void attach(SemanticsOwner owner) {
341407
super.attach(owner);
342-
assert(!owner._nodes.containsKey(_id));
343-
owner._nodes[_id] = this;
408+
assert(!owner._nodes.containsKey(id));
409+
owner._nodes[id] = this;
344410
owner._detachedNodes.remove(this);
345411
if (_dirty) {
346412
_dirty = false;
@@ -356,9 +422,9 @@ class SemanticsNode extends AbstractNode {
356422

357423
@override
358424
void detach() {
359-
assert(owner._nodes.containsKey(_id));
425+
assert(owner._nodes.containsKey(id));
360426
assert(!owner._detachedNodes.contains(this));
361-
owner._nodes.remove(_id);
427+
owner._nodes.remove(id);
362428
owner._detachedNodes.add(this);
363429
super.detach();
364430
if (_children != null) {
@@ -378,9 +444,42 @@ class SemanticsNode extends AbstractNode {
378444
}
379445
}
380446

447+
/// Returns a summary of the semantics for this node.
448+
///
449+
/// If this node has [mergeAllDescendantsIntoThisNode], then the returned data
450+
/// includes the information from this node's descendants. Otherwise, the
451+
/// returned data matches the data on this node.
452+
SemanticsData getSemanticsData() {
453+
int flags = _flags;
454+
int actions = _actions;
455+
String label = _label;
456+
457+
if (mergeAllDescendantsIntoThisNode) {
458+
_visitDescendants((SemanticsNode node) {
459+
flags |= node._flags;
460+
actions |= node._actions;
461+
if (node.label.isNotEmpty) {
462+
if (label.isEmpty)
463+
label = node.label;
464+
else
465+
label = '$label\n${node.label}';
466+
}
467+
return true;
468+
});
469+
}
470+
471+
return new SemanticsData(
472+
flags: flags,
473+
actions: actions,
474+
label: label,
475+
rect: rect,
476+
transform: transform
477+
);
478+
}
479+
381480
mojom.SemanticsNode _serialize() {
382481
mojom.SemanticsNode result = new mojom.SemanticsNode();
383-
result.id = _id;
482+
result.id = id;
384483
if (_dirty) {
385484
// We could be even more efficient about not sending data here, by only
386485
// sending the bits that are dirty (tracking the geometry, flags, strings,
@@ -430,7 +529,7 @@ class SemanticsNode extends AbstractNode {
430529
@override
431530
String toString() {
432531
StringBuffer buffer = new StringBuffer();
433-
buffer.write('$runtimeType($_id');
532+
buffer.write('$runtimeType($id');
434533
if (_dirty)
435534
buffer.write(" (${ owner != null && owner._dirtyNodes.contains(this) ? 'dirty' : 'STALE' })");
436535
if (_shouldMergeAllDescendantsIntoThisNode)
@@ -497,6 +596,11 @@ class SemanticsOwner {
497596

498597
final List<SemanticsListener> _listeners = <SemanticsListener>[];
499598

599+
/// The root node of the semantics tree, if any.
600+
///
601+
/// If the semantics tree is empty, returns null.
602+
SemanticsNode get rootSemanticsNode => _nodes[0];
603+
500604
/// Releases any resources retained by this object.
501605
///
502606
/// Requires that there are no listeners registered with [addListener].
@@ -597,19 +701,18 @@ class SemanticsOwner {
597701
_dirtyNodes.clear();
598702
}
599703

600-
SemanticsActionHandler _getSemanticsActionHandlerForId(int id, { @required SemanticsAction action }) {
601-
assert(action != null);
704+
SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
602705
SemanticsNode result = _nodes[id];
603-
if (result != null && result._shouldMergeAllDescendantsIntoThisNode && !result._hasAction(action)) {
706+
if (result != null && result._shouldMergeAllDescendantsIntoThisNode && !result._canPerformAction(action)) {
604707
result._visitDescendants((SemanticsNode node) {
605-
if (node._actionHandler != null && node._hasAction(action)) {
708+
if (node._canPerformAction(action)) {
606709
result = node;
607710
return false; // found node, abort walk
608711
}
609712
return true; // continue walk
610713
});
611714
}
612-
if (result == null || !result._hasAction(action))
715+
if (result == null || !result._canPerformAction(action))
613716
return null;
614717
return result._actionHandler;
615718
}
@@ -619,7 +722,51 @@ class SemanticsOwner {
619722
/// If the [SemanticsNode] has not indicated that it can perform the action,
620723
/// this function does nothing.
621724
void performAction(int id, SemanticsAction action) {
622-
SemanticsActionHandler handler = _getSemanticsActionHandlerForId(id, action: action);
725+
assert(action != null);
726+
SemanticsActionHandler handler = _getSemanticsActionHandlerForId(id, action);
727+
handler?.performAction(action);
728+
}
729+
730+
SemanticsActionHandler _getSemanticsActionHandlerForPosition(SemanticsNode node, Point position, SemanticsAction action) {
731+
if (node.transform != null) {
732+
Matrix4 inverse = new Matrix4.identity();
733+
if (inverse.copyInverse(node.transform) == 0.0)
734+
return null;
735+
position = MatrixUtils.transformPoint(inverse, position);
736+
}
737+
if (!node.rect.contains(position))
738+
return null;
739+
if (node.mergeAllDescendantsIntoThisNode) {
740+
SemanticsNode result;
741+
node._visitDescendants((SemanticsNode child) {
742+
if (child._canPerformAction(action)) {
743+
result = child;
744+
return false;
745+
}
746+
return true;
747+
});
748+
return result?._actionHandler;
749+
}
750+
if (node.hasChildren) {
751+
for (SemanticsNode child in node._children.reversed) {
752+
SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(child, position, action);
753+
if (handler != null)
754+
return handler;
755+
}
756+
}
757+
return node._canPerformAction(action) ? node._actionHandler : null;
758+
}
759+
760+
/// Asks the [SemanticsNode] with at the given position to perform the given action.
761+
///
762+
/// If the [SemanticsNode] has not indicated that it can perform the action,
763+
/// this function does nothing.
764+
void performActionAt(Point position, SemanticsAction action) {
765+
assert(action != null);
766+
final SemanticsNode node = rootSemanticsNode;
767+
if (node == null)
768+
return;
769+
SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(node, position, action);
623770
handler?.performAction(action);
624771
}
625772
}

0 commit comments

Comments
 (0)