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

3D SemanticsTree (#25670)

parent 14faa8d9
02905560cf2c2132a7adea10943001f29325f3be ecbdcf8d56d3aba0212c2b1b923466023ef09b4e
...@@ -524,15 +524,17 @@ class _AppBarState extends State<AppBar> { ...@@ -524,15 +524,17 @@ class _AppBarState extends State<AppBar> {
return Semantics( return Semantics(
container: true, container: true,
explicitChildNodes: true,
child: AnnotatedRegion<SystemUiOverlayStyle>( child: AnnotatedRegion<SystemUiOverlayStyle>(
value: overlayStyle, value: overlayStyle,
child: Material( child: Material(
color: widget.backgroundColor ?? themeData.primaryColor, color: widget.backgroundColor ?? themeData.primaryColor,
elevation: widget.elevation, elevation: widget.elevation,
child: Semantics(
explicitChildNodes: true,
child: appBar, child: appBar,
), ),
), ),
),
); );
} }
} }
......
...@@ -567,29 +567,18 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr ...@@ -567,29 +567,18 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
break; break;
} }
return Semantics( return Semantics(
container: true,
explicitChildNodes: true, explicitChildNodes: true,
child: Stack( child: Material(
children: <Widget>[
Positioned.fill(
child: Material( // Casts shadow.
elevation: 8.0, elevation: 8.0,
color: backgroundColor, color: backgroundColor,
), child: ConstrainedBox(
),
ConstrainedBox(
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding), constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
child: Stack(
children: <Widget>[
Positioned.fill(
child: CustomPaint( child: CustomPaint(
painter: _RadialPainter( painter: _RadialPainter(
circles: _circles.toList(), circles: _circles.toList(),
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
), ),
), child: Material( // Splashes.
),
Material( // Splashes.
type: MaterialType.transparency, type: MaterialType.transparency,
child: Padding( child: Padding(
padding: EdgeInsets.only(bottom: additionalBottomPadding), padding: EdgeInsets.only(bottom: additionalBottomPadding),
...@@ -600,10 +589,8 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr ...@@ -600,10 +589,8 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
), ),
), ),
), ),
],
), ),
), ),
],
), ),
); );
} }
......
...@@ -135,7 +135,6 @@ class Card extends StatelessWidget { ...@@ -135,7 +135,6 @@ class Card extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Semantics( return Semantics(
container: semanticContainer, container: semanticContainer,
explicitChildNodes: !semanticContainer,
child: Container( child: Container(
margin: margin ?? const EdgeInsets.all(4.0), margin: margin ?? const EdgeInsets.all(4.0),
child: Material( child: Material(
...@@ -146,9 +145,12 @@ class Card extends StatelessWidget { ...@@ -146,9 +145,12 @@ class Card extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(4.0)), borderRadius: BorderRadius.all(Radius.circular(4.0)),
), ),
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
child: Semantics(
explicitChildNodes: !semanticContainer,
child: child, child: child,
), ),
), ),
),
); );
} }
} }
...@@ -311,7 +311,11 @@ class AlertDialog extends StatelessWidget { ...@@ -311,7 +311,11 @@ class AlertDialog extends StatelessWidget {
padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0), padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
child: DefaultTextStyle( child: DefaultTextStyle(
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.title, style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.title,
child: Semantics(child: title, namesRoute: true), child: Semantics(
child: title,
namesRoute: true,
container: true,
),
), ),
)); ));
} else { } else {
......
...@@ -2406,6 +2406,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2406,6 +2406,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
final SemanticsNode node = interestingFragment.compileChildren( final SemanticsNode node = interestingFragment.compileChildren(
parentSemanticsClipRect: _semantics?.parentSemanticsClipRect, parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
parentPaintClipRect: _semantics?.parentPaintClipRect, parentPaintClipRect: _semantics?.parentPaintClipRect,
elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0,
).single; ).single;
// Fragment only wants to add this node's SemanticsNode to the parent. // Fragment only wants to add this node's SemanticsNode to the parent.
assert(interestingFragment.config == null && node == _semantics); assert(interestingFragment.config == null && node == _semantics);
...@@ -3193,9 +3194,19 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { ...@@ -3193,9 +3194,19 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
final List<RenderObject> _ancestorChain; final List<RenderObject> _ancestorChain;
/// The children to be added to the parent. /// 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({ Iterable<SemanticsNode> compileChildren({
@required Rect parentSemanticsClipRect, @required Rect parentSemanticsClipRect,
@required Rect parentPaintClipRect @required Rect parentPaintClipRect,
@required double elevationAdjustment,
}); });
/// The [SemanticsConfiguration] the child wants to merge into the parent's /// The [SemanticsConfiguration] the child wants to merge into the parent's
...@@ -3264,11 +3275,12 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3264,11 +3275,12 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
}) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings); }) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
@override @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(_tagsForChildren == null || _tagsForChildren.isEmpty);
assert(parentSemanticsClipRect == null); assert(parentSemanticsClipRect == null);
assert(parentPaintClipRect == null); assert(parentPaintClipRect == null);
assert(_ancestorChain.length == 1); assert(_ancestorChain.length == 1);
assert(elevationAdjustment == 0.0);
owner._semantics ??= SemanticsNode.root( owner._semantics ??= SemanticsNode.root(
showOnScreen: owner.showOnScreen, showOnScreen: owner.showOnScreen,
...@@ -3287,6 +3299,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3287,6 +3299,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
children.addAll(fragment.compileChildren( children.addAll(fragment.compileChildren(
parentSemanticsClipRect: parentSemanticsClipRect, parentSemanticsClipRect: parentSemanticsClipRect,
parentPaintClipRect: parentPaintClipRect, parentPaintClipRect: parentPaintClipRect,
elevationAdjustment: 0.0,
)); ));
} }
node.updateWith(config: null, childrenInInversePaintOrder: children); node.updateWith(config: null, childrenInInversePaintOrder: children);
...@@ -3352,13 +3365,20 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3352,13 +3365,20 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[]; final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
@override @override
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* { Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect, double elevationAdjustment}) sync* {
if (!_isExplicit) { if (!_isExplicit) {
owner._semantics = null; owner._semantics = null;
for (_InterestingSemanticsFragment fragment in _children) { for (_InterestingSemanticsFragment fragment in _children) {
assert(_ancestorChain.first == fragment._ancestorChain.last); assert(_ancestorChain.first == fragment._ancestorChain.last);
fragment._ancestorChain.addAll(_ancestorChain.sublist(1)); 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; return;
} }
...@@ -3375,6 +3395,12 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3375,6 +3395,12 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
..isMergedIntoParent = _mergeIntoParent ..isMergedIntoParent = _mergeIntoParent
..tags = _tagsForChildren; ..tags = _tagsForChildren;
node.elevationAdjustment = elevationAdjustment;
if (elevationAdjustment != 0.0) {
_ensureConfigIsWritable();
_config.elevation += elevationAdjustment;
}
if (geometry != null) { if (geometry != null) {
assert(_needsGeometryUpdate); assert(_needsGeometryUpdate);
node node
...@@ -3389,8 +3415,13 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3389,8 +3415,13 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
} }
final List<SemanticsNode> children = <SemanticsNode>[]; final List<SemanticsNode> children = <SemanticsNode>[];
for (_InterestingSemanticsFragment fragment in _children) for (_InterestingSemanticsFragment fragment in _children) {
children.addAll(fragment.compileChildren(parentSemanticsClipRect: node.parentSemanticsClipRect, parentPaintClipRect: node.parentPaintClipRect)); children.addAll(fragment.compileChildren(
parentSemanticsClipRect: node.parentSemanticsClipRect,
parentPaintClipRect: node.parentPaintClipRect,
elevationAdjustment: 0.0,
));
}
if (_config.isSemanticBoundary) { if (_config.isSemanticBoundary) {
owner.assembleSemanticsNode(node, _config, children); owner.assembleSemanticsNode(node, _config, children);
...@@ -3457,7 +3488,7 @@ class _AbortingSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -3457,7 +3488,7 @@ class _AbortingSemanticsFragment extends _InterestingSemanticsFragment {
} }
@override @override
Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect}) sync* { Iterable<SemanticsNode> compileChildren({Rect parentSemanticsClipRect, Rect parentPaintClipRect, double elevationAdjustment}) sync* {
yield owner._semantics; yield owner._semantics;
} }
......
...@@ -1571,6 +1571,12 @@ abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> { ...@@ -1571,6 +1571,12 @@ abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> {
@override @override
bool get alwaysNeedsCompositing => _elevation != 0.0 && defaultTargetPlatform == TargetPlatform.fuchsia; bool get alwaysNeedsCompositing => _elevation != 0.0 && defaultTargetPlatform == TargetPlatform.fuchsia;
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.elevation = elevation;
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
......
...@@ -187,6 +187,8 @@ class SemanticsData extends Diagnosticable { ...@@ -187,6 +187,8 @@ class SemanticsData extends Diagnosticable {
@required this.hint, @required this.hint,
@required this.textDirection, @required this.textDirection,
@required this.rect, @required this.rect,
@required this.elevation,
@required this.thickness,
@required this.textSelection, @required this.textSelection,
@required this.scrollIndex, @required this.scrollIndex,
@required this.scrollChildCount, @required this.scrollChildCount,
...@@ -305,6 +307,21 @@ class SemanticsData extends Diagnosticable { ...@@ -305,6 +307,21 @@ class SemanticsData extends Diagnosticable {
/// parent). /// parent).
final Matrix4 transform; 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 /// The identifiers for the custom semantics actions and standard action
/// overrides for this node. /// overrides for this node.
/// ///
...@@ -329,6 +346,8 @@ class SemanticsData extends Diagnosticable { ...@@ -329,6 +346,8 @@ class SemanticsData extends Diagnosticable {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false)); properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false));
properties.add(TransformProperty('transform', transform, showName: false, defaultValue: null)); 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>[]; final List<String> actionSummary = <String>[];
for (SemanticsAction action in SemanticsAction.values.values) { for (SemanticsAction action in SemanticsAction.values.values) {
if ((actions & action.index) != 0) if ((actions & action.index) != 0)
...@@ -383,6 +402,8 @@ class SemanticsData extends Diagnosticable { ...@@ -383,6 +402,8 @@ class SemanticsData extends Diagnosticable {
&& typedOther.scrollExtentMax == scrollExtentMax && typedOther.scrollExtentMax == scrollExtentMax
&& typedOther.scrollExtentMin == scrollExtentMin && typedOther.scrollExtentMin == scrollExtentMin
&& typedOther.transform == transform && typedOther.transform == transform
&& typedOther.elevation == elevation
&& typedOther.thickness == thickness
&& _sortedListsEqual(typedOther.customSemanticsActionIds, customSemanticsActionIds); && _sortedListsEqual(typedOther.customSemanticsActionIds, customSemanticsActionIds);
} }
...@@ -406,6 +427,8 @@ class SemanticsData extends Diagnosticable { ...@@ -406,6 +427,8 @@ class SemanticsData extends Diagnosticable {
scrollExtentMax, scrollExtentMax,
scrollExtentMin, scrollExtentMin,
transform, transform,
elevation,
thickness,
ui.hashList(customSemanticsActionIds), ui.hashList(customSemanticsActionIds),
); );
} }
...@@ -1143,6 +1166,23 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1143,6 +1166,23 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// If this rect is null [parentSemanticsClipRect] also has to be null. /// If this rect is null [parentSemanticsClipRect] also has to be null.
Rect parentPaintClipRect; 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. /// The index of this node within the parent's list of semantic children.
/// ///
/// This includes all semantic nodes, not just those currently in the /// This includes all semantic nodes, not just those currently in the
...@@ -1409,6 +1449,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1409,6 +1449,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) { bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
return _label != config.label || return _label != config.label ||
_hint != config.hint || _hint != config.hint ||
_elevation != config.elevation ||
_thickness != config.thickness ||
_decreasedValue != config.decreasedValue || _decreasedValue != config.decreasedValue ||
_value != config.value || _value != config.value ||
_increasedValue != config.increasedValue || _increasedValue != config.increasedValue ||
...@@ -1483,6 +1525,73 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1483,6 +1525,73 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
String get hint => _hint; String get hint => _hint;
String _hint = _kEmptyConfig.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 /// Provides hint values which override the default hints on supported
/// platforms. /// platforms.
SemanticsHintOverrides get hintOverrides => _hintOverrides; SemanticsHintOverrides get hintOverrides => _hintOverrides;
...@@ -1581,6 +1690,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1581,6 +1690,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_increasedValue = config.increasedValue; _increasedValue = config.increasedValue;
_hint = config.hint; _hint = config.hint;
_hintOverrides = config.hintOverrides; _hintOverrides = config.hintOverrides;
_elevation = config.elevation;
_thickness = config.thickness;
_flags = config._flags; _flags = config._flags;
_textDirection = config.textDirection; _textDirection = config.textDirection;
_sortKey = config.sortKey; _sortKey = config.sortKey;
...@@ -1629,6 +1740,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1629,6 +1740,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
double scrollPosition = _scrollPosition; double scrollPosition = _scrollPosition;
double scrollExtentMax = _scrollExtentMax; double scrollExtentMax = _scrollExtentMax;
double scrollExtentMin = _scrollExtentMin; double scrollExtentMin = _scrollExtentMin;
final double elevation = _elevation;
double thickness = _thickness;
final Set<int> customSemanticsActionIds = Set<int>(); final Set<int> customSemanticsActionIds = Set<int>();
for (CustomSemanticsAction action in _customSemanticsActions.keys) for (CustomSemanticsAction action in _customSemanticsActions.keys)
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action)); customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
...@@ -1703,6 +1816,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1703,6 +1816,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
otherString: node._hint, otherString: node._hint,
otherTextDirection: node._textDirection, otherTextDirection: node._textDirection,
); );
thickness = math.max(thickness, node._thickness + node._elevation);
return true; return true;
}); });
} }
...@@ -1718,6 +1834,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1718,6 +1834,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
textDirection: textDirection, textDirection: textDirection,
rect: rect, rect: rect,
transform: transform, transform: transform,
elevation: elevation,
thickness: thickness,
tags: mergedTags, tags: mergedTags,
textSelection: textSelection, textSelection: textSelection,
scrollChildCount: scrollChildCount, scrollChildCount: scrollChildCount,
...@@ -1786,6 +1904,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1786,6 +1904,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
scrollExtentMax: data.scrollExtentMax != null ? data.scrollExtentMax : double.nan, scrollExtentMax: data.scrollExtentMax != null ? data.scrollExtentMax : double.nan,
scrollExtentMin: data.scrollExtentMin != null ? data.scrollExtentMin : double.nan, scrollExtentMin: data.scrollExtentMin != null ? data.scrollExtentMin : double.nan,
transform: data.transform?.storage ?? _kIdentityTransform, transform: data.transform?.storage ?? _kIdentityTransform,
elevation: data.elevation,
thickness: data.thickness,
childrenInTraversalOrder: childrenInTraversalOrder, childrenInTraversalOrder: childrenInTraversalOrder,
childrenInHitTestOrder: childrenInHitTestOrder, childrenInHitTestOrder: childrenInHitTestOrder,
additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList, additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
...@@ -1923,6 +2043,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1923,6 +2043,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null)); properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null)); properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, 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. /// Returns a string representation of this node and its descendants.
...@@ -3115,6 +3237,36 @@ class SemanticsConfiguration { ...@@ -3115,6 +3237,36 @@ class SemanticsConfiguration {
_hasBeenAnnotated = true; _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 /// Whether the semantics node is the root of a subtree for which values
/// should be announced. /// should be announced.
/// ///
...@@ -3436,55 +3588,60 @@ class SemanticsConfiguration { ...@@ -3436,55 +3588,60 @@ class SemanticsConfiguration {
return true; 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 /// This adds the semantic information of both configurations and saves the
/// result in this configuration. /// 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 /// Only configurations that have [explicitChildNodes] set to false can
/// absorb other configurations and it is recommended to only absorb compatible /// absorb other configurations and it is recommended to only absorb compatible
/// configurations as determined by [isCompatibleWith]. /// configurations as determined by [isCompatibleWith].
void absorb(SemanticsConfiguration other) { void absorb(SemanticsConfiguration child) {
assert(!explicitChildNodes); assert(!explicitChildNodes);
if (!other.hasBeenAnnotated) if (!child.hasBeenAnnotated)
return; return;
_actions.addAll(other._actions); _actions.addAll(child._actions);
_customSemanticsActions.addAll(other._customSemanticsActions); _customSemanticsActions.addAll(child._customSemanticsActions);
_actionsAsBits |= other._actionsAsBits; _actionsAsBits |= child._actionsAsBits;
_flags |= other._flags; _flags |= child._flags;
_textSelection ??= other._textSelection; _textSelection ??= child._textSelection;
_scrollPosition ??= other._scrollPosition; _scrollPosition ??= child._scrollPosition;
_scrollExtentMax ??= other._scrollExtentMax; _scrollExtentMax ??= child._scrollExtentMax;
_scrollExtentMin ??= other._scrollExtentMin; _scrollExtentMin ??= child._scrollExtentMin;
_hintOverrides ??= other._hintOverrides; _hintOverrides ??= child._hintOverrides;
_indexInParent ??= other.indexInParent; _indexInParent ??= child.indexInParent;
_scrollIndex ??= other._scrollIndex; _scrollIndex ??= child._scrollIndex;
_scrollChildCount ??= other._scrollChildCount; _scrollChildCount ??= child._scrollChildCount;
textDirection ??= other.textDirection; textDirection ??= child.textDirection;
_sortKey ??= other._sortKey; _sortKey ??= child._sortKey;
_label = _concatStrings( _label = _concatStrings(
thisString: _label, thisString: _label,
thisTextDirection: textDirection, thisTextDirection: textDirection,
otherString: other._label, otherString: child._label,
otherTextDirection: other.textDirection, otherTextDirection: child.textDirection,
); );
if (_decreasedValue == '' || _decreasedValue == null) if (_decreasedValue == '' || _decreasedValue == null)
_decreasedValue = other._decreasedValue; _decreasedValue = child._decreasedValue;
if (_value == '' || _value == null) if (_value == '' || _value == null)
_value = other._value; _value = child._value;
if (_increasedValue == '' || _increasedValue == null) if (_increasedValue == '' || _increasedValue == null)
_increasedValue = other._increasedValue; _increasedValue = child._increasedValue;
_hint = _concatStrings( _hint = _concatStrings(
thisString: _hint, thisString: _hint,
thisTextDirection: textDirection, thisTextDirection: textDirection,
otherString: other._hint, otherString: child._hint,
otherTextDirection: other.textDirection, 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. /// Returns an exact copy of this configuration.
...@@ -3503,6 +3660,8 @@ class SemanticsConfiguration { ...@@ -3503,6 +3660,8 @@ class SemanticsConfiguration {
.._decreasedValue = _decreasedValue .._decreasedValue = _decreasedValue
.._hint = _hint .._hint = _hint
.._hintOverrides = _hintOverrides .._hintOverrides = _hintOverrides
.._elevation = _elevation
.._thickness = _thickness
.._flags = _flags .._flags = _flags
.._tagsForChildren = _tagsForChildren .._tagsForChildren = _tagsForChildren
.._textSelection = _textSelection .._textSelection = _textSelection
......
...@@ -40,16 +40,21 @@ void main() { ...@@ -40,16 +40,21 @@ void main() {
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 1, id: 1,
elevation: 1.0,
thickness: 0.0,
children: <TestSemantics>[
TestSemantics(
id: 2,
label: 'I am text!', label: 'I am text!',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
), ),
TestSemantics( TestSemantics(
id: 2, id: 3,
label: 'Moar text!!1', label: 'Moar text!!1',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
), ),
TestSemantics( TestSemantics(
id: 3, id: 4,
label: 'Button', label: 'Button',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
actions: <SemanticsAction>[ actions: <SemanticsAction>[
...@@ -62,6 +67,8 @@ void main() { ...@@ -62,6 +67,8 @@ void main() {
], ],
), ),
], ],
)
],
), ),
ignoreTransform: true, ignoreTransform: true,
ignoreRect: true, ignoreRect: true,
......
...@@ -425,9 +425,11 @@ void _tests() { ...@@ -425,9 +425,11 @@ void _tests() {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await preparePicker(tester, (Future<DateTime> date) async { await preparePicker(tester, (Future<DateTime> date) async {
final TestSemantics expected = TestSemantics( final TestSemantics expected = TestSemantics(
flags: <SemanticsFlag>[ flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
SemanticsFlag.scopesRoute, children: <TestSemantics>[
], TestSemantics(
elevation: 24.0,
thickness: 0.0,
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap], actions: <SemanticsAction>[SemanticsAction.tap],
...@@ -443,9 +445,9 @@ void _tests() { ...@@ -443,9 +445,9 @@ void _tests() {
TestSemantics( TestSemantics(
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight],
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight],
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
children: <TestSemantics>[ children: <TestSemantics>[
...@@ -642,6 +644,8 @@ void _tests() { ...@@ -642,6 +644,8 @@ void _tests() {
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
), ),
], ],
),
],
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
......
// 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() { ...@@ -353,6 +353,8 @@ void main() {
' scrollExtentMin: null\n' ' scrollExtentMin: null\n'
' scrollPosition: null\n' ' scrollPosition: null\n'
' scrollExtentMax: null\n' ' scrollExtentMax: null\n'
' elevation: 0.0\n'
' thicknes: 0.0\n'
); );
final SemanticsConfiguration config = SemanticsConfiguration() final SemanticsConfiguration config = SemanticsConfiguration()
...@@ -445,6 +447,8 @@ void main() { ...@@ -445,6 +447,8 @@ void main() {
' scrollExtentMin: null\n' ' scrollExtentMin: null\n'
' scrollPosition: null\n' ' scrollPosition: null\n'
' scrollExtentMax: null\n' ' scrollExtentMax: null\n'
' elevation: 0.0\n'
' thicknes: 0.0\n'
); );
}); });
......
...@@ -46,6 +46,8 @@ class TestSemantics { ...@@ -46,6 +46,8 @@ class TestSemantics {
this.textDirection, this.textDirection,
this.rect, this.rect,
this.transform, this.transform,
this.elevation,
this.thickness,
this.textSelection, this.textSelection,
this.children = const <TestSemantics>[], this.children = const <TestSemantics>[],
this.scrollIndex, this.scrollIndex,
...@@ -87,6 +89,8 @@ class TestSemantics { ...@@ -87,6 +89,8 @@ class TestSemantics {
assert(value != null), assert(value != null),
assert(hint != null), assert(hint != null),
rect = TestSemantics.rootRect, rect = TestSemantics.rootRect,
elevation = 0.0,
thickness = 0.0,
assert(children != null), assert(children != null),
tags = tags?.toSet() ?? Set<SemanticsTag>(); tags = tags?.toSet() ?? Set<SemanticsTag>();
...@@ -111,6 +115,8 @@ class TestSemantics { ...@@ -111,6 +115,8 @@ class TestSemantics {
this.textDirection, this.textDirection,
this.rect, this.rect,
Matrix4 transform, Matrix4 transform,
this.elevation,
this.thickness,
this.textSelection, this.textSelection,
this.children = const <TestSemantics>[], this.children = const <TestSemantics>[],
this.scrollIndex, this.scrollIndex,
...@@ -206,6 +212,21 @@ class TestSemantics { ...@@ -206,6 +212,21 @@ class TestSemantics {
/// parent). /// parent).
final Matrix4 transform; 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. /// The index of the first visible semantic node within a scrollable.
final int scrollIndex; final int scrollIndex;
...@@ -279,6 +300,12 @@ class TestSemantics { ...@@ -279,6 +300,12 @@ class TestSemantics {
return fail('expected node id $id to have rect $rect but found rect ${nodeData.rect}.'); return fail('expected node id $id to have rect $rect but found rect ${nodeData.rect}.');
if (!ignoreTransform && transform != nodeData.transform) if (!ignoreTransform && transform != nodeData.transform)
return fail('expected node id $id to have transform $transform but found transform:\n${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) { 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}].'); 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 { ...@@ -346,6 +373,10 @@ class TestSemantics {
buf.writeln('$indent rect: $rect,'); buf.writeln('$indent rect: $rect,');
if (transform != null) if (transform != null)
buf.writeln('$indent transform:\n${transform.toString().trim().split('\n').map<String>((String line) => '$indent $line').join('\n')},'); 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>['); buf.writeln('$indent children: <TestSemantics>[');
for (TestSemantics child in children) { for (TestSemantics child in children) {
buf.writeln('${child.toString(indentAmount + 2)},'); buf.writeln('${child.toString(indentAmount + 2)},');
......
...@@ -355,6 +355,8 @@ Matcher matchesSemantics({ ...@@ -355,6 +355,8 @@ Matcher matchesSemantics({
TextDirection textDirection, TextDirection textDirection,
Rect rect, Rect rect,
Size size, Size size,
double elevation,
double thickness,
// Flags // // Flags //
bool hasCheckedState = false, bool hasCheckedState = false,
bool isChecked = false, bool isChecked = false,
...@@ -503,6 +505,8 @@ Matcher matchesSemantics({ ...@@ -503,6 +505,8 @@ Matcher matchesSemantics({
textDirection: textDirection, textDirection: textDirection,
rect: rect, rect: rect,
size: size, size: size,
elevation: elevation,
thickness: thickness,
customActions: customActions, customActions: customActions,
hintOverrides: hintOverrides, hintOverrides: hintOverrides,
children: children, children: children,
...@@ -1685,6 +1689,8 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1685,6 +1689,8 @@ class _MatchesSemanticsData extends Matcher {
this.textDirection, this.textDirection,
this.rect, this.rect,
this.size, this.size,
this.elevation,
this.thickness,
this.customActions, this.customActions,
this.hintOverrides, this.hintOverrides,
this.children, this.children,
...@@ -1702,6 +1708,8 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1702,6 +1708,8 @@ class _MatchesSemanticsData extends Matcher {
final TextDirection textDirection; final TextDirection textDirection;
final Rect rect; final Rect rect;
final Size size; final Size size;
final double elevation;
final double thickness;
final List<Matcher> children; final List<Matcher> children;
@override @override
...@@ -1727,6 +1735,10 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1727,6 +1735,10 @@ class _MatchesSemanticsData extends Matcher {
description.add(' with rect: $rect'); description.add(' with rect: $rect');
if (size != null) if (size != null)
description.add(' with size: $size'); description.add(' with size: $size');
if (elevation != null)
description.add(' with elevation: $elevation');
if (thickness != null)
description.add(' with thickness: $thickness');
if (customActions != null) if (customActions != null)
description.add(' with custom actions: $customActions'); description.add(' with custom actions: $customActions');
if (hintOverrides != null) if (hintOverrides != null)
...@@ -1763,6 +1775,10 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1763,6 +1775,10 @@ class _MatchesSemanticsData extends Matcher {
return failWithDescription(matchState, 'rect was: ${data.rect}'); return failWithDescription(matchState, 'rect was: ${data.rect}');
if (size != null && size != data.rect.size) if (size != null && size != data.rect.size)
return failWithDescription(matchState, 'size was: ${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) { if (actions != null) {
int actionBits = 0; int actionBits = 0;
for (SemanticsAction action in actions) for (SemanticsAction action in actions)
......
...@@ -494,6 +494,8 @@ void main() { ...@@ -494,6 +494,8 @@ void main() {
hint: 'e', hint: 'e',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null, textSelection: null,
scrollIndex: null, scrollIndex: null,
scrollChildCount: null, scrollChildCount: null,
...@@ -508,6 +510,8 @@ void main() { ...@@ -508,6 +510,8 @@ void main() {
expect(node, matchesSemantics( expect(node, matchesSemantics(
rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), rect: Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
size: const Size(10.0, 10.0), size: const Size(10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
/* Flags */ /* Flags */
hasCheckedState: true, hasCheckedState: true,
isChecked: 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