Adds a widgets that blocks all semantics of widgets below it in paint order within the same container (#10425)
This commit is contained in:
parent
417df36b45
commit
104725f384
@ -270,10 +270,12 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
|
|||||||
child: new RepaintBoundary(
|
child: new RepaintBoundary(
|
||||||
child: new Stack(
|
child: new Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new GestureDetector(
|
new BlockSemantics(
|
||||||
onTap: close,
|
child: new GestureDetector(
|
||||||
child: new Container(
|
onTap: close,
|
||||||
color: _color.evaluate(_controller)
|
child: new Container(
|
||||||
|
color: _color.evaluate(_controller)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
new Align(
|
new Align(
|
||||||
|
@ -662,7 +662,8 @@ abstract class _SemanticsFragment {
|
|||||||
_SemanticsFragment({
|
_SemanticsFragment({
|
||||||
@required RenderObject renderObjectOwner,
|
@required RenderObject renderObjectOwner,
|
||||||
this.annotator,
|
this.annotator,
|
||||||
List<_SemanticsFragment> children
|
List<_SemanticsFragment> children,
|
||||||
|
this.dropSemanticsOfPreviousSiblings,
|
||||||
}) {
|
}) {
|
||||||
assert(renderObjectOwner != null);
|
assert(renderObjectOwner != null);
|
||||||
_ancestorChain = <RenderObject>[renderObjectOwner];
|
_ancestorChain = <RenderObject>[renderObjectOwner];
|
||||||
@ -678,6 +679,9 @@ abstract class _SemanticsFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final SemanticsAnnotator annotator;
|
final SemanticsAnnotator annotator;
|
||||||
|
bool dropSemanticsOfPreviousSiblings;
|
||||||
|
|
||||||
|
bool get producesSemanticNodes => true;
|
||||||
|
|
||||||
List<RenderObject> _ancestorChain;
|
List<RenderObject> _ancestorChain;
|
||||||
void addAncestor(RenderObject ancestor) {
|
void addAncestor(RenderObject ancestor) {
|
||||||
@ -695,6 +699,20 @@ abstract class _SemanticsFragment {
|
|||||||
String toString() => '$runtimeType#$hashCode';
|
String toString() => '$runtimeType#$hashCode';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A SemanticsFragment that doesn't produce any [SemanticsNode]s when compiled.
|
||||||
|
class _EmptySemanticsFragment extends _SemanticsFragment {
|
||||||
|
_EmptySemanticsFragment({
|
||||||
|
@required RenderObject renderObjectOwner,
|
||||||
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<SemanticsNode> compile({ _SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics }) sync* { }
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get producesSemanticNodes => false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a [RenderObject] which is in no way dirty.
|
/// Represents a [RenderObject] which is in no way dirty.
|
||||||
///
|
///
|
||||||
/// This class has no children and no annotators, and when compiled, it returns
|
/// This class has no children and no annotators, and when compiled, it returns
|
||||||
@ -702,8 +720,9 @@ abstract class _SemanticsFragment {
|
|||||||
/// the matrix, since that comes from the (dirty) ancestors.)
|
/// the matrix, since that comes from the (dirty) ancestors.)
|
||||||
class _CleanSemanticsFragment extends _SemanticsFragment {
|
class _CleanSemanticsFragment extends _SemanticsFragment {
|
||||||
_CleanSemanticsFragment({
|
_CleanSemanticsFragment({
|
||||||
@required RenderObject renderObjectOwner
|
@required RenderObject renderObjectOwner,
|
||||||
}) : super(renderObjectOwner: renderObjectOwner) {
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings) {
|
||||||
assert(renderObjectOwner != null);
|
assert(renderObjectOwner != null);
|
||||||
assert(renderObjectOwner._semantics != null);
|
assert(renderObjectOwner._semantics != null);
|
||||||
}
|
}
|
||||||
@ -728,8 +747,9 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
|
|||||||
_InterestingSemanticsFragment({
|
_InterestingSemanticsFragment({
|
||||||
RenderObject renderObjectOwner,
|
RenderObject renderObjectOwner,
|
||||||
SemanticsAnnotator annotator,
|
SemanticsAnnotator annotator,
|
||||||
Iterable<_SemanticsFragment> children
|
Iterable<_SemanticsFragment> children,
|
||||||
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children);
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
|
|
||||||
bool get haveConcreteNode => true;
|
bool get haveConcreteNode => true;
|
||||||
|
|
||||||
@ -765,8 +785,9 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
|
|||||||
_RootSemanticsFragment({
|
_RootSemanticsFragment({
|
||||||
RenderObject renderObjectOwner,
|
RenderObject renderObjectOwner,
|
||||||
SemanticsAnnotator annotator,
|
SemanticsAnnotator annotator,
|
||||||
Iterable<_SemanticsFragment> children
|
Iterable<_SemanticsFragment> children,
|
||||||
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children);
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) {
|
SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) {
|
||||||
@ -798,8 +819,9 @@ class _ConcreteSemanticsFragment extends _InterestingSemanticsFragment {
|
|||||||
_ConcreteSemanticsFragment({
|
_ConcreteSemanticsFragment({
|
||||||
RenderObject renderObjectOwner,
|
RenderObject renderObjectOwner,
|
||||||
SemanticsAnnotator annotator,
|
SemanticsAnnotator annotator,
|
||||||
Iterable<_SemanticsFragment> children
|
Iterable<_SemanticsFragment> children,
|
||||||
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children);
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) {
|
SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) {
|
||||||
@ -833,8 +855,9 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment {
|
|||||||
_ImplicitSemanticsFragment({
|
_ImplicitSemanticsFragment({
|
||||||
RenderObject renderObjectOwner,
|
RenderObject renderObjectOwner,
|
||||||
SemanticsAnnotator annotator,
|
SemanticsAnnotator annotator,
|
||||||
Iterable<_SemanticsFragment> children
|
Iterable<_SemanticsFragment> children,
|
||||||
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children);
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get haveConcreteNode => _haveConcreteNode;
|
bool get haveConcreteNode => _haveConcreteNode;
|
||||||
@ -878,8 +901,9 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment {
|
|||||||
class _ForkingSemanticsFragment extends _SemanticsFragment {
|
class _ForkingSemanticsFragment extends _SemanticsFragment {
|
||||||
_ForkingSemanticsFragment({
|
_ForkingSemanticsFragment({
|
||||||
RenderObject renderObjectOwner,
|
RenderObject renderObjectOwner,
|
||||||
@required Iterable<_SemanticsFragment> children
|
@required Iterable<_SemanticsFragment> children,
|
||||||
}) : super(renderObjectOwner: renderObjectOwner, children: children) {
|
bool dropSemanticsOfPreviousSiblings,
|
||||||
|
}) : super(renderObjectOwner: renderObjectOwner, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings) {
|
||||||
assert(children != null);
|
assert(children != null);
|
||||||
assert(children.length > 1);
|
assert(children.length > 1);
|
||||||
}
|
}
|
||||||
@ -1414,6 +1438,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
super.adoptChild(child);
|
super.adoptChild(child);
|
||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
markNeedsCompositingBitsUpdate();
|
markNeedsCompositingBitsUpdate();
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called by subclasses when they decide a render object is no longer a child.
|
/// Called by subclasses when they decide a render object is no longer a child.
|
||||||
@ -1431,6 +1456,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
super.dropChild(child);
|
super.dropChild(child);
|
||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
markNeedsCompositingBitsUpdate();
|
markNeedsCompositingBitsUpdate();
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls visitor for each immediate child of this render object.
|
/// Calls visitor for each immediate child of this render object.
|
||||||
@ -2408,6 +2434,22 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
/// setting [isSemanticBoundary] to true.
|
/// setting [isSemanticBoundary] to true.
|
||||||
bool get isSemanticBoundary => false;
|
bool get isSemanticBoundary => false;
|
||||||
|
|
||||||
|
/// Whether this [RenderObject] makes other [RenderObject]s previously painted
|
||||||
|
/// within the same semantic boundary unreachable for accessibility purposes.
|
||||||
|
///
|
||||||
|
/// If `true` is returned, the [SemanticsNode]s for all siblings and cousins
|
||||||
|
/// of this node, that are earlier in a depth-first pre-order traversal, are
|
||||||
|
/// dropped from the semantics tree up until a semantic boundary (as defined
|
||||||
|
/// by [isSemanticBoundary]) is reached.
|
||||||
|
///
|
||||||
|
/// If [isSemanticBoundary] and [isBlockingSemanticsOfPreviouslyPaintedNodes]
|
||||||
|
/// is set on the same node, all previously painted siblings and cousins
|
||||||
|
/// up until the next ancestor that is a semantic boundary are dropped.
|
||||||
|
///
|
||||||
|
/// Paint order as established by [visitChildrenForSemantics] is used to
|
||||||
|
/// determine if a node is previous to this one.
|
||||||
|
bool get isBlockingSemanticsOfPreviouslyPaintedNodes => false;
|
||||||
|
|
||||||
/// The bounding box, in the local coordinate system, of this
|
/// The bounding box, in the local coordinate system, of this
|
||||||
/// object, for accessibility purposes.
|
/// object, for accessibility purposes.
|
||||||
Rect get semanticBounds;
|
Rect get semanticBounds;
|
||||||
@ -2545,9 +2587,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
// early-exit if we're not dirty and have our own semantics
|
// early-exit if we're not dirty and have our own semantics
|
||||||
if (!_needsSemanticsUpdate && isSemanticBoundary) {
|
if (!_needsSemanticsUpdate && isSemanticBoundary) {
|
||||||
assert(_semantics != null);
|
assert(_semantics != null);
|
||||||
return new _CleanSemanticsFragment(renderObjectOwner: this);
|
return new _CleanSemanticsFragment(renderObjectOwner: this, dropSemanticsOfPreviousSiblings: isBlockingSemanticsOfPreviouslyPaintedNodes);
|
||||||
}
|
}
|
||||||
List<_SemanticsFragment> children;
|
List<_SemanticsFragment> children;
|
||||||
|
bool dropSemanticsOfPreviousSiblings = isBlockingSemanticsOfPreviouslyPaintedNodes;
|
||||||
visitChildrenForSemantics((RenderObject child) {
|
visitChildrenForSemantics((RenderObject child) {
|
||||||
if (_needsSemanticsGeometryUpdate) {
|
if (_needsSemanticsGeometryUpdate) {
|
||||||
// If our geometry changed, make sure the child also does a
|
// If our geometry changed, make sure the child also does a
|
||||||
@ -2557,31 +2600,40 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
child._needsSemanticsGeometryUpdate = true;
|
child._needsSemanticsGeometryUpdate = true;
|
||||||
}
|
}
|
||||||
final _SemanticsFragment fragment = child._getSemanticsFragment();
|
final _SemanticsFragment fragment = child._getSemanticsFragment();
|
||||||
if (fragment != null) {
|
assert(fragment != null);
|
||||||
|
if (fragment.dropSemanticsOfPreviousSiblings) {
|
||||||
|
children = null; // throw away all left siblings of [child].
|
||||||
|
dropSemanticsOfPreviousSiblings = true;
|
||||||
|
}
|
||||||
|
if (fragment.producesSemanticNodes) {
|
||||||
fragment.addAncestor(this);
|
fragment.addAncestor(this);
|
||||||
children ??= <_SemanticsFragment>[];
|
children ??= <_SemanticsFragment>[];
|
||||||
assert(!children.contains(fragment));
|
assert(!children.contains(fragment));
|
||||||
children.add(fragment);
|
children.add(fragment);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (isSemanticBoundary && !isBlockingSemanticsOfPreviouslyPaintedNodes) {
|
||||||
|
// Don't propagate [dropSemanticsOfPreviousSiblings] up through a semantic boundary.
|
||||||
|
dropSemanticsOfPreviousSiblings = false;
|
||||||
|
}
|
||||||
_needsSemanticsUpdate = false;
|
_needsSemanticsUpdate = false;
|
||||||
_needsSemanticsGeometryUpdate = false;
|
_needsSemanticsGeometryUpdate = false;
|
||||||
final SemanticsAnnotator annotator = semanticsAnnotator;
|
final SemanticsAnnotator annotator = semanticsAnnotator;
|
||||||
if (parent is! RenderObject)
|
if (parent is! RenderObject)
|
||||||
return new _RootSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children);
|
return new _RootSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
if (isSemanticBoundary)
|
if (isSemanticBoundary)
|
||||||
return new _ConcreteSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children);
|
return new _ConcreteSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
if (annotator != null)
|
if (annotator != null)
|
||||||
return new _ImplicitSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children);
|
return new _ImplicitSemanticsFragment(renderObjectOwner: this, annotator: annotator, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
_semantics = null;
|
_semantics = null;
|
||||||
if (children == null) {
|
if (children == null) {
|
||||||
// Introduces no semantics and has no descendants that introduce semantics.
|
// Introduces no semantics and has no descendants that introduce semantics.
|
||||||
return null;
|
return new _EmptySemanticsFragment(renderObjectOwner: this, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
}
|
}
|
||||||
if (children.length > 1)
|
if (children.length > 1)
|
||||||
return new _ForkingSemanticsFragment(renderObjectOwner: this, children: children);
|
return new _ForkingSemanticsFragment(renderObjectOwner: this, children: children, dropSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings);
|
||||||
assert(children.length == 1);
|
assert(children.length == 1);
|
||||||
return children.single;
|
return children.single..dropSemanticsOfPreviousSiblings = dropSemanticsOfPreviousSiblings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when collecting the semantics of this node.
|
/// Called when collecting the semantics of this node.
|
||||||
@ -2727,6 +2779,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
description.add('layer: $_layer');
|
description.add('layer: $_layer');
|
||||||
if (_semantics != null)
|
if (_semantics != null)
|
||||||
description.add('semantics: $_semantics');
|
description.add('semantics: $_semantics');
|
||||||
|
if (isBlockingSemanticsOfPreviouslyPaintedNodes)
|
||||||
|
description.add('blocks semantics of earlier render objects below the common boundary');
|
||||||
|
if (isSemanticBoundary)
|
||||||
|
description.add('semantic boundary');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string describing the current node's descendants. Each line of
|
/// Returns a string describing the current node's descendants. Each line of
|
||||||
|
@ -2891,6 +2891,18 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Causes the semantics of all siblings and cousins painted before it in the
|
||||||
|
/// same semantic container to be dropped.
|
||||||
|
///
|
||||||
|
/// This is useful in a stack where an overlay should prevent interactions
|
||||||
|
/// with the underlying layers.
|
||||||
|
class RenderBlockSemantics extends RenderProxyBox {
|
||||||
|
RenderBlockSemantics({ RenderBox child }) : super(child);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Causes the semantics of all descendants to be merged into this
|
/// Causes the semantics of all descendants to be merged into this
|
||||||
/// node such that the entire subtree becomes a single leaf in the
|
/// node such that the entire subtree becomes a single leaf in the
|
||||||
/// semantics tree.
|
/// semantics tree.
|
||||||
|
@ -3613,6 +3613,27 @@ class MergeSemantics extends SingleChildRenderObjectWidget {
|
|||||||
RenderMergeSemantics createRenderObject(BuildContext context) => new RenderMergeSemantics();
|
RenderMergeSemantics createRenderObject(BuildContext context) => new RenderMergeSemantics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A widget that drops the semantics of all widget that were painted before it
|
||||||
|
/// in the same semantic container.
|
||||||
|
///
|
||||||
|
/// This is useful to hide widgets from accessibility tools that are painted
|
||||||
|
/// behind a certain widget, e.g. an alert should usually disallow interaction
|
||||||
|
/// with any widget located "behind" the alert (even when they are still
|
||||||
|
/// partially visible). Similarly, an open [Drawer] blocks interactions with
|
||||||
|
/// any widget outside the drawer.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ExcludeSemantics] which drops all semantics of its descendants.
|
||||||
|
class BlockSemantics extends SingleChildRenderObjectWidget {
|
||||||
|
/// Creates a widget that excludes the semantics of all widgets painted before
|
||||||
|
/// it in the same semantic container.
|
||||||
|
const BlockSemantics({ Key key, Widget child }) : super(key: key, child: child);
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderBlockSemantics createRenderObject(BuildContext context) => new RenderBlockSemantics();
|
||||||
|
}
|
||||||
|
|
||||||
/// A widget that drops all the semantics of its descendants.
|
/// A widget that drops all the semantics of its descendants.
|
||||||
///
|
///
|
||||||
/// When [excluding] is true, this widget (and its subtree) is excluded from
|
/// When [excluding] is true, this widget (and its subtree) is excluded from
|
||||||
@ -3622,6 +3643,10 @@ class MergeSemantics extends SingleChildRenderObjectWidget {
|
|||||||
/// reported but that would only be confusing. For example, the
|
/// reported but that would only be confusing. For example, the
|
||||||
/// material library's [Chip] widget hides the avatar since it is
|
/// material library's [Chip] widget hides the avatar since it is
|
||||||
/// redundant with the chip label.
|
/// redundant with the chip label.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [BlockSemantics] which drops semantics of widgets earlier in the tree.
|
||||||
class ExcludeSemantics extends SingleChildRenderObjectWidget {
|
class ExcludeSemantics extends SingleChildRenderObjectWidget {
|
||||||
/// Creates a widget that drops all the semantics of its descendants.
|
/// Creates a widget that drops all the semantics of its descendants.
|
||||||
const ExcludeSemantics({
|
const ExcludeSemantics({
|
||||||
|
@ -26,21 +26,23 @@ class ModalBarrier extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new ExcludeSemantics(
|
return new BlockSemantics(
|
||||||
excluding: !dismissible,
|
child: new ExcludeSemantics(
|
||||||
child: new Semantics(
|
excluding: !dismissible,
|
||||||
container: true,
|
child: new Semantics(
|
||||||
child: new GestureDetector(
|
container: true,
|
||||||
onTapDown: (TapDownDetails details) {
|
child: new GestureDetector(
|
||||||
if (dismissible)
|
onTapDown: (TapDownDetails details) {
|
||||||
Navigator.pop(context);
|
if (dismissible)
|
||||||
},
|
Navigator.pop(context);
|
||||||
behavior: HitTestBehavior.opaque,
|
},
|
||||||
child: new ConstrainedBox(
|
behavior: HitTestBehavior.opaque,
|
||||||
constraints: const BoxConstraints.expand(),
|
child: new ConstrainedBox(
|
||||||
child: color == null ? null : new DecoratedBox(
|
constraints: const BoxConstraints.expand(),
|
||||||
decoration: new BoxDecoration(
|
child: color == null ? null : new DecoratedBox(
|
||||||
color: color
|
decoration: new BoxDecoration(
|
||||||
|
color: color
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:matcher/matcher.dart';
|
||||||
|
|
||||||
|
import '../widgets/semantics_tester.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Dialog is scrollable', (WidgetTester tester) async {
|
testWidgets('Dialog is scrollable', (WidgetTester tester) async {
|
||||||
@ -192,4 +195,38 @@ void main() {
|
|||||||
expect(find.text('Dialog2'), findsOneWidget);
|
expect(find.text('Dialog2'), findsOneWidget);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Dialog hides underlying semantics tree', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||||
|
const String buttonText = 'A button covered by dialog overlay';
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home: const Material(
|
||||||
|
child: const Center(
|
||||||
|
child: const RaisedButton(
|
||||||
|
onPressed: null,
|
||||||
|
child: const Text(buttonText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWithLabel(buttonText));
|
||||||
|
|
||||||
|
final BuildContext context = tester.element(find.text(buttonText));
|
||||||
|
|
||||||
|
const String alertText = 'A button in an overlay alert';
|
||||||
|
showDialog<Null>(
|
||||||
|
context: context,
|
||||||
|
child: const AlertDialog(title: const Text(alertText)),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWithLabel(alertText));
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel(buttonText)));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
import '../widgets/semantics_tester.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Scaffold control test', (WidgetTester tester) async {
|
testWidgets('Scaffold control test', (WidgetTester tester) async {
|
||||||
final Key bodyKey = new UniqueKey();
|
final Key bodyKey = new UniqueKey();
|
||||||
@ -440,4 +442,40 @@ void main() {
|
|||||||
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
|
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Open drawer hides underlying semantics tree', (WidgetTester tester) async {
|
||||||
|
const String bodyLabel = 'I am the body';
|
||||||
|
const String persistentFooterButtonLabel = 'a button on the bottom';
|
||||||
|
const String bottomNavigationBarLabel = 'a bar in an app';
|
||||||
|
const String floatingActionButtonLabel = 'I float in space';
|
||||||
|
const String drawerLabel = 'I am the reason for this test';
|
||||||
|
|
||||||
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||||
|
await tester.pumpWidget(new MaterialApp(home: new Scaffold(
|
||||||
|
body: new Semantics(label: bodyLabel, child: new Container()),
|
||||||
|
persistentFooterButtons: <Widget>[new Semantics(label: persistentFooterButtonLabel, child: new Container())],
|
||||||
|
bottomNavigationBar: new Semantics(label: bottomNavigationBarLabel, child: new Container()),
|
||||||
|
floatingActionButton: new Semantics(label: floatingActionButtonLabel, child: new Container()),
|
||||||
|
drawer: new Drawer(child:new Semantics(label: drawerLabel, child: new Container())),
|
||||||
|
)));
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWithLabel(bodyLabel));
|
||||||
|
expect(semantics, includesNodeWithLabel(persistentFooterButtonLabel));
|
||||||
|
expect(semantics, includesNodeWithLabel(bottomNavigationBarLabel));
|
||||||
|
expect(semantics, includesNodeWithLabel(floatingActionButtonLabel));
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel(drawerLabel)));
|
||||||
|
|
||||||
|
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
|
||||||
|
state.openDrawer();
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel(bodyLabel)));
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel(persistentFooterButtonLabel)));
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel(bottomNavigationBarLabel)));
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel(floatingActionButtonLabel)));
|
||||||
|
expect(semantics, includesNodeWithLabel(drawerLabel));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
149
packages/flutter/test/widgets/semantics_9_test.dart
Normal file
149
packages/flutter/test/widgets/semantics_9_test.dart
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2017 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/widgets.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'semantics_tester.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('BlockSemantics', () {
|
||||||
|
testWidgets('hides semantic nodes of siblings', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||||
|
|
||||||
|
await tester.pumpWidget(new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
new Semantics(
|
||||||
|
label: 'layer#1',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
const BlockSemantics(),
|
||||||
|
new Semantics(
|
||||||
|
label: 'layer#2',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel('layer#1')));
|
||||||
|
|
||||||
|
await tester.pumpWidget(new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
new Semantics(
|
||||||
|
label: 'layer#1',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWithLabel('layer#1'));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('does not hides semantic nodes of siblings outside the current semantic boundary', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||||
|
|
||||||
|
await tester.pumpWidget(new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
new Semantics(
|
||||||
|
label: '#1',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
new Semantics(
|
||||||
|
label: '#2',
|
||||||
|
container: true,
|
||||||
|
child: new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
new Semantics(
|
||||||
|
label: 'NOT#2.1',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
new Semantics(
|
||||||
|
label: '#2.2',
|
||||||
|
child: new BlockSemantics(
|
||||||
|
child: new Semantics(
|
||||||
|
container: true,
|
||||||
|
label: '#2.2.1',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Semantics(
|
||||||
|
label: '#2.3',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Semantics(
|
||||||
|
label: '#3',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWithLabel('#1'));
|
||||||
|
expect(semantics, includesNodeWithLabel('#2'));
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel('NOT#2.1')));
|
||||||
|
expect(semantics, includesNodeWithLabel('#2.2'));
|
||||||
|
expect(semantics, includesNodeWithLabel('#2.2.1'));
|
||||||
|
expect(semantics, includesNodeWithLabel('#2.3'));
|
||||||
|
expect(semantics, includesNodeWithLabel('#3'));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('node is semantic boundary and blocking previously painted nodes', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||||
|
final GlobalKey stackKey = new GlobalKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(new Stack(
|
||||||
|
key: stackKey,
|
||||||
|
children: <Widget>[
|
||||||
|
new Semantics(
|
||||||
|
label: 'NOT#1',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
new BoundaryBlockSemantics(
|
||||||
|
child: new Semantics(
|
||||||
|
label: '#2.1',
|
||||||
|
child: new Container(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Semantics(
|
||||||
|
label: '#3',
|
||||||
|
child: new Container(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(semantics, isNot(includesNodeWithLabel('NOT#1')));
|
||||||
|
expect(semantics, includesNodeWithLabel('#2.1'));
|
||||||
|
expect(semantics, includesNodeWithLabel('#3'));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoundaryBlockSemantics extends SingleChildRenderObjectWidget {
|
||||||
|
const BoundaryBlockSemantics({ Key key, Widget child }) : super(key: key, child: child);
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderBoundaryBlockSemantics createRenderObject(BuildContext context) => new RenderBoundaryBlockSemantics();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderBoundaryBlockSemantics extends RenderProxyBox {
|
||||||
|
RenderBoundaryBlockSemantics({ RenderBox child }) : super(child);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isSemanticBoundary => true;
|
||||||
|
}
|
||||||
|
|
@ -194,6 +194,8 @@ class SemanticsTester {
|
|||||||
String toString() => 'SemanticsTester';
|
String toString() => 'SemanticsTester';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const String _matcherHelp = 'Try dumping the semantics with debugDumpSemanticsTree() from the rendering library to see what the semantics tree looks like.';
|
||||||
|
|
||||||
class _HasSemantics extends Matcher {
|
class _HasSemantics extends Matcher {
|
||||||
const _HasSemantics(this._semantics) : assert(_semantics != null);
|
const _HasSemantics(this._semantics) : assert(_semantics != null);
|
||||||
|
|
||||||
@ -211,30 +213,65 @@ class _HasSemantics extends Matcher {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
|
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
|
||||||
const String help = 'Try dumping the semantics with debugDumpSemanticsTree() from the rendering library to see what the semantics tree looks like.';
|
|
||||||
final TestSemantics testNode = matchState[TestSemantics];
|
final TestSemantics testNode = matchState[TestSemantics];
|
||||||
final SemanticsNode node = matchState[SemanticsNode];
|
final SemanticsNode node = matchState[SemanticsNode];
|
||||||
if (node == null)
|
if (node == null)
|
||||||
return mismatchDescription.add('could not find node with id ${testNode.id}.\n$help');
|
return mismatchDescription.add('could not find node with id ${testNode.id}.\n$_matcherHelp');
|
||||||
if (testNode.id != node.id)
|
if (testNode.id != node.id)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}.\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}.\n$_matcherHelp');
|
||||||
final SemanticsData data = node.getSemanticsData();
|
final SemanticsData data = node.getSemanticsData();
|
||||||
if (testNode.flags != data.flags)
|
if (testNode.flags != data.flags)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}.\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}.\n$_matcherHelp');
|
||||||
if (testNode.actions != data.actions)
|
if (testNode.actions != data.actions)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}.\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}.\n$_matcherHelp');
|
||||||
if (testNode.label != data.label)
|
if (testNode.label != data.label)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}".\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}".\n$_matcherHelp');
|
||||||
if (testNode.rect != data.rect)
|
if (testNode.rect != data.rect)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}.\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}.\n$_matcherHelp');
|
||||||
if (testNode.transform != data.transform)
|
if (testNode.transform != data.transform)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:.\n${data.transform}.\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:.\n${data.transform}.\n$_matcherHelp');
|
||||||
final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount;
|
final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount;
|
||||||
if (testNode.children.length != childrenCount)
|
if (testNode.children.length != childrenCount)
|
||||||
return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} children but found $childrenCount.\n$help');
|
return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} children but found $childrenCount.\n$_matcherHelp');
|
||||||
return mismatchDescription;
|
return mismatchDescription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts that a [SemanticsTester] has a semantics tree that exactly matches the given semantics.
|
/// Asserts that a [SemanticsTester] has a semantics tree that exactly matches the given semantics.
|
||||||
Matcher hasSemantics(TestSemantics semantics) => new _HasSemantics(semantics);
|
Matcher hasSemantics(TestSemantics semantics) => new _HasSemantics(semantics);
|
||||||
|
|
||||||
|
class _IncludesNodeWithLabel extends Matcher {
|
||||||
|
const _IncludesNodeWithLabel(this._label) : assert(_label != null);
|
||||||
|
|
||||||
|
final String _label;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||||
|
bool result = false;
|
||||||
|
SemanticsNodeVisitor visitor;
|
||||||
|
visitor = (SemanticsNode node) {
|
||||||
|
if (node.label == _label) {
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
node.visitChildren(visitor);
|
||||||
|
}
|
||||||
|
return !result;
|
||||||
|
};
|
||||||
|
final SemanticsNode root = item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;
|
||||||
|
visitor(root);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
return description.add('includes node with label "$_label"');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
|
||||||
|
return mismatchDescription.add('could not find node with label "$_label".\n$_matcherHelp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that a node in the semantics tree of [SemanticsTester] has [label].
|
||||||
|
Matcher includesNodeWithLabel(String label) => new _IncludesNodeWithLabel(label);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user