Commit 437e4c08 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Make "mergeIntoParent" information available during compile of semantics tree (#12332)

* ++

* Allow unmerging of SemanticsNodes

* test passing

* ++

* remove prints

* doc comments

* rectify comment

* review comments
parent 0044ea2d
...@@ -3014,6 +3014,7 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA ...@@ -3014,6 +3014,7 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
_innerNode ??= new SemanticsNode(handler: this, showOnScreen: showOnScreen); _innerNode ??= new SemanticsNode(handler: this, showOnScreen: showOnScreen);
_innerNode _innerNode
..wasAffectedByClip = node.wasAffectedByClip ..wasAffectedByClip = node.wasAffectedByClip
..isMergedIntoParent = node.isPartOfNodeMerging
..rect = Offset.zero & node.rect.size; ..rect = Offset.zero & node.rect.size;
semanticsAnnotator(_innerNode); semanticsAnnotator(_innerNode);
...@@ -3270,11 +3271,8 @@ class RenderMergeSemantics extends RenderProxyBox { ...@@ -3270,11 +3271,8 @@ class RenderMergeSemantics extends RenderProxyBox {
RenderMergeSemantics({ RenderBox child }) : super(child); RenderMergeSemantics({ RenderBox child }) : super(child);
@override @override
SemanticsAnnotator get semanticsAnnotator => _annotate; bool get isMergingSemanticsOfDescendants => true;
void _annotate(SemanticsNode node) {
node.mergeAllDescendantsIntoThisNode = true;
}
} }
/// Excludes this subtree from the semantic tree. /// Excludes this subtree from the semantic tree.
......
...@@ -330,7 +330,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -330,7 +330,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
return _actionHandler != null && (_actions & action.index) != 0; return _actionHandler != null && (_actions & action.index) != 0;
} }
/// Whether all this node and all of its descendants should be treated as one logical entity. /// Whether this node and all of its descendants should be treated as one logical entity.
bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode; bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
bool _mergeAllDescendantsIntoThisNode = false; bool _mergeAllDescendantsIntoThisNode = false;
set mergeAllDescendantsIntoThisNode(bool value) { set mergeAllDescendantsIntoThisNode(bool value) {
...@@ -341,17 +341,26 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -341,17 +341,26 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_markDirty(); _markDirty();
} }
bool get _inheritedMergeAllDescendantsIntoThisNode => _inheritedMergeAllDescendantsIntoThisNodeValue; /// Whether this node merges its semantic information into an ancestor node.
bool _inheritedMergeAllDescendantsIntoThisNodeValue = false; bool get isMergedIntoParent => _isMergedIntoParent;
set _inheritedMergeAllDescendantsIntoThisNode(bool value) { bool _isMergedIntoParent = false;
set isMergedIntoParent(bool value) {
assert(value != null); assert(value != null);
if (_inheritedMergeAllDescendantsIntoThisNodeValue == value) if (_isMergedIntoParent == value)
return; return;
_inheritedMergeAllDescendantsIntoThisNodeValue = value; _isMergedIntoParent = value;
_markDirty(); _markDirty();
} }
bool get _shouldMergeAllDescendantsIntoThisNode => mergeAllDescendantsIntoThisNode || _inheritedMergeAllDescendantsIntoThisNode; /// Whether this node is taking part in a merge of semantic information.
///
/// This returns true if the node is either merged into an ancestor node or if
/// decedent nodes are merged into this node.
///
/// See also:
/// * [isMergedIntoParent]
/// * [mergeAllDescendantsIntoThisNode]
bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
int _flags = 0; int _flags = 0;
void _setFlag(SemanticsFlags flag, bool value) { void _setFlag(SemanticsFlags flag, bool value) {
...@@ -433,13 +442,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -433,13 +442,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// Restore this node to its default state. /// Restore this node to its default state.
void reset() { void reset() {
final bool hadInheritedMergeAllDescendantsIntoThisNode = _inheritedMergeAllDescendantsIntoThisNode;
_actions = 0; _actions = 0;
_flags = 0; _flags = 0;
if (hadInheritedMergeAllDescendantsIntoThisNode)
_inheritedMergeAllDescendantsIntoThisNodeValue = true;
_label = ''; _label = '';
_textDirection = null; _textDirection = null;
_mergeAllDescendantsIntoThisNode = false;
_tags.clear(); _tags.clear();
_markDirty(); _markDirty();
} }
...@@ -603,8 +610,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -603,8 +610,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_dirty = false; _dirty = false;
_markDirty(); _markDirty();
} }
if (parent != null) assert(isMergedIntoParent == (parent?.isPartOfNodeMerging ?? false));
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
if (_children != null) { if (_children != null) {
for (SemanticsNode child in _children) for (SemanticsNode child in _children)
child.attach(owner); child.attach(owner);
...@@ -759,7 +765,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -759,7 +765,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
hideOwner = inDirtyNodes; hideOwner = inDirtyNodes;
} }
properties.add(new DiagnosticsProperty<SemanticsOwner>('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info)); properties.add(new DiagnosticsProperty<SemanticsOwner>('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info));
properties.add(new FlagProperty('shouldMergeAllDescendantsIntoThisNode', value: _shouldMergeAllDescendantsIntoThisNode, ifTrue: 'leaf merge')); properties.add(new FlagProperty('isPartOfNodeMerging', value: isPartOfNodeMerging, ifTrue: 'leaf merge'));
final Offset offset = transform != null ? MatrixUtils.getAsTranslation(transform) : null; final Offset offset = transform != null ? MatrixUtils.getAsTranslation(transform) : null;
if (offset != null) { if (offset != null) {
properties.add(new DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false)); properties.add(new DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
...@@ -885,31 +891,12 @@ class SemanticsOwner extends ChangeNotifier { ...@@ -885,31 +891,12 @@ class SemanticsOwner extends ChangeNotifier {
visitedNodes.addAll(localDirtyNodes); visitedNodes.addAll(localDirtyNodes);
for (SemanticsNode node in localDirtyNodes) { for (SemanticsNode node in localDirtyNodes) {
assert(node._dirty); assert(node._dirty);
assert(node.parent == null || !node.parent._shouldMergeAllDescendantsIntoThisNode || node._inheritedMergeAllDescendantsIntoThisNode); assert(node.parent == null || !node.parent.isPartOfNodeMerging || node.isMergedIntoParent);
if (node._shouldMergeAllDescendantsIntoThisNode) { if (node.isPartOfNodeMerging) {
assert(node.mergeAllDescendantsIntoThisNode || node.parent != null); assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
if (node.mergeAllDescendantsIntoThisNode || // if we're merged into our parent, make sure our parent is added to the dirty list
node.parent != null && node.parent._shouldMergeAllDescendantsIntoThisNode) { if (node.parent != null && node.parent.isPartOfNodeMerging)
// if we're merged into our parent, make sure our parent is added to the list node.parent._markDirty(); // this can add the node to the dirty list
if (node.parent != null && node.parent._shouldMergeAllDescendantsIntoThisNode)
node.parent._markDirty(); // this can add the node to the dirty list
// make sure all the descendants are also marked, so that if one gets marked dirty later we know to walk up then too
if (node._children != null) {
for (SemanticsNode child in node._children)
child._inheritedMergeAllDescendantsIntoThisNode = true; // this can add the node to the dirty list
}
} else {
// we previously were being merged but aren't any more
// update our bits and all our descendants'
assert(node._inheritedMergeAllDescendantsIntoThisNode);
assert(!node.mergeAllDescendantsIntoThisNode);
assert(node.parent == null || !node.parent._shouldMergeAllDescendantsIntoThisNode);
node._inheritedMergeAllDescendantsIntoThisNode = false;
if (node._children != null) {
for (SemanticsNode child in node._children)
child._inheritedMergeAllDescendantsIntoThisNode = false; // this can add the node to the dirty list
}
}
} }
} }
} }
...@@ -937,7 +924,7 @@ class SemanticsOwner extends ChangeNotifier { ...@@ -937,7 +924,7 @@ class SemanticsOwner extends ChangeNotifier {
SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) { SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
SemanticsNode result = _nodes[id]; SemanticsNode result = _nodes[id];
if (result != null && result._shouldMergeAllDescendantsIntoThisNode && !result._canPerformAction(action)) { if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
result._visitDescendants((SemanticsNode node) { result._visitDescendants((SemanticsNode node) {
if (node._canPerformAction(action)) { if (node._canPerformAction(action)) {
result = node; result = node;
......
...@@ -177,7 +177,7 @@ void main() { ...@@ -177,7 +177,7 @@ void main() {
expect( expect(
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden), minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
'SemanticsNode#16(owner: null, shouldMergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), wasAffectedByClip: false, actions: [], tags: [], isSelected: false, label: "", textDirection: null)\n', 'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), wasAffectedByClip: false, actions: [], tags: [], isSelected: false, label: "", textDirection: null)\n',
); );
final SemanticsNode allProperties = new SemanticsNode() final SemanticsNode allProperties = new SemanticsNode()
......
// 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 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('MergeSemantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
// not merged
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Row(
children: <Widget>[
new Semantics(
label: 'test1',
textDirection: TextDirection.ltr,
child: new Container()
),
new Semantics(
label: 'test2',
textDirection: TextDirection.ltr,
child: new Container()
)
],
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(id: 1, label: 'test1'),
new TestSemantics.rootChild(id: 2, label: 'test2'),
],
),
ignoreRect: true,
ignoreTransform: true,
));
// merged
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new MergeSemantics(
child: new Row(
children: <Widget>[
new Semantics(
label: 'test1',
textDirection: TextDirection.ltr,
child: new Container()
),
new Semantics(
label: 'test2',
textDirection: TextDirection.ltr,
child: new Container()
)
],
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(label: 'test1\ntest2'),
ignoreRect: true,
ignoreTransform: true,
));
// not merged
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Row(
children: <Widget>[
new Semantics(
label: 'test1',
textDirection: TextDirection.ltr,
child: new Container()
),
new Semantics(
label: 'test2',
textDirection: TextDirection.ltr,
child: new Container()
)
],
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(id: 5, label: 'test1'),
new TestSemantics.rootChild(id: 6, label: 'test2'),
],
),
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment