Commit 87c5b24e authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Exclude ModalBarrier from Semantics Tree if it is not dismissible (#10395)

parent d14bb2cd
......@@ -2901,12 +2901,41 @@ class RenderMergeSemantics extends RenderProxyBox {
/// Excludes this subtree from the semantic tree.
///
/// When [excluding] is true, this render object (and its subtree) is excluded
/// from the semantic tree.
///
/// Useful e.g. for hiding text that is redundant with other text next
/// to it (e.g. text included only for the visual effect).
class RenderExcludeSemantics extends RenderProxyBox {
/// Creates a render object that ignores the semantics of its subtree.
RenderExcludeSemantics({ RenderBox child }) : super(child);
RenderExcludeSemantics({
RenderBox child,
bool excluding: true,
}) : _excluding = excluding, super(child) {
assert(_excluding != null);
}
/// Whether this render object is excluded from the semantic tree.
bool get excluding => _excluding;
bool _excluding;
set excluding(bool value) {
assert(value != null);
if (value == _excluding)
return;
_excluding = value;
markNeedsSemanticsUpdate();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) { }
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (excluding)
return;
super.visitChildrenForSemantics(visitor);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('excluding: $excluding');
}
}
......@@ -3424,16 +3424,38 @@ class MergeSemantics extends SingleChildRenderObjectWidget {
/// A widget that drops all the semantics of its descendants.
///
/// When [excluding] is true, this widget (and its subtree) is excluded from
/// the semantics tree.
///
/// This can be used to hide subwidgets that would otherwise be
/// reported but that would only be confusing. For example, the
/// material library's [Chip] widget hides the avatar since it is
/// redundant with the chip label.
class ExcludeSemantics extends SingleChildRenderObjectWidget {
/// Creates a widget that drops all the semantics of its descendants.
const ExcludeSemantics({ Key key, Widget child }) : super(key: key, child: child);
const ExcludeSemantics({
Key key,
this.excluding: true,
Widget child,
}) : assert(excluding != null),
super(key: key, child: child);
/// Whether this widget is excluded in the semantics tree.
final bool excluding;
@override
RenderExcludeSemantics createRenderObject(BuildContext context) => new RenderExcludeSemantics(excluding: excluding);
@override
RenderExcludeSemantics createRenderObject(BuildContext context) => new RenderExcludeSemantics();
void updateRenderObject(BuildContext context, RenderExcludeSemantics renderObject) {
renderObject.excluding = excluding;
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('excluding: $excluding');
}
}
/// A widget that builds its child.
......
......@@ -26,19 +26,22 @@ class ModalBarrier extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Semantics(
container: true,
child: new GestureDetector(
onTapDown: (TapDownDetails details) {
if (dismissible)
Navigator.pop(context);
},
behavior: HitTestBehavior.opaque,
child: new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: color == null ? null : new DecoratedBox(
decoration: new BoxDecoration(
color: color
return new ExcludeSemantics(
excluding: !dismissible,
child: new Semantics(
container: true,
child: new GestureDetector(
onTapDown: (TapDownDetails details) {
if (dismissible)
Navigator.pop(context);
},
behavior: HitTestBehavior.opaque,
child: new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: color == null ? null : new DecoratedBox(
decoration: new BoxDecoration(
color: color
)
)
)
)
......
......@@ -4,8 +4,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'semantics_tester.dart';
void main() {
bool tapped;
Widget tapTarget;
......@@ -78,6 +81,40 @@ void main() {
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.');
});
testWidgets('Undismissible ModalBarrier hidden in semantic tree', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(const ModalBarrier(dismissible: false));
final TestSemantics expectedSemantics = new TestSemantics.root();
expect(semantics, hasSemantics(expectedSemantics));
semantics.dispose();
});
testWidgets('Dismissible ModalBarrier includes button in semantic tree', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(const ModalBarrier(dismissible: true));
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
new TestSemantics(
id: 2,
rect: TestSemantics.fullScreen,
actions: SemanticsAction.tap.index,
),
]
),
]
);
expect(semantics, hasSemantics(expectedSemantics));
semantics.dispose();
});
}
class FirstWidget extends StatelessWidget {
......
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