Unverified Commit e4c8f1b9 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

3D SemanticsTree (#25670)

parent 14faa8d9
02905560cf2c2132a7adea10943001f29325f3be
ecbdcf8d56d3aba0212c2b1b923466023ef09b4e
......@@ -524,13 +524,15 @@ class _AppBarState extends State<AppBar> {
return Semantics(
container: true,
explicitChildNodes: true,
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: overlayStyle,
child: Material(
color: widget.backgroundColor ?? themeData.primaryColor,
elevation: widget.elevation,
child: appBar,
child: Semantics(
explicitChildNodes: true,
child: appBar,
),
),
),
);
......
......@@ -567,43 +567,30 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
break;
}
return Semantics(
container: true,
explicitChildNodes: true,
child: Stack(
children: <Widget>[
Positioned.fill(
child: Material( // Casts shadow.
elevation: 8.0,
color: backgroundColor,
child: Material(
elevation: 8.0,
color: backgroundColor,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
child: CustomPaint(
painter: _RadialPainter(
circles: _circles.toList(),
textDirection: Directionality.of(context),
),
),
ConstrainedBox(
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
child: Stack(
children: <Widget>[
Positioned.fill(
child: CustomPaint(
painter: _RadialPainter(
circles: _circles.toList(),
textDirection: Directionality.of(context),
),
),
),
Material( // Splashes.
type: MaterialType.transparency,
child: Padding(
padding: EdgeInsets.only(bottom: additionalBottomPadding),
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: _createContainer(_createTiles()),
),
),
child: Material( // Splashes.
type: MaterialType.transparency,
child: Padding(
padding: EdgeInsets.only(bottom: additionalBottomPadding),
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: _createContainer(_createTiles()),
),
],
),
),
),
],
),
),
);
}
......
......@@ -135,7 +135,6 @@ class Card extends StatelessWidget {
Widget build(BuildContext context) {
return Semantics(
container: semanticContainer,
explicitChildNodes: !semanticContainer,
child: Container(
margin: margin ?? const EdgeInsets.all(4.0),
child: Material(
......@@ -146,7 +145,10 @@ class Card extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
clipBehavior: clipBehavior,
child: child,
child: Semantics(
explicitChildNodes: !semanticContainer,
child: child,
),
),
),
);
......
......@@ -311,7 +311,11 @@ class AlertDialog extends StatelessWidget {
padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
child: DefaultTextStyle(
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.title,
child: Semantics(child: title, namesRoute: true),
child: Semantics(
child: title,
namesRoute: true,
container: true,
),
),
));
} else {
......
......@@ -2406,6 +2406,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
final SemanticsNode node = interestingFragment.compileChildren(
parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
parentPaintClipRect: _semantics?.parentPaintClipRect,
elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0,
).single;
// Fragment only wants to add this node's SemanticsNode to the parent.
assert(interestingFragment.config == null && node == _semantics);
......@@ -3193,9 +3194,19 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
final List<RenderObject> _ancestorChain;
/// The children to be added to the parent.
///
/// See also:
///
/// * [SemanticsNode.parentSemanticsClipRect] for the source and definition
/// of the `parentSemanticsClipRect` argument.
/// * [SemanticsNode.parentPaintClipRect] for the source and definition
// of the `parentPaintClipRect` argument.
/// * [SemanticsNode.elevationAdjustment] for the source and definition
// of the `elevationAdjustment` argument.
Iterable<SemanticsNode> compileChildren({
@required Rect parentSemanticsClipRect,
@required Rect parentPaintClipRect
@required Rect parentPaintClipRect,
@required double elevationAdjustment,
});
/// The [SemanticsConfiguration] the child wants to merge into the parent's
......@@ -3264,11 +3275,12 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
}) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
@override
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect, double elevationAdjustment}) sync* {
assert(_tagsForChildren == null || _tagsForChildren.isEmpty);
assert(parentSemanticsClipRect == null);
assert(parentPaintClipRect == null);
assert(_ancestorChain.length == 1);
assert(elevationAdjustment == 0.0);
owner._semantics ??= SemanticsNode.root(
showOnScreen: owner.showOnScreen,
......@@ -3287,6 +3299,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
children.addAll(fragment.compileChildren(
parentSemanticsClipRect: parentSemanticsClipRect,
parentPaintClipRect: parentPaintClipRect,
elevationAdjustment: 0.0,
));
}
node.updateWith(config: null, childrenInInversePaintOrder: children);
......@@ -3352,13 +3365,20 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
@override
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect, double elevationAdjustment}) sync* {
if (!_isExplicit) {
owner._semantics = null;
for (_InterestingSemanticsFragment fragment in _children) {
assert(_ancestorChain.first == fragment._ancestorChain.last);
fragment._ancestorChain.addAll(_ancestorChain.sublist(1));
yield* fragment.compileChildren(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect);
yield* fragment.compileChildren(
parentSemanticsClipRect: parentSemanticsClipRect,
parentPaintClipRect: parentPaintClipRect,
// The fragment is not explicit, its elevation has been absorbed by
// the parent config (as thickness). We still need to make sure that
// its children are placed at the elevation dictated by this config.
elevationAdjustment: elevationAdjustment + _config.elevation,
);
}
return;
}
......@@ -3375,6 +3395,12 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
..isMergedIntoParent = _mergeIntoParent
..tags = _tagsForChildren;
node.elevationAdjustment = elevationAdjustment;
if (elevationAdjustment != 0.0) {
_ensureConfigIsWritable();
_config.elevation += elevationAdjustment;
}
if (geometry != null) {
assert(_needsGeometryUpdate);
node
......@@ -3389,8 +3415,13 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
}
final List<SemanticsNode> children = <SemanticsNode>[];
for (_InterestingSemanticsFragment fragment in _children)
children.addAll(fragment.compileChildren(parentSemanticsClipRect: node.parentSemanticsClipRect, parentPaintClipRect: node.parentPaintClipRect));
for (_InterestingSemanticsFragment fragment in _children) {
children.addAll(fragment.compileChildren(
parentSemanticsClipRect: node.parentSemanticsClipRect,
parentPaintClipRect: node.parentPaintClipRect,
elevationAdjustment: 0.0,
));
}
if (_config.isSemanticBoundary) {
owner.assembleSemanticsNode(node, _config, children);
......@@ -3457,7 +3488,7 @@ class _AbortingSemanticsFragment extends _InterestingSemanticsFragment {
}
@override
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* {
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect, double elevationAdjustment}) sync* {
yield owner._semantics;
}
......
......@@ -1571,6 +1571,12 @@ abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> {
@override
bool get alwaysNeedsCompositing => _elevation != 0.0 && defaultTargetPlatform == TargetPlatform.fuchsia;
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.elevation = elevation;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
......
......@@ -187,6 +187,8 @@ class SemanticsData extends Diagnosticable {
@required this.hint,
@required this.textDirection,
@required this.rect,
@required this.elevation,
@required this.thickness,
@required this.textSelection,
@required this.scrollIndex,
@required this.scrollChildCount,
......@@ -305,6 +307,21 @@ class SemanticsData extends Diagnosticable {
/// parent).
final Matrix4 transform;
/// The elevation of this node relative to the parent semantics node.
///
/// See also:
///
/// * [SemanticsConfiguration.elevation] for a detailed discussion regarding
/// elevation and semantics.
final double elevation;
/// The extent of this node along the z-axis beyond its [elevation]
///
/// See also:
///
/// * [SemanticsConfiguration.thickness] for a more detailed definition.
final double thickness;
/// The identifiers for the custom semantics actions and standard action
/// overrides for this node.
///
......@@ -329,6 +346,8 @@ class SemanticsData extends Diagnosticable {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false));
properties.add(TransformProperty('transform', transform, showName: false, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
final List<String> actionSummary = <String>[];
for (SemanticsAction action in SemanticsAction.values.values) {
if ((actions & action.index) != 0)
......@@ -383,6 +402,8 @@ class SemanticsData extends Diagnosticable {
&& typedOther.scrollExtentMax == scrollExtentMax
&& typedOther.scrollExtentMin == scrollExtentMin
&& typedOther.transform == transform
&& typedOther.elevation == elevation
&& typedOther.thickness == thickness
&& _sortedListsEqual(typedOther.customSemanticsActionIds, customSemanticsActionIds);
}
......@@ -406,6 +427,8 @@ class SemanticsData extends Diagnosticable {
scrollExtentMax,
scrollExtentMin,
transform,
elevation,
thickness,
ui.hashList(customSemanticsActionIds),
);
}
......@@ -1143,6 +1166,23 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// If this rect is null [parentSemanticsClipRect] also has to be null.
Rect parentPaintClipRect;
/// The elevation adjustment that the parent imposes on this node.
///
/// The [elevation] property is relative to the elevation of the parent
/// [SemanticsNode]. However, as [SemanticsConfiguration]s from various
/// ascending [RenderObjects] are merged into each other to form that
/// [SemanticsNode] the parent’s elevation may change. This requires an
/// adjustment of the child’s relative elevation which is represented by this
/// value.
///
/// The value is rarely accessed directly. Instead, for most use cases the
/// [elevation] value should be used, which includes this adjustment.
///
/// See also:
///
/// * [elevation], the actual elevation of this [SemanticsNode].
double elevationAdjustment;
/// The index of this node within the parent's list of semantic children.
///
/// This includes all semantic nodes, not just those currently in the
......@@ -1409,6 +1449,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
return _label != config.label ||
_hint != config.hint ||
_elevation != config.elevation ||
_thickness != config.thickness ||
_decreasedValue != config.decreasedValue ||
_value != config.value ||
_increasedValue != config.increasedValue ||
......@@ -1483,6 +1525,73 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
String get hint => _hint;
String _hint = _kEmptyConfig.hint;
/// The elevation along the z-axis at which the [rect] of this [SemanticsNode]
/// is located above its parent.
///
/// The value is relative to the parent's [elevation]. The sum of the
/// [elevation]s of all ancestor node plus this value determines the absolute
/// elevation of this [SemanticsNode].
///
/// See also:
///
/// * [thickness], which describes how much space in z-direction this
/// [SemanticsNode] occupies starting at this [elevation].
/// * [elevationAdjustment], which has been used to calculate this value.
double get elevation => _elevation;
double _elevation = _kEmptyConfig.elevation;
/// Describes how much space the [SemanticsNode] takes up along the z-axis.
///
/// A [SemanticsNode] represents multiple [RenderObject]s, which can be
/// located at various elevations in 3D. The [thickness] is the difference
/// between the absolute elevations of the lowest and highest [RenderObject]
/// represented by this [SemanticsNode]. In other words, the thickness
/// describes how high the box is that this [SemanticsNode] occupies in three
/// dimensional space. The two other dimensions are defined by [rect].
///
/// ## Sample Code
///
/// The following code stacks three [PhysicalModel]s on top of each other
/// separated by none-zero elevations:
///
/// ```dart
/// PhysicalModel( // A
/// color: Colors.amber,
/// elevation: 0.0,
/// child: Semantics(
/// explicitChildNodes: true,
/// child: PhysicalModel( // B
/// color: Colors.brown,
/// elevation: 5.0,
/// child: PhysicalModel( // C
/// color: Colors.cyan,
/// elevation: 10.0,
/// child: Placeholder(),
/// ),
/// ),
/// ),
/// );
/// ```
///
/// [PhysicalModel] C is elevated 10.0 above [PhysicalModel] B, which in turn
/// is elevated 5.0 above [PhysicalModel] A. The side view of this
/// constellation looks as follows:
///
/// ![A diagram illustrating the elevations of three PhysicalModels and their
/// corresponding SemanticsNodes.](https://flutter.github.io/assets-for-api-docs/assets/semantics/SemanticsNode.thickness.png)
///
/// In this example the [RenderObject]s for [PhysicalModel] C and B share one
/// [SemanticsNode] Y. Given the elevations of those [RenderObject]s, this
/// [SemanticsNode] has a [thickness] of 10.0 and an elevation of 5.0 over
/// its parent [SemanticsNode] X.
///
/// See also:
///
/// * [elevation], which describes the elevation of the box defined by
/// [thickness] and [rect] relative to the parent of this [SemanticsNode].
double get thickness => _thickness;
double _thickness = _kEmptyConfig.thickness;
/// Provides hint values which override the default hints on supported
/// platforms.
SemanticsHintOverrides get hintOverrides => _hintOverrides;
......@@ -1581,6 +1690,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_increasedValue = config.increasedValue;
_hint = config.hint;
_hintOverrides = config.hintOverrides;
_elevation = config.elevation;
_thickness = config.thickness;
_flags = config._flags;
_textDirection = config.textDirection;
_sortKey = config.sortKey;
......@@ -1629,6 +1740,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
double scrollPosition = _scrollPosition;
double scrollExtentMax = _scrollExtentMax;
double scrollExtentMin = _scrollExtentMin;
final double elevation = _elevation;
double thickness = _thickness;
final Set<int> customSemanticsActionIds = Set<int>();
for (CustomSemanticsAction action in _customSemanticsActions.keys)
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
......@@ -1703,6 +1816,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
otherString: node._hint,
otherTextDirection: node._textDirection,
);
thickness = math.max(thickness, node._thickness + node._elevation);
return true;
});
}
......@@ -1718,6 +1834,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
textDirection: textDirection,
rect: rect,
transform: transform,
elevation: elevation,
thickness: thickness,
tags: mergedTags,
textSelection: textSelection,
scrollChildCount: scrollChildCount,
......@@ -1786,6 +1904,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
scrollExtentMax: data.scrollExtentMax != null ? data.scrollExtentMax : double.nan,
scrollExtentMin: data.scrollExtentMin != null ? data.scrollExtentMin : double.nan,
transform: data.transform?.storage ?? _kIdentityTransform,
elevation: data.elevation,
thickness: data.thickness,
childrenInTraversalOrder: childrenInTraversalOrder,
childrenInHitTestOrder: childrenInHitTestOrder,
additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
......@@ -1923,6 +2043,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
properties.add(DoubleProperty('thicknes', thickness, defaultValue: 0.0));
}
/// Returns a string representation of this node and its descendants.
......@@ -3115,6 +3237,36 @@ class SemanticsConfiguration {
_hasBeenAnnotated = true;
}
/// The elevation in z-direction at which the owning [RenderObject] is
/// located relative to its parent.
double get elevation => _elevation;
double _elevation = 0.0;
set elevation(double value) {
assert(value != null && value >= 0.0);
if (value == _elevation) {
return;
}
_elevation = value;
_hasBeenAnnotated = true;
}
/// The extend that the owning [RenderObject] occupies in z-direction starting
/// at [elevation].
///
/// It's extremely rare to set this value directly. Instead, it is calculated
/// implicitly when other [SemanticsConfiguration]s are merged into this one
/// via [absorb].
double get thickness => _thickness;
double _thickness = 0.0;
set thickness(double value) {
assert(value != null && value >= 0.0);
if (value == _thickness) {
return;
}
_thickness = value;
_hasBeenAnnotated = true;
}
/// Whether the semantics node is the root of a subtree for which values
/// should be announced.
///
......@@ -3436,55 +3588,60 @@ class SemanticsConfiguration {
return true;
}
/// Absorb the semantic information from `other` into this configuration.
/// Absorb the semantic information from `child` into this configuration.
///
/// This adds the semantic information of both configurations and saves the
/// result in this configuration.
///
/// The [RenderObject] owning the `child` configuration must be a descendant
/// of the [RenderObject] that owns this configuration.
///
/// Only configurations that have [explicitChildNodes] set to false can
/// absorb other configurations and it is recommended to only absorb compatible
/// configurations as determined by [isCompatibleWith].
void absorb(SemanticsConfiguration other) {
void absorb(SemanticsConfiguration child) {
assert(!explicitChildNodes);
if (!other.hasBeenAnnotated)
if (!child.hasBeenAnnotated)
return;
_actions.addAll(other._actions);
_customSemanticsActions.addAll(other._customSemanticsActions);
_actionsAsBits |= other._actionsAsBits;
_flags |= other._flags;
_textSelection ??= other._textSelection;
_scrollPosition ??= other._scrollPosition;
_scrollExtentMax ??= other._scrollExtentMax;
_scrollExtentMin ??= other._scrollExtentMin;
_hintOverrides ??= other._hintOverrides;
_indexInParent ??= other.indexInParent;
_scrollIndex ??= other._scrollIndex;
_scrollChildCount ??= other._scrollChildCount;
textDirection ??= other.textDirection;
_sortKey ??= other._sortKey;
_actions.addAll(child._actions);
_customSemanticsActions.addAll(child._customSemanticsActions);
_actionsAsBits |= child._actionsAsBits;
_flags |= child._flags;
_textSelection ??= child._textSelection;
_scrollPosition ??= child._scrollPosition;
_scrollExtentMax ??= child._scrollExtentMax;
_scrollExtentMin ??= child._scrollExtentMin;
_hintOverrides ??= child._hintOverrides;
_indexInParent ??= child.indexInParent;
_scrollIndex ??= child._scrollIndex;
_scrollChildCount ??= child._scrollChildCount;
textDirection ??= child.textDirection;
_sortKey ??= child._sortKey;
_label = _concatStrings(
thisString: _label,
thisTextDirection: textDirection,
otherString: other._label,
otherTextDirection: other.textDirection,
otherString: child._label,
otherTextDirection: child.textDirection,
);
if (_decreasedValue == '' || _decreasedValue == null)
_decreasedValue = other._decreasedValue;
_decreasedValue = child._decreasedValue;
if (_value == '' || _value == null)
_value = other._value;
_value = child._value;
if (_increasedValue == '' || _increasedValue == null)
_increasedValue = other._increasedValue;
_increasedValue = child._increasedValue;
_hint = _concatStrings(
thisString: _hint,
thisTextDirection: textDirection,
otherString: other._hint,
otherTextDirection: other.textDirection,
otherString: child._hint,
otherTextDirection: child.textDirection,
);
_hasBeenAnnotated = _hasBeenAnnotated || other._hasBeenAnnotated;
_thickness = math.max(_thickness, child._thickness + child._elevation);
_hasBeenAnnotated = _hasBeenAnnotated || child._hasBeenAnnotated;
}
/// Returns an exact copy of this configuration.
......@@ -3503,6 +3660,8 @@ class SemanticsConfiguration {
.._decreasedValue = _decreasedValue
.._hint = _hint
.._hintOverrides = _hintOverrides
.._elevation = _elevation
.._thickness = _thickness
.._flags = _flags
.._tagsForChildren = _tagsForChildren
.._textSelection = _textSelection
......
......@@ -40,27 +40,34 @@ void main() {
children: <TestSemantics>[
TestSemantics(
id: 1,
label: 'I am text!',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 2,
label: 'Moar text!!1',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 3,
label: 'Button',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
elevation: 1.0,
thickness: 0.0,
children: <TestSemantics>[
TestSemantics(
id: 2,
label: 'I am text!',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 3,
label: 'Moar text!!1',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 4,
label: 'Button',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
),
],
),
)
],
),
ignoreTransform: true,
......
......@@ -425,23 +425,23 @@ void _tests() {
final SemanticsTester semantics = SemanticsTester(tester);
await preparePicker(tester, (Future<DateTime> date) async {
final TestSemantics expected = TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.scopesRoute,
],
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isSelected],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Fri, Jan 15',
textDirection: TextDirection.ltr,
),
TestSemantics(
elevation: 24.0,
thickness: 0.0,
children: <TestSemantics>[
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isSelected],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Fri, Jan 15',
textDirection: TextDirection.ltr,
),
TestSemantics(
children: <TestSemantics>[
TestSemantics(
......@@ -452,160 +452,164 @@ void _tests() {
TestSemantics(
children: <TestSemantics>[
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '1, Friday, January 1, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '2, Saturday, January 2, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '3, Sunday, January 3, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '4, Monday, January 4, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '5, Tuesday, January 5, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '6, Wednesday, January 6, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '7, Thursday, January 7, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '8, Friday, January 8, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '9, Saturday, January 9, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '10, Sunday, January 10, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '11, Monday, January 11, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '12, Tuesday, January 12, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '13, Wednesday, January 13, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '14, Thursday, January 14, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isSelected],
actions: <SemanticsAction>[SemanticsAction.tap],
label: '15, Friday, January 15, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '16, Saturday, January 16, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '17, Sunday, January 17, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '18, Monday, January 18, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '19, Tuesday, January 19, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '20, Wednesday, January 20, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '21, Thursday, January 21, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '22, Friday, January 22, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '23, Saturday, January 23, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '24, Sunday, January 24, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '25, Monday, January 25, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '26, Tuesday, January 26, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '27, Wednesday, January 27, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '28, Thursday, January 28, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '29, Friday, January 29, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '30, Saturday, January 30, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '31, Sunday, January 31, 2016',
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '1, Friday, January 1, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '2, Saturday, January 2, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '3, Sunday, January 3, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '4, Monday, January 4, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '5, Tuesday, January 5, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '6, Wednesday, January 6, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '7, Thursday, January 7, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '8, Friday, January 8, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '9, Saturday, January 9, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '10, Sunday, January 10, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '11, Monday, January 11, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '12, Tuesday, January 12, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '13, Wednesday, January 13, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '14, Thursday, January 14, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isSelected],
actions: <SemanticsAction>[SemanticsAction.tap],
label: '15, Friday, January 15, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '16, Saturday, January 16, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '17, Sunday, January 17, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '18, Monday, January 18, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '19, Tuesday, January 19, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '20, Wednesday, January 20, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '21, Thursday, January 21, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '22, Friday, January 22, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '23, Saturday, January 23, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '24, Sunday, January 24, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '25, Monday, January 25, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '26, Tuesday, January 26, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '27, Wednesday, January 27, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '28, Thursday, January 28, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '29, Friday, January 29, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '30, Saturday, January 30, 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: '31, Sunday, January 31, 2016',
textDirection: TextDirection.ltr,
),
],
),
],
),
......@@ -615,32 +619,32 @@ void _tests() {
),
],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Previous month December 2015',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Next month February 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'CANCEL',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'OK',
textDirection: TextDirection.ltr,
),
],
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Previous month December 2015',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Next month February 2016',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'CANCEL',
textDirection: TextDirection.ltr,
),
TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isButton, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'OK',
textDirection: TextDirection.ltr,
),
],
);
......
// Copyright 2018 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 '../widgets/semantics_tester.dart';
void main() {
testWidgets('SemanticsNodes overlapping in z', (WidgetTester tester) async {
// Cards are semantic boundaries that always own their own SemanticNode,
// PhysicalModels merge their semantics information into parent.
//
// Side view of the widget tree:
//
// Card('abs. elevation: 30') ---------------
// | 8 ----------- Card('abs. elevation 25')
// Card('abs. elevation: 22') --------------- |
// | 7 |
// PhysicalModel('abs. elevation: 15') --------------- | 15
// | 5 |
// --------------------------------------- Card('abs. elevation: 10')
// | 10
// |
// --------------------------------------- 'ground'
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Text('ground'),
Card(
elevation: 10.0,
child: Column(
children: <Widget>[
const Text('absolute elevation: 10'),
PhysicalModel(
elevation: 5.0,
color: Colors.black,
child: Column(
children: <Widget>[
const Text('absolute elevation: 15'),
Card(
elevation: 7.0,
child: Column(
children: const <Widget>[
Text('absolute elevation: 22'),
Card(
elevation: 8.0,
child: Text('absolute elevation: 30'),
)
],
),
)
],
),
),
const Card(
elevation: 15.0,
child: Text('absolute elevation: 25'),
)
],
),
)
],
),
));
final SemanticsNode ground = tester.getSemantics(find.text('ground'));
expect(ground.thickness, 0.0);
expect(ground.elevation, 0.0);
expect(ground.label, 'ground');
final SemanticsNode elevation10 = tester.getSemantics(find.text('absolute elevation: 10'));
final SemanticsNode elevation15 = tester.getSemantics(find.text('absolute elevation: 15'));
expect(elevation10, same(elevation15)); // configs got merged into each other.
expect(elevation10.thickness, 15.0);
expect(elevation10.elevation, 0.0);
expect(elevation10.label, 'absolute elevation: 10\nabsolute elevation: 15');
final SemanticsNode elevation22 = tester.getSemantics(find.text('absolute elevation: 22'));
expect(elevation22.thickness, 7.0);
expect(elevation22.elevation, 15.0);
expect(elevation22.label, 'absolute elevation: 22');
final SemanticsNode elevation25 = tester.getSemantics(find.text('absolute elevation: 25'));
expect(elevation25.thickness, 15.0);
expect(elevation25.elevation, 10.0);
expect(elevation22.label, 'absolute elevation: 22');
final SemanticsNode elevation30 = tester.getSemantics(find.text('absolute elevation: 30'));
expect(elevation30.thickness, 8.0);
expect(elevation30.elevation, 7.0);
expect(elevation30.label, 'absolute elevation: 30');
semantics.dispose();
});
testWidgets('SemanticsNodes overlapping in z with switched children', (WidgetTester tester) async {
// Same as 'SemanticsNodes overlapping in z', but the order of children
// is reversed
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Text('ground'),
Card(
elevation: 10.0,
child: Column(
children: <Widget>[
const Card(
elevation: 15.0,
child: Text('absolute elevation: 25'),
),
PhysicalModel(
elevation: 5.0,
color: Colors.black,
child: Column(
children: <Widget>[
const Text('absolute elevation: 15'),
Card(
elevation: 7.0,
child: Column(
children: const <Widget>[
Text('absolute elevation: 22'),
Card(
elevation: 8.0,
child: Text('absolute elevation: 30'),
)
],
),
)
],
),
),
const Text('absolute elevation: 10'),
],
),
)
],
),
));
final SemanticsNode ground = tester.getSemantics(find.text('ground'));
expect(ground.thickness, 0.0);
expect(ground.elevation, 0.0);
expect(ground.label, 'ground');
final SemanticsNode elevation10 = tester.getSemantics(find.text('absolute elevation: 10'));
final SemanticsNode elevation15 = tester.getSemantics(find.text('absolute elevation: 15'));
expect(elevation10, same(elevation15)); // configs got merged into each other.
expect(elevation10.thickness, 15.0);
expect(elevation10.elevation, 0.0);
expect(elevation10.label, 'absolute elevation: 15\nabsolute elevation: 10');
final SemanticsNode elevation22 = tester.getSemantics(find.text('absolute elevation: 22'));
expect(elevation22.thickness, 7.0);
expect(elevation22.elevation, 15.0);
expect(elevation22.label, 'absolute elevation: 22');
final SemanticsNode elevation25 = tester.getSemantics(find.text('absolute elevation: 25'));
expect(elevation25.thickness, 15.0);
expect(elevation25.elevation, 10.0);
expect(elevation22.label, 'absolute elevation: 22');
final SemanticsNode elevation30 = tester.getSemantics(find.text('absolute elevation: 30'));
expect(elevation30.thickness, 8.0);
expect(elevation30.elevation, 7.0);
expect(elevation30.label, 'absolute elevation: 30');
semantics.dispose();
});
testWidgets('single node thickness', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(const MaterialApp(
home: Center(
child: Material(
elevation: 24.0,
child: Text('Hello'),
)
)
));
final SemanticsNode node = tester.getSemantics(find.text('Hello'));
expect(node.thickness, 0.0);
expect(node.elevation, 24.0);
expect(node.label, 'Hello');
semantics.dispose();
});
testWidgets('force-merge', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(MaterialApp(
home: Card(
elevation: 10.0,
child: Column(
children: <Widget>[
const Text('abs. elevation: 10.0'),
MergeSemantics(
child: Semantics(
explicitChildNodes: true, // just to be sure that it's going to be an explicit merge
child: Column(
children: const <Widget>[
Card(
elevation: 15.0,
child: Text('abs. elevation 25.0'),
),
Card(
elevation: 5.0,
child: Text('abs. elevation 15.0'),
),
],
),
),
),
],
)
)
));
final SemanticsNode elevation10 = tester.getSemantics(find.text('abs. elevation: 10.0'));
expect(elevation10.thickness, 10.0);
expect(elevation10.elevation, 0.0);
expect(elevation10.label, 'abs. elevation: 10.0');
expect(elevation10.childrenCount, 1);
// TODO(goderbauer): remove awkward workaround when accessing force-merged
// SemanticsData becomes easier, https://github.com/flutter/flutter/issues/25669
SemanticsData mergedChildData;
elevation10.visitChildren((SemanticsNode child) {
expect(mergedChildData, isNull);
mergedChildData = child.getSemanticsData();
return true;
});
expect(mergedChildData.thickness, 15.0);
expect(mergedChildData.elevation, 10.0);
expect(mergedChildData.label, 'abs. elevation 25.0\nabs. elevation 15.0');
semantics.dispose();
});
testWidgets('force-merge with inversed children', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(MaterialApp(
home: Card(
elevation: 10.0,
child: Column(
children: <Widget>[
const Text('abs. elevation: 10.0'),
MergeSemantics(
child: Semantics(
explicitChildNodes: true, // just to be sure that it's going to be an explicit merge
child: Column(
children: const <Widget>[
Card(
elevation: 5.0,
child: Text('abs. elevation 15.0'),
),
Card(
elevation: 15.0,
child: Text('abs. elevation 25.0'),
),
],
),
),
),
],
)
)
));
final SemanticsNode elevation10 = tester.getSemantics(find.text('abs. elevation: 10.0'));
expect(elevation10.thickness, 10.0);
expect(elevation10.elevation, 0.0);
expect(elevation10.label, 'abs. elevation: 10.0');
expect(elevation10.childrenCount, 1);
// TODO(goderbauer): remove awkward workaround when accessing force-merged
// SemanticsData becomes easier, https://github.com/flutter/flutter/issues/25669
SemanticsData mergedChildData;
elevation10.visitChildren((SemanticsNode child) {
expect(mergedChildData, isNull);
mergedChildData = child.getSemanticsData();
return true;
});
expect(mergedChildData.thickness, 15.0);
expect(mergedChildData.elevation, 10.0);
expect(mergedChildData.label, 'abs. elevation 15.0\nabs. elevation 25.0');
semantics.dispose();
});
}
......@@ -353,6 +353,8 @@ void main() {
' scrollExtentMin: null\n'
' scrollPosition: null\n'
' scrollExtentMax: null\n'
' elevation: 0.0\n'
' thicknes: 0.0\n'
);
final SemanticsConfiguration config = SemanticsConfiguration()
......@@ -445,6 +447,8 @@ void main() {
' scrollExtentMin: null\n'
' scrollPosition: null\n'
' scrollExtentMax: null\n'
' elevation: 0.0\n'
' thicknes: 0.0\n'
);
});
......
......@@ -46,6 +46,8 @@ class TestSemantics {
this.textDirection,
this.rect,
this.transform,
this.elevation,
this.thickness,
this.textSelection,
this.children = const <TestSemantics>[],
this.scrollIndex,
......@@ -87,6 +89,8 @@ class TestSemantics {
assert(value != null),
assert(hint != null),
rect = TestSemantics.rootRect,
elevation = 0.0,
thickness = 0.0,
assert(children != null),
tags = tags?.toSet() ?? Set<SemanticsTag>();
......@@ -111,6 +115,8 @@ class TestSemantics {
this.textDirection,
this.rect,
Matrix4 transform,
this.elevation,
this.thickness,
this.textSelection,
this.children = const <TestSemantics>[],
this.scrollIndex,
......@@ -206,6 +212,21 @@ class TestSemantics {
/// parent).
final Matrix4 transform;
/// The elevation of this node reative to the parent node.
///
/// See also:
///
/// * [SemanticsConfiguration.elevation] for a detailed discussion regarding
/// elevation and semantics.
final double elevation;
/// The extend that this node occupies in z-direction starting at [elevation].
///
/// See also:
///
/// * [SemanticsConfiguration.thickness] for a more detailed definition.
final double thickness;
/// The index of the first visible semantic node within a scrollable.
final int scrollIndex;
......@@ -279,6 +300,12 @@ class TestSemantics {
return fail('expected node id $id to have rect $rect but found rect ${nodeData.rect}.');
if (!ignoreTransform && transform != nodeData.transform)
return fail('expected node id $id to have transform $transform but found transform:\n${nodeData.transform}.');
if (elevation != null && elevation != nodeData.elevation) {
return fail('expected node id $id to have elevation $elevation but found elevation:\n${nodeData.elevation}.');
}
if (thickness != null && thickness != nodeData.thickness) {
return fail('expected node id $id to have thickness $thickness but found thickness:\n${nodeData.thickness}.');
}
if (textSelection?.baseOffset != nodeData.textSelection?.baseOffset || textSelection?.extentOffset != nodeData.textSelection?.extentOffset) {
return fail('expected node id $id to have textSelection [${textSelection?.baseOffset}, ${textSelection?.end}] but found: [${nodeData.textSelection?.baseOffset}, ${nodeData.textSelection?.extentOffset}].');
}
......@@ -346,6 +373,10 @@ class TestSemantics {
buf.writeln('$indent rect: $rect,');
if (transform != null)
buf.writeln('$indent transform:\n${transform.toString().trim().split('\n').map<String>((String line) => '$indent $line').join('\n')},');
if (elevation != null)
buf.writeln('$indent elevation: $elevation,');
if (thickness != null)
buf.writeln('$indent thickness: $thickness,');
buf.writeln('$indent children: <TestSemantics>[');
for (TestSemantics child in children) {
buf.writeln('${child.toString(indentAmount + 2)},');
......
......@@ -355,6 +355,8 @@ Matcher matchesSemantics({
TextDirection textDirection,
Rect rect,
Size size,
double elevation,
double thickness,
// Flags //
bool hasCheckedState = false,
bool isChecked = false,
......@@ -503,6 +505,8 @@ Matcher matchesSemantics({
textDirection: textDirection,
rect: rect,
size: size,
elevation: elevation,
thickness: thickness,
customActions: customActions,
hintOverrides: hintOverrides,
children: children,
......@@ -1685,6 +1689,8 @@ class _MatchesSemanticsData extends Matcher {
this.textDirection,
this.rect,
this.size,
this.elevation,
this.thickness,
this.customActions,
this.hintOverrides,
this.children,
......@@ -1702,6 +1708,8 @@ class _MatchesSemanticsData extends Matcher {
final TextDirection textDirection;
final Rect rect;
final Size size;
final double elevation;
final double thickness;
final List<Matcher> children;
@override
......@@ -1727,6 +1735,10 @@ class _MatchesSemanticsData extends Matcher {
description.add(' with rect: $rect');
if (size != null)
description.add(' with size: $size');
if (elevation != null)
description.add(' with elevation: $elevation');
if (thickness != null)
description.add(' with thickness: $thickness');
if (customActions != null)
description.add(' with custom actions: $customActions');
if (hintOverrides != null)
......@@ -1763,6 +1775,10 @@ class _MatchesSemanticsData extends Matcher {
return failWithDescription(matchState, 'rect was: ${data.rect}');
if (size != null && size != data.rect.size)
return failWithDescription(matchState, 'size was: ${data.rect.size}');
if (elevation != null && elevation != data.elevation)
return failWithDescription(matchState, 'elevation was: ${data.elevation}');
if (thickness != null && thickness != data.thickness)
return failWithDescription(matchState, 'thickness was: ${data.thickness}');
if (actions != null) {
int actionBits = 0;
for (SemanticsAction action in actions)
......
......@@ -494,6 +494,8 @@ void main() {
hint: 'e',
textDirection: TextDirection.ltr,
rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null,
scrollIndex: null,
scrollChildCount: null,
......@@ -508,6 +510,8 @@ void main() {
expect(node, matchesSemantics(
rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
size: const Size(10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
/* Flags */
hasCheckedState: true,
isChecked: true,
......
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