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
_innerNode ??= new SemanticsNode(handler: this, showOnScreen: showOnScreen);
_innerNode
..wasAffectedByClip = node.wasAffectedByClip
..isMergedIntoParent = node.isPartOfNodeMerging
..rect = Offset.zero & node.rect.size;
semanticsAnnotator(_innerNode);
......@@ -3270,11 +3271,8 @@ class RenderMergeSemantics extends RenderProxyBox {
RenderMergeSemantics({ RenderBox child }) : super(child);
@override
SemanticsAnnotator get semanticsAnnotator => _annotate;
bool get isMergingSemanticsOfDescendants => true;
void _annotate(SemanticsNode node) {
node.mergeAllDescendantsIntoThisNode = true;
}
}
/// Excludes this subtree from the semantic tree.
......
......@@ -330,7 +330,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
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 _mergeAllDescendantsIntoThisNode = false;
set mergeAllDescendantsIntoThisNode(bool value) {
......@@ -341,17 +341,26 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_markDirty();
}
bool get _inheritedMergeAllDescendantsIntoThisNode => _inheritedMergeAllDescendantsIntoThisNodeValue;
bool _inheritedMergeAllDescendantsIntoThisNodeValue = false;
set _inheritedMergeAllDescendantsIntoThisNode(bool value) {
/// Whether this node merges its semantic information into an ancestor node.
bool get isMergedIntoParent => _isMergedIntoParent;
bool _isMergedIntoParent = false;
set isMergedIntoParent(bool value) {
assert(value != null);
if (_inheritedMergeAllDescendantsIntoThisNodeValue == value)
if (_isMergedIntoParent == value)
return;
_inheritedMergeAllDescendantsIntoThisNodeValue = value;
_isMergedIntoParent = value;
_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;
void _setFlag(SemanticsFlags flag, bool value) {
......@@ -433,13 +442,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// Restore this node to its default state.
void reset() {
final bool hadInheritedMergeAllDescendantsIntoThisNode = _inheritedMergeAllDescendantsIntoThisNode;
_actions = 0;
_flags = 0;
if (hadInheritedMergeAllDescendantsIntoThisNode)
_inheritedMergeAllDescendantsIntoThisNodeValue = true;
_label = '';
_textDirection = null;
_mergeAllDescendantsIntoThisNode = false;
_tags.clear();
_markDirty();
}
......@@ -603,8 +610,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_dirty = false;
_markDirty();
}
if (parent != null)
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
assert(isMergedIntoParent == (parent?.isPartOfNodeMerging ?? false));
if (_children != null) {
for (SemanticsNode child in _children)
child.attach(owner);
......@@ -759,7 +765,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
hideOwner = inDirtyNodes;
}
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;
if (offset != null) {
properties.add(new DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
......@@ -885,31 +891,12 @@ class SemanticsOwner extends ChangeNotifier {
visitedNodes.addAll(localDirtyNodes);
for (SemanticsNode node in localDirtyNodes) {
assert(node._dirty);
assert(node.parent == null || !node.parent._shouldMergeAllDescendantsIntoThisNode || node._inheritedMergeAllDescendantsIntoThisNode);
if (node._shouldMergeAllDescendantsIntoThisNode) {
assert(node.parent == null || !node.parent.isPartOfNodeMerging || node.isMergedIntoParent);
if (node.isPartOfNodeMerging) {
assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
if (node.mergeAllDescendantsIntoThisNode ||
node.parent != null && node.parent._shouldMergeAllDescendantsIntoThisNode) {
// if we're merged into our parent, make sure our parent is added to the 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
}
}
// if we're merged into our parent, make sure our parent is added to the dirty list
if (node.parent != null && node.parent.isPartOfNodeMerging)
node.parent._markDirty(); // this can add the node to the dirty list
}
}
}
......@@ -937,7 +924,7 @@ class SemanticsOwner extends ChangeNotifier {
SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
SemanticsNode result = _nodes[id];
if (result != null && result._shouldMergeAllDescendantsIntoThisNode && !result._canPerformAction(action)) {
if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
result._visitDescendants((SemanticsNode node) {
if (node._canPerformAction(action)) {
result = node;
......
......@@ -177,7 +177,7 @@ void main() {
expect(
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()
......
// 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