Unverified Commit 8a6e9737 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Remove semantics boundary from gesture detector AND MORE (#13983)

`RenderSemanticsGestureHandler` is no longer a semantics boundary, which allows us to correctly mark disabled buttons as disabled without having their semantics size and semantics node id change unexpectedly.

Fixes https://github.com/flutter/flutter/issues/12589.
Fixes https://github.com/flutter/flutter/issues/11991.
See also https://github.com/flutter/flutter/issues/11993.

This change also required some refactoring to how we deal with `twoPaneSemantics` scrolling as it previously relied on `RenderSemanticsGestureHandler` being a semantics boundary. This should also make the underlying logic easier to understand.

In addition, the following minor changes are included in this PR:
* Removal of orphaned and unused `SemanticsConfiguration.isMergingDescendantsIntoOneNode`.
* Logic optimizations for `markNeedsSemanticsUpdate` .
* Fix for edge case where `MergeSemantics` failed to merge semantics.
* Use of emojis to better indicate leaf merging in the printed semantics tree.
* Better assert message for adding invisible child semantics nodes.
* Make some semantics tests robuster by not relying on creation order of SemanticsNode ids across test boundaries.

Fixes https://github.com/flutter/flutter/issues/13943.
parent 33d8a035
......@@ -332,7 +332,7 @@ class _MaterialButtonState extends State<MaterialButton> {
child: new Center(
widthFactor: 1.0,
heightFactor: 1.0,
child: new Semantics(button: true, child: widget.child),
child: widget.child,
)
)
)
......@@ -352,12 +352,17 @@ class _MaterialButtonState extends State<MaterialButton> {
child: contents
);
}
return new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: widget.minWidth ?? buttonTheme.minWidth,
minHeight: height,
return new Semantics(
container: true,
button: true,
enabled: widget.enabled,
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: widget.minWidth ?? buttonTheme.minWidth,
minHeight: height,
),
child: contents
),
child: contents
);
}
}
......@@ -2243,7 +2243,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
// RenderObject are still up-to date. Therefore, we will later only rebuild
// the semantics subtree starting at th identified semantics boundary.
final bool wasSemanticsBoundary = _cachedSemanticsConfiguration?.isSemanticBoundary == true;
final bool wasSemanticsBoundary = _semantics != null && _cachedSemanticsConfiguration?.isSemanticBoundary == true;
_cachedSemanticsConfiguration = null;
bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary;
RenderObject node = this;
......@@ -2254,7 +2254,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
node._needsSemanticsUpdate = true;
node = node.parent;
node._cachedSemanticsConfiguration = null;
isEffectiveSemanticsBoundary = node._semanticsConfiguration.isSemanticBoundary;
if (isEffectiveSemanticsBoundary && node._semantics == null) {
// We have reached a semantics boundary that doesn't own a semantics node.
......@@ -2274,10 +2273,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
owner._nodesNeedingSemantics.remove(this);
}
if (!node._needsSemanticsUpdate) {
if (node != this) {
// Reset for `this` happened above already.
node._cachedSemanticsConfiguration = null;
}
node._needsSemanticsUpdate = true;
if (owner != null) {
assert(node._semanticsConfiguration.isSemanticBoundary || node.parent is! RenderObject);
......
......@@ -2565,37 +2565,6 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
_onVerticalDragUpdate = onVerticalDragUpdate,
super(child);
/// When a [SemanticsNode] that is a direct child of this object's
/// [SemanticsNode] is tagged with [excludeFromScrolling] it will not be
/// part of the scrolling area for semantic purposes.
///
/// This behavior is only active if the [SemanticsNode] of this
/// [RenderSemanticsGestureHandler] is tagged with [useTwoPaneSemantics].
/// Otherwise, the [excludeFromScrolling] tag is ignored.
///
/// As an example, a [RenderSliver] that stays on the screen within a
/// [Scrollable] even though the user has scrolled past it (e.g. a pinned app
/// bar) can tag its [SemanticsNode] with [excludeFromScrolling] to indicate
/// that it should no longer be considered for semantic actions related to
/// scrolling.
static const SemanticsTag excludeFromScrolling = const SemanticsTag('RenderSemanticsGestureHandler.excludeFromScrolling');
/// If the [SemanticsNode] of this [RenderSemanticsGestureHandler] is tagged
/// with [useTwoPaneSemantics], two semantics nodes will be used to represent
/// this render object in the semantics tree.
///
/// Two semantics nodes are necessary to exclude certain child nodes (via the
/// [excludeFromScrolling] tag) from the scrollable area for semantic
/// purposes.
///
/// If this tag is used, the first "outer" semantics node is the regular node
/// of this object. The second "inner" node is introduced as a child to that
/// node. All scrollable children become children of the inner node, which has
/// the semantic scrolling logic enabled. All children that have been
/// excluded from scrolling with [excludeFromScrolling] are turned into
/// children of the outer node.
static const SemanticsTag useTwoPaneSemantics = const SemanticsTag('RenderSemanticsGestureHandler.twoPane');
/// If non-null, the set of actions to allow. Other actions will be omitted,
/// even if their callback is provided.
///
......@@ -2673,24 +2642,10 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
/// leftwards drag.
double scrollFactor;
bool get _hasHandlers {
return onTap != null
|| onLongPress != null
|| onHorizontalDragUpdate != null
|| onVerticalDragUpdate != null;
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = _hasHandlers;
// TODO(goderbauer): this needs to be set even when there is only potential
// for this to become a scroll view.
config.explicitChildNodes = onHorizontalDragUpdate != null
|| onVerticalDragUpdate != null;
if (onTap != null && _isValidAction(SemanticsAction.tap))
config.onTap = onTap;
if (onLongPress != null && _isValidAction(SemanticsAction.longPress))
......@@ -2713,42 +2668,6 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
return validActions == null || validActions.contains(action);
}
SemanticsNode _innerNode;
SemanticsNode _annotatedNode;
/// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is
/// annotated with this object's semantics information.
void sendSemanticsEvent(SemanticsEvent event) {
_annotatedNode?.sendEvent(event);
}
@override
void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
if (children.isEmpty || !children.first.isTagged(useTwoPaneSemantics)) {
_annotatedNode = node;
super.assembleSemanticsNode(node, config, children);
return;
}
_innerNode ??= new SemanticsNode(showOnScreen: showOnScreen);
_innerNode
..isMergedIntoParent = node.isPartOfNodeMerging
..rect = Offset.zero & node.rect.size;
_annotatedNode = _innerNode;
final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode];
final List<SemanticsNode> included = <SemanticsNode>[];
for (SemanticsNode child in children) {
assert(child.isTagged(useTwoPaneSemantics));
if (child.isTagged(excludeFromScrolling))
excluded.add(child);
else
included.add(child);
}
node.updateWith(config: null, childrenInInversePaintOrder: excluded);
_innerNode.updateWith(config: config, childrenInInversePaintOrder: included);
}
void _performSemanticScrollLeft() {
if (onHorizontalDragUpdate != null) {
final double primaryDelta = size.width * -scrollFactor;
......
......@@ -14,8 +14,8 @@ import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart';
import 'object.dart';
import 'proxy_box.dart';
import 'sliver.dart';
import 'viewport.dart';
import 'viewport_offset.dart';
/// A base class for slivers that have a [RenderBox] child which scrolls
......@@ -225,7 +225,7 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
super.describeSemanticsConfiguration(config);
if (_excludeFromSemanticsScrolling)
config.addTagForChildren(RenderSemanticsGestureHandler.excludeFromScrolling);
config.addTagForChildren(RenderViewport.excludeFromScrolling);
}
@override
......
......@@ -12,7 +12,6 @@ import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart';
import 'object.dart';
import 'proxy_box.dart';
import 'sliver.dart';
import 'viewport_offset.dart';
......@@ -91,12 +90,11 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
_crossAxisDirection = crossAxisDirection,
_offset = offset;
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.addTagForChildren(RenderSemanticsGestureHandler.useTwoPaneSemantics);
config.addTagForChildren(RenderViewport.useTwoPaneSemantics);
}
@override
......@@ -746,6 +744,36 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
_center = firstChild;
}
/// If a [RenderAbstractViewport] overrides
/// [RenderObject.describeSemanticsConfiguration] to add the [SemanticsTag]
/// [useTwoPaneSemantics] to its [SemanticsConfiguration], two semantics nodes
/// will be used to represent the viewport with its associated scrolling
/// actions in the semantics tree.
///
/// Two semantics nodes (an inner and an outer node) are necessary to exclude
/// certain child nodes (via the [excludeFromScrolling] tag) from the
/// scrollable area for semantic purposes: The [SemanticsNode]s of children
/// that should be excluded from scrolling will be attached to the outer node.
/// The semantic scrolling actions and the [SemanticsNode]s of scrollable
/// children will be attached to the inner node, which itself is a child of
/// the outer node.
static const SemanticsTag useTwoPaneSemantics = const SemanticsTag('RenderViewport.twoPane');
/// When a top-level [SemanticsNode] below a [RenderAbstractViewport] is
/// tagged with [excludeFromScrolling] it will not be part of the scrolling
/// area for semantic purposes.
///
/// This behavior is only active if the [RenderAbstractViewport]
/// tagged its [SemanticsConfiguration] with [useTwoPaneSemantics].
/// Otherwise, the [excludeFromScrolling] tag is ignored.
///
/// As an example, a [RenderSliver] that stays on the screen within a
/// [Scrollable] even though the user has scrolled past it (e.g. a pinned app
/// bar) can tag its [SemanticsNode] with [excludeFromScrolling] to indicate
/// that it should no longer be considered for semantic actions related to
/// scrolling.
static const SemanticsTag excludeFromScrolling = const SemanticsTag('RenderViewport.excludeFromScrolling');
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalContainerParentData)
......
......@@ -654,6 +654,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
);
}
}
assert(!newChildren.any((SemanticsNode node) => node.isMergedIntoParent) || isPartOfNodeMerging);
_debugPreviousSnapshot = new List<SemanticsNode>.from(newChildren);
......@@ -677,7 +678,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
}
if (newChildren != null) {
for (SemanticsNode child in newChildren) {
assert(!child.isInvisible, 'Child with id ${child.id} is invisible and should not be added to tree.');
assert(!child.isInvisible, '$child is invisible and should not be added as child of $this.');
child._dead = false;
}
}
......@@ -1072,7 +1073,8 @@ 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('isPartOfNodeMerging', value: isPartOfNodeMerging, ifTrue: 'leaf merge'));
properties.add(new FlagProperty('isMergedIntoParent', value: isMergedIntoParent, ifTrue: 'merged up ⬆️'));
properties.add(new FlagProperty('mergeAllDescendantsIntoThisNode', value: mergeAllDescendantsIntoThisNode, ifTrue: 'merge boundary ⛔️'));
final Offset offset = transform != null ? MatrixUtils.getAsTranslation(transform) : null;
if (offset != null) {
properties.add(new DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
......@@ -1344,7 +1346,7 @@ class SemanticsConfiguration {
bool get isSemanticBoundary => _isSemanticBoundary;
bool _isSemanticBoundary = false;
set isSemanticBoundary(bool value) {
assert(!isMergingDescendantsIntoOneNode || value);
assert(!isMergingSemanticsOfDescendants || value);
_isSemanticBoundary = value;
}
......@@ -1380,20 +1382,6 @@ class SemanticsConfiguration {
/// determine if a node is previous to this one.
bool isBlockingSemanticsOfPreviouslyPaintedNodes = false;
/// Whether the semantics information of all descendants should be merged
/// into the owning [RenderObject] semantics node.
///
/// When this is set to true the [SemanticsNode] of the owning [RenderObject]
/// will not have any children.
///
/// Setting this to true requires that [isSemanticBoundary] is also true.
bool get isMergingDescendantsIntoOneNode => _isMergingDescendantsIntoOneNode;
bool _isMergingDescendantsIntoOneNode = false;
set isMergingDescendantsIntoOneNode(bool value) {
assert(isSemanticBoundary);
_isMergingDescendantsIntoOneNode = value;
}
// SEMANTIC ANNOTATIONS
// These will end up on [SemanticNode]s generated from
// [SemanticsConfiguration]s.
......@@ -1645,9 +1633,12 @@ class SemanticsConfiguration {
/// If set to true, the descendants of the owning [RenderObject]'s
/// [SemanticsNode] will merge their semantic information into the
/// [SemanticsNode] representing the owning [RenderObject].
///
/// Setting this to true requires that [isSemanticBoundary] is also true.
bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
bool _isMergingSemanticsOfDescendants = false;
set isMergingSemanticsOfDescendants(bool value) {
assert(isSemanticBoundary);
_isMergingSemanticsOfDescendants = value;
_hasBeenAnnotated = true;
}
......@@ -1910,9 +1901,11 @@ class SemanticsConfiguration {
/// Returns an exact copy of this configuration.
SemanticsConfiguration copy() {
return new SemanticsConfiguration()
..isSemanticBoundary = isSemanticBoundary
.._isSemanticBoundary = _isSemanticBoundary
..explicitChildNodes = explicitChildNodes
..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes
.._hasBeenAnnotated = _hasBeenAnnotated
.._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
.._textDirection = _textDirection
.._label = _label
.._increasedValue = _increasedValue
......@@ -1920,6 +1913,7 @@ class SemanticsConfiguration {
.._decreasedValue = _decreasedValue
.._hint = _hint
.._flags = _flags
.._tagsForChildren = _tagsForChildren
.._actionsAsBits = _actionsAsBits
.._actions.addAll(_actions);
}
......
......@@ -570,21 +570,6 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
}
}
/// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is
/// annotated with this object's semantics information.
///
/// The event can be interpreted by assistive technologies to provide
/// additional feedback to the user about the state of the UI.
///
/// The event will not be sent if [RawGestureDetector.excludeFromSemantics] is
/// set to true.
void sendSemanticsEvent(SemanticsEvent event) {
if (!widget.excludeFromSemantics) {
final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
semanticsGestureHandler.sendSemanticsEvent(event);
}
}
@override
void dispose() {
for (GestureRecognizer recognizer in _recognizers.values)
......
......@@ -281,7 +281,8 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
if (_semanticsScrollEventScheduled)
return;
SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
_gestureDetectorKey.currentState?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent(
final _RenderExcludableScrollSemantics render = _excludableScrollSemanticsKey.currentContext?.findRenderObject();
render?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent(
axis: position.axis,
pixels: position.pixels,
minScrollExtent: position.minScrollExtent,
......@@ -332,7 +333,9 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
}
// SEMANTICS ACTIONS
// SEMANTICS
final GlobalKey _excludableScrollSemanticsKey = new GlobalKey();
@override
@protected
......@@ -487,18 +490,24 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
Widget build(BuildContext context) {
assert(position != null);
// TODO(ianh): Having all these global keys is sad.
final Widget result = new RawGestureDetector(
key: _gestureDetectorKey,
gestures: _gestureRecognizers,
behavior: HitTestBehavior.opaque,
child: new IgnorePointer(
key: _ignorePointerKey,
ignoring: _shouldIgnorePointer,
ignoringSemantics: false,
child: new _ScrollableScope(
scrollable: this,
position: position,
child: widget.viewportBuilder(context, position),
final Widget result = new _ExcludableScrollSemantics(
key: _excludableScrollSemanticsKey,
child: new RawGestureDetector(
key: _gestureDetectorKey,
gestures: _gestureRecognizers,
behavior: HitTestBehavior.opaque,
child: new Semantics(
explicitChildNodes: true,
child: new IgnorePointer(
key: _ignorePointerKey,
ignoring: _shouldIgnorePointer,
ignoringSemantics: false,
child: new _ScrollableScope(
scrollable: this,
position: position,
child: widget.viewportBuilder(context, position),
),
),
),
),
);
......@@ -511,3 +520,70 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
description.add(new DiagnosticsProperty<ScrollPosition>('position', position));
}
}
/// With [_ExcludableScrollSemantics] certain child [SemanticsNode]s can be
/// excluded from the scrollable area for semantics purposes.
///
/// Nodes, that are to be excluded, have to be tagged with
/// [RenderViewport.excludeFromScrolling] and the [RenderAbstractViewport] in
/// use has to add the [RenderViewport.useTwoPaneSemantics] tag to its
/// [SemanticsConfiguration] by overriding
/// [RenderObject.describeSemanticsConfiguration].
///
/// If the tag [RenderViewport.useTwoPaneSemantics] is present on the viewport,
/// two semantics nodes will be used to represent the [Scrollable]: The outer
/// node will contain all children, that are excluded from scrolling. The inner
/// node, which is annotated with the scrolling actions, will house the
/// scrollable children.
class _ExcludableScrollSemantics extends SingleChildRenderObjectWidget {
const _ExcludableScrollSemantics({ Key key, Widget child }) : super(key: key, child: child);
@override
_RenderExcludableScrollSemantics createRenderObject(BuildContext context) => new _RenderExcludableScrollSemantics();
}
class _RenderExcludableScrollSemantics extends RenderProxyBox {
_RenderExcludableScrollSemantics({ RenderBox child }) : super(child);
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = true;
}
SemanticsNode _innerNode;
SemanticsNode _annotatedNode;
@override
void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
if (children.isEmpty || !children.first.isTagged(RenderViewport.useTwoPaneSemantics)) {
_annotatedNode = node;
super.assembleSemanticsNode(node, config, children);
return;
}
_innerNode ??= new SemanticsNode(showOnScreen: showOnScreen);
_innerNode
..isMergedIntoParent = node.isPartOfNodeMerging
..rect = Offset.zero & node.rect.size;
_annotatedNode = _innerNode;
final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode];
final List<SemanticsNode> included = <SemanticsNode>[];
for (SemanticsNode child in children) {
assert(child.isTagged(RenderViewport.useTwoPaneSemantics));
if (child.isTagged(RenderViewport.excludeFromScrolling))
excluded.add(child);
else
included.add(child);
}
node.updateWith(config: null, childrenInInversePaintOrder: excluded);
_innerNode.updateWith(config: config, childrenInInversePaintOrder: included);
}
/// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is
/// annotated with this object's semantics information.
void sendSemanticsEvent(SemanticsEvent event) {
_annotatedNode?.sendEvent(event);
}
}
......@@ -13,6 +13,10 @@ import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
setUp(() {
debugResetSemanticsIdCounter();
});
testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
......@@ -33,11 +37,17 @@ void main() {
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
actions: SemanticsAction.tap.index,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
label: 'ABC',
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
transform: new Matrix4.translationValues(356.0, 282.0, 0.0),
flags: SemanticsFlag.isButton.index,
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
)
],
),
......@@ -67,11 +77,17 @@ void main() {
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
actions: SemanticsAction.tap.index,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
label: 'ABC',
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
transform: new Matrix4.translationValues(356.0, 282.0, 0.0),
flags: SemanticsFlag.isButton.index,
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
)
]
),
......@@ -248,4 +264,83 @@ void main() {
await gesture.up();
});
testWidgets('Disabled MaterialButton has same semantic size as enabled and exposes disabled semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
final Rect expectedButtonSize = new Rect.fromLTRB(0.0, 0.0, 116.0, 36.0);
// Button is in center of screen
final Matrix4 expectedButtonTransform = new Matrix4.identity()
..translate(
TestSemantics.fullScreen.width / 2 - expectedButtonSize.width /2,
TestSemantics.fullScreen.height / 2 - expectedButtonSize.height /2,
);
// enabled button
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new MaterialButton(
child: const Text('Button'),
onPressed: () { /* to make sure the button is enabled */ },
),
),
),
));
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: expectedButtonSize,
transform: expectedButtonTransform,
label: 'Button',
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
),
],
),
));
// disabled button
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr,
child: const Material(
child: const Center(
child: const MaterialButton(
child: const Text('Button'),
onPressed: null, // button is disabled
),
),
),
));
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: expectedButtonSize,
transform: expectedButtonTransform,
label: 'Button',
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
],
),
],
),
));
semantics.dispose();
});
}
......@@ -48,8 +48,14 @@ void main() {
id: 2,
label: 'Button',
textDirection: TextDirection.ltr,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isButton.index,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
),
],
),
......
......@@ -94,11 +94,11 @@ void main() {
],
),
));
// This test verifies that the label and the control get merged.
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: null,
flags: <SemanticsFlag>[
......@@ -111,7 +111,6 @@ void main() {
label: 'aaa\nAAA',
),
new TestSemantics.rootChild(
id: 4,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 56.0, 0.0),
flags: <SemanticsFlag>[
......@@ -124,7 +123,6 @@ void main() {
label: 'bbb\nBBB',
),
new TestSemantics.rootChild(
id: 7,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 112.0, 0.0),
flags: <SemanticsFlag>[
......@@ -136,7 +134,7 @@ void main() {
label: 'CCC\nccc',
),
],
)));
), ignoreId: true));
});
}
......@@ -585,13 +585,21 @@ void _tests() {
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
actions: <SemanticsAction>[SemanticsAction.tap],
label: r'CANCEL',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
actions: <SemanticsAction>[SemanticsAction.tap],
label: r'OK',
textDirection: TextDirection.ltr,
......
......@@ -165,6 +165,10 @@ class TestScrollPhysics extends ScrollPhysics {
}
void main() {
setUp(() {
debugResetSemanticsIdCounter();
});
testWidgets('TabBar tap selects tab', (WidgetTester tester) async {
final List<String> tabs = <String>['A', 'B', 'C'];
......@@ -1213,19 +1217,25 @@ void main() {
children: <TestSemantics>[
new TestSemantics(
id: 2,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isSelected.index,
label: 'TAB #0\nTab 1 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
),
new TestSemantics(
id: 3,
actions: SemanticsAction.tap.index,
label: 'TAB #1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
),
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 3,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isSelected.index,
label: 'TAB #0\nTab 1 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
),
new TestSemantics(
id: 4,
actions: SemanticsAction.tap.index,
label: 'TAB #1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
),
]
)
],
),
],
......@@ -1458,24 +1468,30 @@ void main() {
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 23,
id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 24,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isSelected.index,
label: 'Semantics override 0\nTab 1 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
),
new TestSemantics(
id: 25,
actions: SemanticsAction.tap.index,
label: 'Semantics override 1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
),
id: 2,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 3,
actions: SemanticsAction.tap.index,
flags: SemanticsFlag.isSelected.index,
label: 'Semantics override 0\nTab 1 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
),
new TestSemantics(
id: 4,
actions: SemanticsAction.tap.index,
label: 'Semantics override 1\nTab 2 of 2',
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
),
]
)
],
),
],
......
......@@ -11,6 +11,10 @@ import '../rendering/rendering_tester.dart';
void main() {
setUp(() {
debugResetSemanticsIdCounter();
});
group('SemanticsNode', () {
const SemanticsTag tag1 = const SemanticsTag('Tag One');
const SemanticsTag tag2 = const SemanticsTag('Tag Two');
......@@ -45,6 +49,7 @@ void main() {
tags.add(tag3);
final SemanticsConfiguration config = new SemanticsConfiguration()
..isSemanticBoundary = true
..isMergingSemanticsOfDescendants = true;
node.updateWith(
......@@ -121,9 +126,9 @@ void main() {
expect(
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
'SemanticsNode#8(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
'├SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
'└SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n',
'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
'├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
'└SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n',
);
});
......@@ -140,16 +145,16 @@ void main() {
);
expect(
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
'├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
'└SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
'├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
'└SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
);
expect(
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
'├SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
'└SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n',
'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
'├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
'└SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n',
);
final SemanticsNode child3 = new SemanticsNode()
......@@ -173,22 +178,22 @@ void main() {
expect(
rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
'SemanticsNode#15(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
'├SemanticsNode#12(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
'│├SemanticsNode#14(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
'│└SemanticsNode#13(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
'├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
'└SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
'├SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
'│├SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
'│└SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
'├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
'└SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
);
expect(
rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
'SemanticsNode#15(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
'├SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
'├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
'└SemanticsNode#12(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
' ├SemanticsNode#13(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
' └SemanticsNode#14(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n',
'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
'├SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
'├SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
'└SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
' ├SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
' └SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n',
);
});
......@@ -196,15 +201,16 @@ void main() {
final SemanticsNode minimalProperties = new SemanticsNode();
expect(
minimalProperties.toStringDeep(),
'SemanticsNode#16(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0))\n',
'SemanticsNode#1(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0))\n',
);
expect(
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n'
'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n'
);
final SemanticsConfiguration config = new SemanticsConfiguration()
..isSemanticBoundary = true
..isMergingSemanticsOfDescendants = true
..onScrollUp = () { }
..onLongPress = () { }
......@@ -220,7 +226,7 @@ void main() {
..updateWith(config: config, childrenInInversePaintOrder: null);
expect(
allProperties.toStringDeep(),
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl)\n',
'SemanticsNode#2(STALE, owner: null, merge boundary ⛔️, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl)\n',
);
expect(
allProperties.getSemanticsData().toString(),
......@@ -232,7 +238,7 @@ void main() {
..transform = new Matrix4.diagonal3(new Vector3(10.0, 10.0, 1.0));
expect(
scaled.toStringDeep(),
'SemanticsNode#18(STALE, owner: null, Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x)\n',
'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x)\n',
);
expect(
scaled.getSemanticsData().toString(),
......@@ -251,7 +257,6 @@ void main() {
expect(config.isSelected, isFalse);
expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isFalse);
expect(config.isFocused, isFalse);
expect(config.isMergingDescendantsIntoOneNode, isFalse);
expect(config.isTextField, isFalse);
expect(config.onShowOnScreen, isNull);
......@@ -274,7 +279,6 @@ void main() {
config.isSelected = true;
config.isBlockingSemanticsOfPreviouslyPaintedNodes = true;
config.isFocused = true;
config.isMergingDescendantsIntoOneNode = true;
config.isTextField = true;
final VoidCallback onShowOnScreen = () { };
......@@ -309,7 +313,6 @@ void main() {
expect(config.isSelected, isTrue);
expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isTrue);
expect(config.isFocused, isTrue);
expect(config.isMergingDescendantsIntoOneNode, isTrue);
expect(config.isTextField, isTrue);
expect(config.onShowOnScreen, same(onShowOnScreen));
......
......@@ -220,73 +220,84 @@ void main() {
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │\n'
' └─child: RenderSemanticsGestureHandler#00000\n'
' └─child: _RenderExcludableScrollSemantics#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ semantic boundary\n'
' │ size: Size(800.0, 600.0)\n'
' │ gestures: vertical scroll\n'
' │\n'
' └─child: RenderPointerListener#00000\n'
' └─child: RenderSemanticsGestureHandler#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ behavior: opaque\n'
' │ listeners: down\n'
' │ gestures: vertical scroll\n'
' │\n'
' └─child: RenderIgnorePointer#00000\n'
' └─child: RenderPointerListener#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ ignoring: false\n'
' │ ignoringSemantics: false\n'
' │ behavior: opaque\n'
' │ listeners: down\n'
' │\n'
' └─child: RenderViewport#00000\n'
' └─child: RenderSemanticsAnnotations#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ axisDirection: down\n'
' │ crossAxisDirection: right\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 0.0, range:\n'
' │ 0.0..39400.0, viewport: 600.0, ScrollableState,\n'
' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n'
' │ IdleScrollActivity#00000, ScrollDirection.idle)\n'
' │ anchor: 0.0\n'
' │\n'
' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n'
' │ currently live children: 0 to 1\n'
' └─child: RenderIgnorePointer#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ ignoring: false\n'
' │ ignoringSemantics: false\n'
' │\n'
' ├─child with index 0: RenderLimitedBox#00000\n'
' │ │ parentData: index=0; layoutOffset=0.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' └─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here
' │ parentData: index=1; layoutOffset=400.0\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │ maxWidth: 400.0\n'
' │ maxHeight: 400.0\n'
' └─child: RenderViewport#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ axisDirection: down\n'
' │ crossAxisDirection: right\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 0.0, range:\n'
' │ 0.0..39400.0, viewport: 600.0, ScrollableState,\n'
' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n'
' │ IdleScrollActivity#00000, ScrollDirection.idle)\n'
' │ anchor: 0.0\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
' size: Size(800.0, 400.0)\n'
' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n'
' │ currently live children: 0 to 1\n'
' │\n'
' ├─child with index 0: RenderLimitedBox#00000\n'
' │ │ parentData: index=0; layoutOffset=0.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' └─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here
' │ parentData: index=1; layoutOffset=400.0\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │ maxWidth: 400.0\n'
' │ maxHeight: 400.0\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
' size: Size(800.0, 400.0)\n'
));
const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
......@@ -324,97 +335,108 @@ void main() {
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │\n'
' └─child: RenderSemanticsGestureHandler#00000\n'
' └─child: _RenderExcludableScrollSemantics#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ semantic boundary\n'
' │ size: Size(800.0, 600.0)\n'
' │ gestures: vertical scroll\n'
' │\n'
' └─child: RenderPointerListener#00000\n'
' └─child: RenderSemanticsGestureHandler#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ behavior: opaque\n'
' │ listeners: down\n'
' │ gestures: vertical scroll\n'
' │\n'
' └─child: RenderIgnorePointer#00000\n'
' └─child: RenderPointerListener#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ ignoring: false\n'
' │ ignoringSemantics: false\n'
' │ behavior: opaque\n'
' │ listeners: down\n'
' │\n'
' └─child: RenderViewport#00000\n'
' └─child: RenderSemanticsAnnotations#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ axisDirection: down\n'
' │ crossAxisDirection: right\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 2000.0,\n'
' │ range: 0.0..39400.0, viewport: 600.0, ScrollableState,\n'
' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n'
' │ IdleScrollActivity#00000, ScrollDirection.idle)\n'
' │ anchor: 0.0\n'
' │\n'
' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n'
' │ currently live children: 5 to 6\n'
' │\n'
' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0
' │ │ parentData: index=5; layoutOffset=2000.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' └─child: RenderIgnorePointer#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ ignoring: false\n'
' │ ignoringSemantics: false\n'
' │\n'
' ├─child with index 6: RenderLimitedBox#00000\n'
' ╎ │ parentData: index=6; layoutOffset=2400.0\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ │ size: Size(800.0, 400.0)\n'
' ╎ │ maxWidth: 400.0\n'
' ╎ │ maxHeight: 400.0\n'
' ╎ │\n'
' ╎ └─child: RenderCustomPaint#00000\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ size: Size(800.0, 400.0)\n'
' ╎\n'
' ╎╌child with index 0 (kept alive offstage): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being offstage
' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ │ size: Size(800.0, 400.0)\n'
' ╎ │ maxWidth: 400.0\n'
' ╎ │ maxHeight: 400.0\n'
' ╎ │\n'
' ╎ └─child: RenderCustomPaint#00000\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ size: Size(800.0, 400.0)\n'
' ╎\n' // <----- dashed line ends here
' └╌child with index 3 (kept alive offstage): RenderLimitedBox#00000\n'
' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │ maxWidth: 400.0\n'
' │ maxHeight: 400.0\n'
' └─child: RenderViewport#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ axisDirection: down\n'
' │ crossAxisDirection: right\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 2000.0,\n'
' │ range: 0.0..39400.0, viewport: 600.0, ScrollableState,\n'
' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics,\n'
' │ IdleScrollActivity#00000, ScrollDirection.idle)\n'
' │ anchor: 0.0\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
' size: Size(800.0, 400.0)\n'
' └─center child: RenderSliverFixedExtentList#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 2000.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true)\n'
' │ currently live children: 5 to 6\n'
' │\n'
' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0
' │ │ parentData: index=5; layoutOffset=2000.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' ├─child with index 6: RenderLimitedBox#00000\n'
' ╎ │ parentData: index=6; layoutOffset=2400.0\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ │ size: Size(800.0, 400.0)\n'
' ╎ │ maxWidth: 400.0\n'
' ╎ │ maxHeight: 400.0\n'
' ╎ │\n'
' ╎ └─child: RenderCustomPaint#00000\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ size: Size(800.0, 400.0)\n'
' ╎\n'
' ╎╌child with index 0 (kept alive offstage): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being offstage
' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ │ size: Size(800.0, 400.0)\n'
' ╎ │ maxWidth: 400.0\n'
' ╎ │ maxHeight: 400.0\n'
' ╎ │\n'
' ╎ └─child: RenderCustomPaint#00000\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ size: Size(800.0, 400.0)\n'
' ╎\n' // <----- dashed line ends here
' └╌child with index 3 (kept alive offstage): RenderLimitedBox#00000\n'
' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │ maxWidth: 400.0\n'
' │ maxHeight: 400.0\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
' size: Size(800.0, 400.0)\n'
));
});
......
......@@ -110,7 +110,6 @@ void main() {
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 2,
rect: TestSemantics.fullScreen,
actions: SemanticsAction.tap.index,
label: 'Dismiss',
......@@ -118,7 +117,7 @@ void main() {
),
]
);
expect(semantics, hasSemantics(expectedSemantics));
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
semantics.dispose();
debugDefaultTargetPlatformOverride = null;
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show SemanticsFlag;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
......@@ -10,6 +12,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
setUp(() {
debugResetSemanticsIdCounter();
});
testWidgets('MergeSemantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
......@@ -19,8 +25,14 @@ void main() {
textDirection: TextDirection.ltr,
child: new Row(
children: <Widget>[
const Text('test1'),
const Text('test2'),
new Semantics(
container: true,
child: const Text('test1'),
),
new Semantics(
container: true,
child: const Text('test2'),
),
],
),
),
......@@ -44,8 +56,14 @@ void main() {
child: new MergeSemantics(
child: new Row(
children: <Widget>[
const Text('test1'),
const Text('test2'),
new Semantics(
container: true,
child: const Text('test1'),
),
new Semantics(
container: true,
child: const Text('test2'),
),
],
),
),
......@@ -71,8 +89,14 @@ void main() {
textDirection: TextDirection.ltr,
child: new Row(
children: <Widget>[
const Text('test1'),
const Text('test2'),
new Semantics(
container: true,
child: const Text('test1'),
),
new Semantics(
container: true,
child: const Text('test2'),
),
],
),
),
......@@ -81,8 +105,8 @@ void main() {
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(id: 4, label: 'test1'),
new TestSemantics.rootChild(id: 5, label: 'test2'),
new TestSemantics.rootChild(id: 6, label: 'test1'),
new TestSemantics.rootChild(id: 7, label: 'test2'),
],
),
ignoreRect: true,
......@@ -91,4 +115,49 @@ void main() {
semantics.dispose();
});
testWidgets('MergeSemantics works if other nodes are implicitly merged into its node', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new MergeSemantics(
child: new Semantics(
selected: true, // this is implicitly merged into the MergeSemantics node
child: new Row(
children: <Widget>[
new Semantics(
container: true,
child: const Text('test1'),
),
new Semantics(
container: true,
child: const Text('test2'),
),
],
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.isSelected,
],
label: 'test1\ntest2',
),
]
),
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
}
......@@ -51,7 +51,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 5,
......@@ -89,7 +89,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 5,
......@@ -112,7 +112,7 @@ void main() {
new TestSemantics(
id: 4,
label: 'Semantics Test with Slivers',
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
),
],
)
......@@ -132,7 +132,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 5,
......@@ -203,7 +203,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 7,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 10,
......@@ -257,7 +257,7 @@ void main() {
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 11,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 17,
......@@ -339,7 +339,7 @@ void main() {
new TestSemantics.rootChild(
id: 18,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 23,
......@@ -371,7 +371,7 @@ void main() {
new TestSemantics(
id: 22,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar',
),
],
......@@ -422,7 +422,7 @@ void main() {
new TestSemantics.rootChild(
id: 24,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 29,
......@@ -454,7 +454,7 @@ void main() {
new TestSemantics(
id: 28,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar'
),
],
......@@ -507,7 +507,7 @@ void main() {
new TestSemantics.rootChild(
id: 30,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 35,
......@@ -540,7 +540,7 @@ void main() {
id: 34,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar'
),
],
......@@ -592,7 +592,7 @@ void main() {
new TestSemantics.rootChild(
id: 36,
rect: TestSemantics.fullScreen,
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
tags: <SemanticsTag>[RenderViewport.useTwoPaneSemantics],
children: <TestSemantics>[
new TestSemantics(
id: 41,
......@@ -625,7 +625,7 @@ void main() {
id: 40,
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
tags: <SemanticsTag>[RenderViewport.excludeFromScrolling],
label: 'AppBar'
),
],
......
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