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);
......
......@@ -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,
......
This diff is collapsed.
......@@ -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