mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Redesign Semantic Tree Compilation Algorithm (#12605)
* Oct 12 10:12am * implicit_semantics_test.dart passes * refactoring * works in nice * minor rename * more doc comments * to be explicit check better * fix test * ++ * ++ * semantics_9_test (BlockSemantics) and implicit_semantics_test are passing * doc updates * tiny refactor * fix static errors in tests * fix gesture detector * ++ * ++ * geometry * ++ * remove noGeometry * revert test * + * all tests but scrolling/clipping pass * clipping works * scrolling halfway * sliver tests pass * ALL TESTS PASS * SemanticsNode changed * docs and tiny fixes * card test * more doc comments * remove missed print * more tests * make test pass on Linux * remove changes to intellij proj file * review comments
This commit is contained in:
parent
d47d268719
commit
f8a2bd20e9
@ -251,48 +251,46 @@ class RecipeCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new MergeSemantics(
|
||||
child: new GestureDetector(
|
||||
onTap: onTap,
|
||||
child: new Card(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Hero(
|
||||
tag: 'packages/$_kGalleryAssetsPackage/${recipe.imagePath}',
|
||||
child: new Image.asset(
|
||||
recipe.imagePath,
|
||||
package: recipe.imagePackage,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
return new GestureDetector(
|
||||
onTap: onTap,
|
||||
child: new Card(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Hero(
|
||||
tag: 'packages/$_kGalleryAssetsPackage/${recipe.imagePath}',
|
||||
child: new Image.asset(
|
||||
recipe.imagePath,
|
||||
package: recipe.imagePackage,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
new Expanded(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: new Image.asset(
|
||||
recipe.ingredientsImagePath,
|
||||
package: recipe.ingredientsImagePackage,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: new Image.asset(
|
||||
recipe.ingredientsImagePath,
|
||||
package: recipe.ingredientsImagePackage,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Text(recipe.name, style: titleStyle, softWrap: false, overflow: TextOverflow.ellipsis),
|
||||
new Text(recipe.author, style: authorStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Text(recipe.name, style: titleStyle, softWrap: false, overflow: TextOverflow.ellipsis),
|
||||
new Text(recipe.author, style: authorStyle),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -31,20 +31,18 @@ class GalleryItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new MergeSemantics(
|
||||
child: new ListTile(
|
||||
title: new Text(title),
|
||||
subtitle: new Text(subtitle),
|
||||
onTap: () {
|
||||
if (routeName != null) {
|
||||
Timeline.instantSync('Start Transition', arguments: <String, String>{
|
||||
'from': '/',
|
||||
'to': routeName
|
||||
});
|
||||
Navigator.pushNamed(context, routeName);
|
||||
}
|
||||
return new ListTile(
|
||||
title: new Text(title),
|
||||
subtitle: new Text(subtitle),
|
||||
onTap: () {
|
||||
if (routeName != null) {
|
||||
Timeline.instantSync('Start Transition', arguments: <String, String>{
|
||||
'from': '/',
|
||||
'to': routeName
|
||||
});
|
||||
Navigator.pushNamed(context, routeName);
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ final Duration _kDiscreteTransitionDuration = const Duration(milliseconds: 500);
|
||||
|
||||
const double _kAdjustmentUnit = 0.1; // Matches iOS implementation of material slider.
|
||||
|
||||
class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsActionHandler {
|
||||
class _RenderCupertinoSlider extends RenderConstrainedBox {
|
||||
_RenderCupertinoSlider({
|
||||
@required double value,
|
||||
int divisions,
|
||||
@ -253,7 +253,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
|
||||
final bool wasInteractive = isInteractive;
|
||||
_onChanged = value;
|
||||
if (wasInteractive != isInteractive)
|
||||
markNeedsSemanticsUpdate(noGeometry: true);
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
TextDirection get textDirection => _textDirection;
|
||||
@ -379,31 +379,25 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements SemanticsAc
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => isInteractive;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode semantics) {
|
||||
if (isInteractive)
|
||||
semantics.addAdjustmentActions();
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(SemanticsAction action) {
|
||||
final double unit = divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
|
||||
switch (action) {
|
||||
case SemanticsAction.increase:
|
||||
if (isInteractive)
|
||||
onChanged((value + unit).clamp(0.0, 1.0));
|
||||
break;
|
||||
case SemanticsAction.decrease:
|
||||
if (isInteractive)
|
||||
onChanged((value - unit).clamp(0.0, 1.0));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
config.isSemanticBoundary = isInteractive;
|
||||
if (isInteractive) {
|
||||
config.addAction(SemanticsAction.increase, _increaseAction);
|
||||
config.addAction(SemanticsAction.decrease, _decreaseAction);
|
||||
}
|
||||
}
|
||||
|
||||
double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
|
||||
|
||||
void _increaseAction() {
|
||||
if (isInteractive)
|
||||
onChanged((value + _semanticActionUnit).clamp(0.0, 1.0));
|
||||
}
|
||||
|
||||
void _decreaseAction() {
|
||||
if (isInteractive)
|
||||
onChanged((value - _semanticActionUnit).clamp(0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ const Color _kTrackColor = CupertinoColors.lightBackgroundGray;
|
||||
const Duration _kReactionDuration = const Duration(milliseconds: 300);
|
||||
const Duration _kToggleDuration = const Duration(milliseconds: 200);
|
||||
|
||||
class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsActionHandler {
|
||||
class _RenderCupertinoSwitch extends RenderConstrainedBox {
|
||||
_RenderCupertinoSwitch({
|
||||
@required bool value,
|
||||
@required Color activeColor,
|
||||
@ -214,7 +214,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
|
||||
if (value == _value)
|
||||
return;
|
||||
_value = value;
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: true);
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: true);
|
||||
_position
|
||||
..curve = Curves.ease
|
||||
..reverseCurve = Curves.ease.flipped;
|
||||
@ -254,7 +254,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
|
||||
_onChanged = value;
|
||||
if (wasInteractive != isInteractive) {
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate(noGeometry: true);
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,23 +375,13 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => isInteractive;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode semantics) {
|
||||
semantics
|
||||
..hasCheckedState = true
|
||||
..isChecked = _value;
|
||||
config.isSemanticBoundary = isInteractive;
|
||||
if (isInteractive)
|
||||
semantics.addAction(SemanticsAction.tap);
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(SemanticsAction action) {
|
||||
if (action == SemanticsAction.tap)
|
||||
_handleTap();
|
||||
config.addAction(SemanticsAction.tap, _handleTap);
|
||||
config.isChecked = _value;
|
||||
}
|
||||
|
||||
final CupertinoThumbPainter _thumbPainter = new CupertinoThumbPainter();
|
||||
|
||||
@ -80,14 +80,17 @@ class Card extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
margin: const EdgeInsets.all(4.0),
|
||||
child: new Material(
|
||||
color: color,
|
||||
type: MaterialType.card,
|
||||
elevation: elevation,
|
||||
child: child
|
||||
)
|
||||
return new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
margin: const EdgeInsets.all(4.0),
|
||||
child: new Material(
|
||||
color: color,
|
||||
type: MaterialType.card,
|
||||
elevation: elevation,
|
||||
child: child
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,7 +307,7 @@ double _getPreferredTotalHeight(String label) {
|
||||
return 2 * _kReactionRadius + _getAdditionalHeightForLabel(label);
|
||||
}
|
||||
|
||||
class _RenderSlider extends RenderBox implements SemanticsActionHandler {
|
||||
class _RenderSlider extends RenderBox {
|
||||
_RenderSlider({
|
||||
@required double value,
|
||||
int divisions,
|
||||
@ -441,7 +441,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
|
||||
_onChanged = value;
|
||||
if (wasInteractive != isInteractive) {
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate(noGeometry: true);
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -708,31 +708,25 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => isInteractive;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode semantics) {
|
||||
if (isInteractive)
|
||||
semantics.addAdjustmentActions();
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(SemanticsAction action) {
|
||||
final double unit = divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
|
||||
switch (action) {
|
||||
case SemanticsAction.increase:
|
||||
if (isInteractive)
|
||||
onChanged((value + unit).clamp(0.0, 1.0));
|
||||
break;
|
||||
case SemanticsAction.decrease:
|
||||
if (isInteractive)
|
||||
onChanged((value - unit).clamp(0.0, 1.0));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
config.isSemanticBoundary = isInteractive;
|
||||
if (isInteractive) {
|
||||
config.addAction(SemanticsAction.increase, _increaseAction);
|
||||
config.addAction(SemanticsAction.decrease, _decreaseAction);
|
||||
}
|
||||
}
|
||||
|
||||
double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
|
||||
|
||||
void _increaseAction() {
|
||||
if (isInteractive)
|
||||
onChanged((value + _semanticActionUnit).clamp(0.0, 1.0));
|
||||
}
|
||||
|
||||
void _decreaseAction() {
|
||||
if (isInteractive)
|
||||
onChanged((value - _semanticActionUnit).clamp(0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -754,22 +754,20 @@ class _TabBarState extends State<TabBar> {
|
||||
// reflect the intrinsic width of their labels.
|
||||
final int tabCount = widget.tabs.length;
|
||||
for (int index = 0; index < tabCount; index++) {
|
||||
wrappedTabs[index] = new MergeSemantics(
|
||||
child: new Stack(
|
||||
children: <Widget>[
|
||||
new InkWell(
|
||||
onTap: () { _handleTap(index); },
|
||||
child: new Padding(
|
||||
padding: new EdgeInsets.only(bottom: widget.indicatorWeight),
|
||||
child: wrappedTabs[index],
|
||||
wrappedTabs[index] = new InkWell(
|
||||
onTap: () { _handleTap(index); },
|
||||
child: new Padding(
|
||||
padding: new EdgeInsets.only(bottom: widget.indicatorWeight),
|
||||
child: new Stack(
|
||||
children: <Widget>[
|
||||
wrappedTabs[index],
|
||||
new Semantics(
|
||||
selected: index == _currentIndex,
|
||||
// TODO(goderbauer): I10N-ify
|
||||
label: 'Tab ${index + 1} of $tabCount',
|
||||
),
|
||||
),
|
||||
new Semantics(
|
||||
selected: index == _currentIndex,
|
||||
// TODO(goderbauer): I10N-ify
|
||||
label: 'Tab ${index + 1} of $tabCount',
|
||||
),
|
||||
],
|
||||
]
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!widget.isScrollable)
|
||||
|
||||
@ -18,7 +18,7 @@ final Tween<double> _kRadialReactionRadiusTween = new Tween<double>(begin: 0.0,
|
||||
/// This class handles storing the current value, dispatching ValueChanged on a
|
||||
/// tap gesture and driving a changed animation. Subclasses are responsible for
|
||||
/// painting.
|
||||
abstract class RenderToggleable extends RenderConstrainedBox implements SemanticsActionHandler {
|
||||
abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
/// Creates a toggleable render object.
|
||||
///
|
||||
/// The [value], [activeColor], and [inactiveColor] arguments must not be
|
||||
@ -122,7 +122,7 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic
|
||||
if (value == _value)
|
||||
return;
|
||||
_value = value;
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: true);
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: true);
|
||||
_position
|
||||
..curve = Curves.easeIn
|
||||
..reverseCurve = Curves.easeOut;
|
||||
@ -178,7 +178,7 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic
|
||||
_onChanged = value;
|
||||
if (wasInteractive != isInteractive) {
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate(noGeometry: true);
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,23 +283,13 @@ abstract class RenderToggleable extends RenderConstrainedBox implements Semantic
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => isInteractive;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode semantics) {
|
||||
semantics
|
||||
..hasCheckedState = true
|
||||
..isChecked = _value;
|
||||
config.isSemanticBoundary = isInteractive;
|
||||
if (isInteractive)
|
||||
semantics.addAction(SemanticsAction.tap);
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(SemanticsAction action) {
|
||||
if (action == SemanticsAction.tap)
|
||||
_handleTap();
|
||||
config.addAction(SemanticsAction.tap, _handleTap);
|
||||
config.isChecked = _value;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -404,11 +404,11 @@ class RenderParagraph extends RenderBox {
|
||||
}
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
node.label = text.toPlainText();
|
||||
node.textDirection = textDirection;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config
|
||||
..label = text.toPlainText()
|
||||
..textDirection = textDirection;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -2860,7 +2860,7 @@ class RenderMetaData extends RenderProxyBoxWithHitTestBehavior {
|
||||
|
||||
/// Listens for the specified gestures from the semantics server (e.g.
|
||||
/// an accessibility tool).
|
||||
class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsActionHandler {
|
||||
class RenderSemanticsGestureHandler extends RenderProxyBox {
|
||||
/// Creates a render object that listens for specific semantic gestures.
|
||||
///
|
||||
/// The [scrollFactor] argument must not be null.
|
||||
@ -2937,11 +2937,11 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
set onTap(GestureTapCallback value) {
|
||||
if (_onTap == value)
|
||||
return;
|
||||
final bool wasSemanticBoundary = isSemanticBoundary;
|
||||
final bool hadHandlers = _hasHandlers;
|
||||
final bool hadHandler = _onTap != null;
|
||||
_onTap = value;
|
||||
if ((value != null) != hadHandler)
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: isSemanticBoundary == wasSemanticBoundary);
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: _hasHandlers == hadHandlers);
|
||||
}
|
||||
|
||||
/// Called when the user presses on the render object for a long period of time.
|
||||
@ -2950,11 +2950,11 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
set onLongPress(GestureLongPressCallback value) {
|
||||
if (_onLongPress == value)
|
||||
return;
|
||||
final bool wasSemanticBoundary = isSemanticBoundary;
|
||||
final bool hadHandlers = _hasHandlers;
|
||||
final bool hadHandler = _onLongPress != null;
|
||||
_onLongPress = value;
|
||||
if ((value != null) != hadHandler)
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: isSemanticBoundary == wasSemanticBoundary);
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: _hasHandlers == hadHandlers);
|
||||
}
|
||||
|
||||
/// Called when the user scrolls to the left or to the right.
|
||||
@ -2963,11 +2963,11 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
set onHorizontalDragUpdate(GestureDragUpdateCallback value) {
|
||||
if (_onHorizontalDragUpdate == value)
|
||||
return;
|
||||
final bool wasSemanticBoundary = isSemanticBoundary;
|
||||
final bool hadHandlers = _hasHandlers;
|
||||
final bool hadHandler = _onHorizontalDragUpdate != null;
|
||||
_onHorizontalDragUpdate = value;
|
||||
if ((value != null) != hadHandler)
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: isSemanticBoundary == wasSemanticBoundary);
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: _hasHandlers == hadHandlers);
|
||||
}
|
||||
|
||||
/// Called when the user scrolls up or down.
|
||||
@ -2976,11 +2976,11 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
set onVerticalDragUpdate(GestureDragUpdateCallback value) {
|
||||
if (_onVerticalDragUpdate == value)
|
||||
return;
|
||||
final bool wasSemanticBoundary = isSemanticBoundary;
|
||||
final bool hadHandlers = _hasHandlers;
|
||||
final bool hadHandler = _onVerticalDragUpdate != null;
|
||||
_onVerticalDragUpdate = value;
|
||||
if ((value != null) != hadHandler)
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: isSemanticBoundary == wasSemanticBoundary);
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: _hasHandlers == hadHandlers);
|
||||
}
|
||||
|
||||
/// The fraction of the dimension of this render box to use when
|
||||
@ -2990,8 +2990,7 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
/// leftwards drag.
|
||||
double scrollFactor;
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary {
|
||||
bool get _hasHandlers {
|
||||
return onTap != null
|
||||
|| onLongPress != null
|
||||
|| onHorizontalDragUpdate != null
|
||||
@ -2999,7 +2998,38 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
}
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => isSemanticBoundary ? _annotate : null;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
config.isSemanticBoundary = _hasHandlers;
|
||||
|
||||
// TODO(goderbauer): this needs to be set even when there is only potential
|
||||
// for this to become a scroll view.
|
||||
config.explicitChildNodes = onHorizontalDragUpdate != null
|
||||
|| onVerticalDragUpdate != null;
|
||||
|
||||
final Map<SemanticsAction, VoidCallback> actions = <SemanticsAction, VoidCallback>{};
|
||||
if (onTap != null)
|
||||
actions[SemanticsAction.tap] = onTap;
|
||||
if (onLongPress != null)
|
||||
actions[SemanticsAction.longPress] = onLongPress;
|
||||
if (onHorizontalDragUpdate != null) {
|
||||
actions[SemanticsAction.scrollRight] = _performSemanticScrollRight;
|
||||
actions[SemanticsAction.scrollLeft] = _performSemanticScrollLeft;
|
||||
}
|
||||
if (onVerticalDragUpdate != null) {
|
||||
actions[SemanticsAction.scrollUp] = _performSemanticScrollUp;
|
||||
actions[SemanticsAction.scrollDown] = _performSemanticScrollDown;
|
||||
}
|
||||
|
||||
final Iterable<SemanticsAction> actionsToAdd = validActions ?? actions.keys;
|
||||
|
||||
for (SemanticsAction action in actionsToAdd) {
|
||||
final VoidCallback handler = actions[action];
|
||||
if (handler != null)
|
||||
config.addAction(action, handler);
|
||||
}
|
||||
}
|
||||
|
||||
SemanticsNode _innerNode;
|
||||
SemanticsNode _annotatedNode;
|
||||
@ -3011,114 +3041,70 @@ class RenderSemanticsGestureHandler extends RenderProxyBox implements SemanticsA
|
||||
}
|
||||
|
||||
@override
|
||||
void assembleSemanticsNode(SemanticsNode node, Iterable<SemanticsNode> children) {
|
||||
if (!node.hasTag(useTwoPaneSemantics)) {
|
||||
super.assembleSemanticsNode(node, children);
|
||||
void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
|
||||
if (children.isEmpty || !children.first.isTagged(useTwoPaneSemantics)) {
|
||||
_annotatedNode = node;
|
||||
super.assembleSemanticsNode(node, config, children);
|
||||
return;
|
||||
}
|
||||
|
||||
_innerNode ??= new SemanticsNode(handler: this, showOnScreen: showOnScreen);
|
||||
_innerNode ??= new SemanticsNode(showOnScreen: showOnScreen);
|
||||
_innerNode
|
||||
..wasAffectedByClip = node.wasAffectedByClip
|
||||
..isMergedIntoParent = node.isPartOfNodeMerging
|
||||
..rect = Offset.zero & node.rect.size;
|
||||
|
||||
semanticsAnnotator(_innerNode);
|
||||
_annotatedNode = _innerNode;
|
||||
|
||||
final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode];
|
||||
final List<SemanticsNode> included = <SemanticsNode>[];
|
||||
for (SemanticsNode child in children) {
|
||||
if (child.hasTag(excludeFromScrolling))
|
||||
assert(child.isTagged(useTwoPaneSemantics));
|
||||
if (child.isTagged(excludeFromScrolling))
|
||||
excluded.add(child);
|
||||
else
|
||||
included.add(child);
|
||||
}
|
||||
node.addChildren(excluded);
|
||||
_innerNode.addChildren(included);
|
||||
_innerNode.finalizeChildren();
|
||||
node.finalizeChildren();
|
||||
node.updateWith(config: null, childrenInInversePaintOrder: excluded);
|
||||
_innerNode.updateWith(config: config, childrenInInversePaintOrder: included);
|
||||
}
|
||||
|
||||
@override
|
||||
void resetSemantics() {
|
||||
_innerNode?.reset();
|
||||
super.resetSemantics();
|
||||
}
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
_annotatedNode = node;
|
||||
List<SemanticsAction> actions = <SemanticsAction>[];
|
||||
if (onTap != null)
|
||||
actions.add(SemanticsAction.tap);
|
||||
if (onLongPress != null)
|
||||
actions.add(SemanticsAction.longPress);
|
||||
void _performSemanticScrollLeft() {
|
||||
if (onHorizontalDragUpdate != null) {
|
||||
actions.add(SemanticsAction.scrollRight);
|
||||
actions.add(SemanticsAction.scrollLeft);
|
||||
final double primaryDelta = size.width * -scrollFactor;
|
||||
onHorizontalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
if (onVerticalDragUpdate != null) {
|
||||
actions.add(SemanticsAction.scrollUp);
|
||||
actions.add(SemanticsAction.scrollDown);
|
||||
}
|
||||
|
||||
// If a set of validActions has been provided only expose those.
|
||||
if (validActions != null)
|
||||
actions = actions.where((SemanticsAction action) => validActions.contains(action)).toList();
|
||||
|
||||
actions.forEach(node.addAction);
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(SemanticsAction action) {
|
||||
switch (action) {
|
||||
case SemanticsAction.tap:
|
||||
if (onTap != null)
|
||||
onTap();
|
||||
break;
|
||||
case SemanticsAction.longPress:
|
||||
if (onLongPress != null)
|
||||
onLongPress();
|
||||
break;
|
||||
case SemanticsAction.scrollLeft:
|
||||
if (onHorizontalDragUpdate != null) {
|
||||
final double primaryDelta = size.width * -scrollFactor;
|
||||
onHorizontalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
break;
|
||||
case SemanticsAction.scrollRight:
|
||||
if (onHorizontalDragUpdate != null) {
|
||||
final double primaryDelta = size.width * scrollFactor;
|
||||
onHorizontalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
break;
|
||||
case SemanticsAction.scrollUp:
|
||||
if (onVerticalDragUpdate != null) {
|
||||
final double primaryDelta = size.height * -scrollFactor;
|
||||
onVerticalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
break;
|
||||
case SemanticsAction.scrollDown:
|
||||
if (onVerticalDragUpdate != null) {
|
||||
final double primaryDelta = size.height * scrollFactor;
|
||||
onVerticalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
break;
|
||||
case SemanticsAction.increase:
|
||||
case SemanticsAction.decrease:
|
||||
assert(false);
|
||||
break;
|
||||
void _performSemanticScrollRight() {
|
||||
if (onHorizontalDragUpdate != null) {
|
||||
final double primaryDelta = size.width * scrollFactor;
|
||||
onHorizontalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _performSemanticScrollUp() {
|
||||
if (onVerticalDragUpdate != null) {
|
||||
final double primaryDelta = size.height * -scrollFactor;
|
||||
onVerticalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _performSemanticScrollDown() {
|
||||
if (onVerticalDragUpdate != null) {
|
||||
final double primaryDelta = size.height * scrollFactor;
|
||||
onVerticalDragUpdate(new DragUpdateDetails(
|
||||
delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
|
||||
globalPosition: localToGlobal(size.center(Offset.zero)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3150,28 +3136,27 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
RenderSemanticsAnnotations({
|
||||
RenderBox child,
|
||||
bool container: false,
|
||||
bool explicitChildNodes,
|
||||
bool checked,
|
||||
bool selected,
|
||||
String label,
|
||||
TextDirection textDirection,
|
||||
}) : assert(container != null),
|
||||
_container = container,
|
||||
_explicitChildNodes = explicitChildNodes,
|
||||
_checked = checked,
|
||||
_selected = selected,
|
||||
_label = label,
|
||||
_textDirection = textDirection,
|
||||
super(child);
|
||||
|
||||
/// If 'container' is true, this RenderObject will introduce a new
|
||||
/// If 'container' is true, this [RenderObject] will introduce a new
|
||||
/// node in the semantics tree. Otherwise, the semantics will be
|
||||
/// merged with the semantics of any ancestors.
|
||||
///
|
||||
/// The 'container' flag is implicitly set to true on the immediate
|
||||
/// semantics-providing descendants of a node where multiple
|
||||
/// children have semantics or have descendants providing semantics.
|
||||
/// In other words, the semantics of siblings are not merged. To
|
||||
/// merge the semantics of an entire subtree, including siblings,
|
||||
/// you can use a [RenderMergeSemantics].
|
||||
/// Whether descendants of this [RenderObject] can add their semantic information
|
||||
/// to the [SemanticsNode] introduced by this configuration is controlled by
|
||||
/// [explicitChildNodes].
|
||||
bool get container => _container;
|
||||
bool _container;
|
||||
set container(bool value) {
|
||||
@ -3182,6 +3167,28 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// Whether descendants of this [RenderObject] are allowed to add semantic
|
||||
/// information to the [SemanticsNode] annotated by this widget.
|
||||
///
|
||||
/// When set to false descendants are allowed to annotate [SemanticNode]s of
|
||||
/// their parent with the semantic information they want to contribute to the
|
||||
/// semantic tree.
|
||||
/// When set to true the only way for descendants to contribute semantic
|
||||
/// information to the semantic tree is to introduce new explicit
|
||||
/// [SemanticNode]s to the tree.
|
||||
///
|
||||
/// This setting is often used in combination with [isSemanticBoundary] to
|
||||
/// create semantic boundaries that are either writable or not for children.
|
||||
bool get explicitChildNodes => _explicitChildNodes;
|
||||
bool _explicitChildNodes;
|
||||
set explicitChildNodes(bool value) {
|
||||
assert(value != null);
|
||||
if (_explicitChildNodes == value)
|
||||
return;
|
||||
_explicitChildNodes = value;
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and
|
||||
/// the [SemanticsNode.isChecked] semantic to the given value.
|
||||
bool get checked => _checked;
|
||||
@ -3231,23 +3238,18 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => container;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
config.isSemanticBoundary = container;
|
||||
config.explicitChildNodes = explicitChildNodes;
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null || textDirection != null ? _annotate : null;
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
if (checked != null) {
|
||||
node
|
||||
..hasCheckedState = true
|
||||
..isChecked = checked;
|
||||
}
|
||||
if (checked != null)
|
||||
config.isChecked = checked;
|
||||
if (selected != null)
|
||||
node.isSelected = selected;
|
||||
config.isSelected = selected;
|
||||
if (label != null)
|
||||
node.label = label;
|
||||
config.label = label;
|
||||
if (textDirection != null)
|
||||
node.textDirection = textDirection;
|
||||
config.textDirection = textDirection;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3262,7 +3264,10 @@ class RenderBlockSemantics extends RenderProxyBox {
|
||||
RenderBlockSemantics({ RenderBox child }) : super(child);
|
||||
|
||||
@override
|
||||
bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config.isBlockingSemanticsOfPreviouslyPaintedNodes = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Causes the semantics of all descendants to be merged into this
|
||||
@ -3277,8 +3282,12 @@ class RenderMergeSemantics extends RenderProxyBox {
|
||||
RenderMergeSemantics({ RenderBox child }) : super(child);
|
||||
|
||||
@override
|
||||
bool get isMergingSemanticsOfDescendants => true;
|
||||
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config
|
||||
..isSemanticBoundary = true
|
||||
..isMergingSemanticsOfDescendants = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Excludes this subtree from the semantic tree.
|
||||
|
||||
@ -18,31 +18,6 @@ import 'semantics_event.dart';
|
||||
export 'dart:ui' show SemanticsAction;
|
||||
export 'semantics_event.dart';
|
||||
|
||||
/// Interface for [RenderObject]s to implement when they want to support
|
||||
/// being tapped, etc.
|
||||
///
|
||||
/// The handler will only be called for a particular flag if that flag is set
|
||||
/// (e.g. [performAction] will only be called with [SemanticsAction.tap] if
|
||||
/// [SemanticsNode.addAction] was called for [SemanticsAction.tap].)
|
||||
abstract class SemanticsActionHandler { // ignore: one_member_abstracts
|
||||
/// Called when the object implementing this interface receives a
|
||||
/// [SemanticsAction]. For example, if the user of an accessibility tool
|
||||
/// instructs their device that they wish to tap a button, the [RenderObject]
|
||||
/// behind that button would have its [performAction] method called with the
|
||||
/// [SemanticsAction.tap] action.
|
||||
void performAction(SemanticsAction action);
|
||||
}
|
||||
|
||||
/// Signature for functions returned by [RenderObject.semanticsAnnotator].
|
||||
///
|
||||
/// These callbacks are called with the [SemanticsNode] object that
|
||||
/// corresponds to the [RenderObject]. (One [SemanticsNode] can
|
||||
/// correspond to multiple [RenderObject] objects.)
|
||||
///
|
||||
/// See [RenderObject.semanticsAnnotator] for details on the
|
||||
/// contract that semantic annotators must follow.
|
||||
typedef void SemanticsAnnotator(SemanticsNode semantics);
|
||||
|
||||
/// Signature for a function that is called for each [SemanticsNode].
|
||||
///
|
||||
/// Return false to stop visiting nodes.
|
||||
@ -103,14 +78,13 @@ class SemanticsData extends Diagnosticable {
|
||||
@required this.label,
|
||||
@required this.textDirection,
|
||||
@required this.rect,
|
||||
@required this.tags,
|
||||
this.tags,
|
||||
this.transform,
|
||||
}) : assert(flags != null),
|
||||
assert(actions != null),
|
||||
assert(label != null),
|
||||
assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'),
|
||||
assert(rect != null),
|
||||
assert(tags != null);
|
||||
assert(rect != null);
|
||||
|
||||
/// A bit field of [SemanticsFlags] that apply to this node.
|
||||
final int flags;
|
||||
@ -223,22 +197,18 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
/// Each semantic node has a unique identifier that is assigned when the node
|
||||
/// is created.
|
||||
SemanticsNode({
|
||||
SemanticsActionHandler handler,
|
||||
VoidCallback showOnScreen,
|
||||
}) : id = _generateNewId(),
|
||||
_showOnScreen = showOnScreen,
|
||||
_actionHandler = handler;
|
||||
_showOnScreen = showOnScreen;
|
||||
|
||||
/// Creates a semantic node to represent the root of the semantics tree.
|
||||
///
|
||||
/// The root node is assigned an identifier of zero.
|
||||
SemanticsNode.root({
|
||||
SemanticsActionHandler handler,
|
||||
VoidCallback showOnScreen,
|
||||
SemanticsOwner owner,
|
||||
}) : id = 0,
|
||||
_showOnScreen = showOnScreen,
|
||||
_actionHandler = handler {
|
||||
_showOnScreen = showOnScreen {
|
||||
attach(owner);
|
||||
}
|
||||
|
||||
@ -254,11 +224,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
/// they are created.
|
||||
final int id;
|
||||
|
||||
final SemanticsActionHandler _actionHandler;
|
||||
final VoidCallback _showOnScreen;
|
||||
|
||||
// GEOMETRY
|
||||
// These are automatically handled by RenderObject's own logic
|
||||
|
||||
/// The transform from this node's coordinate system to its parent's coordinate system.
|
||||
///
|
||||
@ -285,11 +253,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether [rect] was clipped by ancestors.
|
||||
/// Whether [rect] was affected by a clip from an ancestors.
|
||||
///
|
||||
/// This is only true if the [rect] of this [SemanticsNode] has been altered
|
||||
/// due to clipping by an ancestor. If ancestors have been clipped, but the
|
||||
/// [rect] of this node was unaffected it will be false.
|
||||
/// If this is true it means that an ancestor imposed a clip on this
|
||||
/// [SemanticsNode]. However, it does not mean that the clip had any effect
|
||||
/// on the [rect] whatsoever.
|
||||
bool wasAffectedByClip = false;
|
||||
|
||||
/// Whether the node is invisible.
|
||||
@ -304,57 +272,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
/// currently shown on screen.
|
||||
bool get isInvisible => !isMergedIntoParent && rect.isEmpty;
|
||||
|
||||
// FLAGS AND LABELS
|
||||
// These are supposed to be set by SemanticsAnnotator obtained from getSemanticsAnnotators
|
||||
|
||||
int _actions = 0;
|
||||
|
||||
/// Adds the given action to the set of semantic actions.
|
||||
///
|
||||
/// If the user chooses to perform an action,
|
||||
/// [SemanticsActionHandler.performAction] will be called with the chosen
|
||||
/// action.
|
||||
void addAction(SemanticsAction action) {
|
||||
assert(action != null);
|
||||
final int index = action.index;
|
||||
if ((_actions & index) == 0) {
|
||||
_actions |= index;
|
||||
_markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the [SemanticsAction.scrollLeft] and [SemanticsAction.scrollRight] actions.
|
||||
void addHorizontalScrollingActions() {
|
||||
addAction(SemanticsAction.scrollLeft);
|
||||
addAction(SemanticsAction.scrollRight);
|
||||
}
|
||||
|
||||
/// Adds the [SemanticsAction.scrollUp] and [SemanticsAction.scrollDown] actions.
|
||||
void addVerticalScrollingActions() {
|
||||
addAction(SemanticsAction.scrollUp);
|
||||
addAction(SemanticsAction.scrollDown);
|
||||
}
|
||||
|
||||
/// Adds the [SemanticsAction.increase] and [SemanticsAction.decrease] actions.
|
||||
void addAdjustmentActions() {
|
||||
addAction(SemanticsAction.increase);
|
||||
addAction(SemanticsAction.decrease);
|
||||
}
|
||||
|
||||
bool _canPerformAction(SemanticsAction action) {
|
||||
return _actionHandler != null && (_actions & action.index) != 0;
|
||||
}
|
||||
|
||||
/// Whether this node and all of its descendants should be treated as one logical entity.
|
||||
bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
|
||||
bool _mergeAllDescendantsIntoThisNode = false;
|
||||
set mergeAllDescendantsIntoThisNode(bool value) {
|
||||
assert(value != null);
|
||||
if (_mergeAllDescendantsIntoThisNode == value)
|
||||
return;
|
||||
_mergeAllDescendantsIntoThisNode = value;
|
||||
_markDirty();
|
||||
}
|
||||
// MERGING
|
||||
|
||||
/// Whether this node merges its semantic information into an ancestor node.
|
||||
bool get isMergedIntoParent => _isMergedIntoParent;
|
||||
@ -377,129 +295,91 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
/// * [mergeAllDescendantsIntoThisNode]
|
||||
bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
|
||||
|
||||
int _flags = 0;
|
||||
void _setFlag(SemanticsFlags flag, bool value) {
|
||||
final int index = flag.index;
|
||||
if (value) {
|
||||
if ((_flags & index) == 0) {
|
||||
_flags |= index;
|
||||
_markDirty();
|
||||
}
|
||||
} else {
|
||||
if ((_flags & index) != 0) {
|
||||
_flags &= ~index;
|
||||
_markDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Whether this node and all of its descendants should be treated as one logical entity.
|
||||
bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
|
||||
bool _mergeAllDescendantsIntoThisNode = _kEmptyConfig.isMergingSemanticsOfDescendants;
|
||||
|
||||
/// Whether this node has Boolean state that can be controlled by the user.
|
||||
bool get hasCheckedState => (_flags & SemanticsFlags.hasCheckedState.index) != 0;
|
||||
set hasCheckedState(bool value) => _setFlag(SemanticsFlags.hasCheckedState, value);
|
||||
|
||||
/// If this node has Boolean state that can be controlled by the user, whether
|
||||
/// that state is on or off, corresponding to true and false, respectively.
|
||||
bool get isChecked => (_flags & SemanticsFlags.isChecked.index) != 0;
|
||||
set isChecked(bool value) => _setFlag(SemanticsFlags.isChecked, value);
|
||||
// CHILDREN
|
||||
|
||||
/// Whether the current node is selected (true) or not (false).
|
||||
bool get isSelected => (_flags & SemanticsFlags.isSelected.index) != 0;
|
||||
set isSelected(bool value) => _setFlag(SemanticsFlags.isSelected, value);
|
||||
/// Contains the children in inverse hit test order (i.e. paint order).
|
||||
List<SemanticsNode> _children;
|
||||
|
||||
/// A textual description of this node.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get label => _label;
|
||||
String _label = '';
|
||||
set label(String value) {
|
||||
assert(value != null);
|
||||
if (_label != value) {
|
||||
_label = value;
|
||||
_markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// The reading direction for the text in [label].
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
assert(value != null);
|
||||
if (_textDirection != value) {
|
||||
_textDirection = value;
|
||||
_markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
final Set<SemanticsTag> _tags = new Set<SemanticsTag>();
|
||||
|
||||
/// Tags the [SemanticsNode] with [tag].
|
||||
///
|
||||
/// Tags are not sent to the engine. They can be used by a parent
|
||||
/// [SemanticsNode] to figure out how to add the node as a child.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsTag], whose documentation discusses the purposes of tags.
|
||||
/// * [hasTag] to check if the node has a certain tag.
|
||||
void addTag(SemanticsTag tag) {
|
||||
assert(tag != null);
|
||||
_tags.add(tag);
|
||||
}
|
||||
|
||||
/// Check if the [SemanticsNode] is tagged with [tag].
|
||||
///
|
||||
/// Tags can be added and removed with [ensureTag].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SemanticsTag], whose documentation discusses the purposes of tags.
|
||||
bool hasTag(SemanticsTag tag) => _tags.contains(tag);
|
||||
|
||||
/// Restore this node to its default state.
|
||||
void reset() {
|
||||
_actions = 0;
|
||||
_flags = 0;
|
||||
_label = '';
|
||||
_textDirection = null;
|
||||
_mergeAllDescendantsIntoThisNode = false;
|
||||
_tags.clear();
|
||||
_markDirty();
|
||||
}
|
||||
|
||||
List<SemanticsNode> _newChildren;
|
||||
|
||||
/// Append the given children as children of this node.
|
||||
///
|
||||
/// Children must be added in inverse hit test order (i.e. paint order).
|
||||
///
|
||||
/// The [finalizeChildren] method must be called after all children have been
|
||||
/// added.
|
||||
void addChildren(Iterable<SemanticsNode> childrenInInverseHitTestOrder) {
|
||||
_newChildren ??= <SemanticsNode>[];
|
||||
_newChildren.addAll(childrenInInverseHitTestOrder);
|
||||
// we do the asserts afterwards because children is an Iterable
|
||||
// and doing the asserts before would mean the behavior is
|
||||
// different in checked mode vs release mode (if you walk an
|
||||
// iterator after having reached the end, it'll just start over;
|
||||
// the values are not cached).
|
||||
assert(!_newChildren.any((SemanticsNode child) => child == this));
|
||||
void _replaceChildren(List<SemanticsNode> newChildren) {
|
||||
assert(!newChildren.any((SemanticsNode child) => child == this));
|
||||
assert(() {
|
||||
SemanticsNode ancestor = this;
|
||||
while (ancestor.parent is SemanticsNode)
|
||||
ancestor = ancestor.parent;
|
||||
assert(!_newChildren.any((SemanticsNode child) => child == ancestor));
|
||||
assert(!newChildren.any((SemanticsNode child) => child == ancestor));
|
||||
return true;
|
||||
}());
|
||||
assert(() {
|
||||
final Set<SemanticsNode> seenChildren = new Set<SemanticsNode>();
|
||||
for (SemanticsNode child in _newChildren)
|
||||
for (SemanticsNode child in newChildren)
|
||||
assert(seenChildren.add(child)); // check for duplicate adds
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
/// Contains the children in inverse hit test order (i.e. paint order).
|
||||
List<SemanticsNode> _children;
|
||||
// The goal of this function is updating sawChange.
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
child._dead = true;
|
||||
}
|
||||
if (newChildren != null) {
|
||||
for (SemanticsNode child in newChildren) {
|
||||
assert(!child.isInvisible, 'Child with id ${child.id} is invisible and should not be added to tree.');
|
||||
child._dead = false;
|
||||
}
|
||||
}
|
||||
bool sawChange = false;
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children) {
|
||||
if (child._dead) {
|
||||
if (child.parent == this) {
|
||||
// we might have already had our child stolen from us by
|
||||
// another node that is deeper in the tree.
|
||||
dropChild(child);
|
||||
}
|
||||
sawChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newChildren != null) {
|
||||
for (SemanticsNode child in newChildren) {
|
||||
if (child.parent != this) {
|
||||
if (child.parent != null) {
|
||||
// we're rebuilding the tree from the bottom up, so it's possible
|
||||
// that our child was, in the last pass, a child of one of our
|
||||
// ancestors. In that case, we drop the child eagerly here.
|
||||
// TODO(ianh): Find a way to assert that the same node didn't
|
||||
// actually appear in the tree in two places.
|
||||
child.parent?.dropChild(child);
|
||||
}
|
||||
assert(!child.attached);
|
||||
adoptChild(child);
|
||||
sawChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sawChange && _children != null) {
|
||||
assert(newChildren != null);
|
||||
assert(newChildren.length == _children.length);
|
||||
// Did the order change?
|
||||
for (int i = 0; i < _children.length; i++) {
|
||||
if (_children[i].id != newChildren[i].id) {
|
||||
sawChange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
final List<SemanticsNode> oldChildren = _children;
|
||||
_children = newChildren;
|
||||
oldChildren?.clear();
|
||||
newChildren = oldChildren;
|
||||
if (sawChange)
|
||||
_markDirty();
|
||||
}
|
||||
|
||||
/// Whether this node has a non-zero number of children.
|
||||
bool get hasChildren => _children?.isNotEmpty ?? false;
|
||||
@ -522,86 +402,6 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called during the compilation phase after all the children of this node have been compiled.
|
||||
///
|
||||
/// This function lets the semantic node respond to all the changes to its
|
||||
/// child list for the given frame at once instead of needing to process the
|
||||
/// changes incrementally as new children are compiled.
|
||||
void finalizeChildren() {
|
||||
// The goal of this function is updating sawChange.
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
child._dead = true;
|
||||
}
|
||||
if (_newChildren != null) {
|
||||
for (SemanticsNode child in _newChildren) {
|
||||
assert(!child.isInvisible, 'Child with id ${child.id} is invisible and should not be added to tree.');
|
||||
child._dead = false;
|
||||
}
|
||||
}
|
||||
bool sawChange = false;
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children) {
|
||||
if (child._dead) {
|
||||
if (child.parent == this) {
|
||||
// we might have already had our child stolen from us by
|
||||
// another node that is deeper in the tree.
|
||||
dropChild(child);
|
||||
}
|
||||
sawChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_newChildren != null) {
|
||||
for (SemanticsNode child in _newChildren) {
|
||||
if (child.parent != this) {
|
||||
if (child.parent != null) {
|
||||
// we're rebuilding the tree from the bottom up, so it's possible
|
||||
// that our child was, in the last pass, a child of one of our
|
||||
// ancestors. In that case, we drop the child eagerly here.
|
||||
// TODO(ianh): Find a way to assert that the same node didn't
|
||||
// actually appear in the tree in two places.
|
||||
child.parent?.dropChild(child);
|
||||
}
|
||||
assert(!child.attached);
|
||||
adoptChild(child);
|
||||
sawChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sawChange && _children != null) {
|
||||
assert(_newChildren != null);
|
||||
assert(_newChildren.length == _children.length);
|
||||
// Did the order change?
|
||||
for (int i = 0; i < _children.length; i++) {
|
||||
if (_children[i].id != _newChildren[i].id) {
|
||||
sawChange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
final List<SemanticsNode> oldChildren = _children;
|
||||
_children = _newChildren;
|
||||
oldChildren?.clear();
|
||||
_newChildren = oldChildren;
|
||||
if (sawChange)
|
||||
_markDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
SemanticsOwner get owner => super.owner;
|
||||
|
||||
@override
|
||||
SemanticsNode get parent => super.parent;
|
||||
|
||||
@override
|
||||
void redepthChildren() {
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
redepthChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit all the descendants of this node.
|
||||
///
|
||||
/// This function calls visitor for each descendant in a pre-order travseral
|
||||
@ -617,6 +417,22 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
return true;
|
||||
}
|
||||
|
||||
// AbstractNode OVERRIDES
|
||||
|
||||
@override
|
||||
SemanticsOwner get owner => super.owner;
|
||||
|
||||
@override
|
||||
SemanticsNode get parent => super.parent;
|
||||
|
||||
@override
|
||||
void redepthChildren() {
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
redepthChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void attach(SemanticsOwner owner) {
|
||||
super.attach(owner);
|
||||
@ -627,7 +443,6 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
_dirty = false;
|
||||
_markDirty();
|
||||
}
|
||||
assert(isMergedIntoParent == (parent?.isPartOfNodeMerging ?? false));
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
child.attach(owner);
|
||||
@ -656,6 +471,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
_markDirty();
|
||||
}
|
||||
|
||||
// DIRTY MANAGEMENT
|
||||
|
||||
bool _dirty = false;
|
||||
void _markDirty() {
|
||||
if (_dirty)
|
||||
@ -667,6 +484,66 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
|
||||
return _label != config.label ||
|
||||
_flags != config._flags ||
|
||||
_textDirection != config.textDirection ||
|
||||
_actionsAsBitMap(_actions) != _actionsAsBitMap(config._actions) ||
|
||||
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
|
||||
}
|
||||
|
||||
// TAGS, LABELS, ACTIONS
|
||||
|
||||
Map<SemanticsAction, VoidCallback> _actions = _kEmptyConfig._actions;
|
||||
|
||||
/// The [SemanticsTag]s this node is tagged with.
|
||||
///
|
||||
/// Tags are used during the construction of the semantics tree. They are not
|
||||
/// transfered to the engine.
|
||||
Set<SemanticsTag> tags;
|
||||
|
||||
/// Whether this node is tagged with `tag`.
|
||||
bool isTagged(SemanticsTag tag) => tags != null && tags.contains(tag);
|
||||
|
||||
int _flags = _kEmptyConfig._flags;
|
||||
|
||||
bool _hasFlag(SemanticsFlags flag) => _flags & flag.index != 0;
|
||||
|
||||
/// A textual description of this node.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get label => _label;
|
||||
String _label = _kEmptyConfig.label;
|
||||
|
||||
/// The reading direction for [label].
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection = _kEmptyConfig.textDirection;
|
||||
|
||||
int _actionsAsBitMap(Map<SemanticsAction, VoidCallback> actions) {
|
||||
return actions.keys.fold(0, (int prev, SemanticsAction action) => prev |= action.index);
|
||||
}
|
||||
|
||||
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
||||
|
||||
static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration();
|
||||
|
||||
void updateWith({
|
||||
@required SemanticsConfiguration config,
|
||||
@required List<SemanticsNode> childrenInInversePaintOrder,
|
||||
}) {
|
||||
config ??= _kEmptyConfig;
|
||||
if (_isDifferentFromCurrentSemanticAnnotation(config))
|
||||
_markDirty();
|
||||
|
||||
_label = config.label;
|
||||
_flags = config._flags;
|
||||
_textDirection = config.textDirection;
|
||||
_actions = new Map<SemanticsAction, VoidCallback>.from(config._actions);
|
||||
_mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
|
||||
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
||||
}
|
||||
|
||||
|
||||
/// Returns a summary of the semantics for this node.
|
||||
///
|
||||
/// If this node has [mergeAllDescendantsIntoThisNode], then the returned data
|
||||
@ -674,21 +551,25 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
/// returned data matches the data on this node.
|
||||
SemanticsData getSemanticsData() {
|
||||
int flags = _flags;
|
||||
int actions = _actions;
|
||||
int actions = _actionsAsBitMap(_actions);
|
||||
String label = _label;
|
||||
TextDirection textDirection = _textDirection;
|
||||
final Set<SemanticsTag> tags = new Set<SemanticsTag>.from(_tags);
|
||||
Set<SemanticsTag> mergedTags = tags == null ? null : new Set<SemanticsTag>.from(tags);
|
||||
|
||||
if (mergeAllDescendantsIntoThisNode) {
|
||||
_visitDescendants((SemanticsNode node) {
|
||||
assert(node.isMergedIntoParent);
|
||||
flags |= node._flags;
|
||||
actions |= node._actions;
|
||||
actions |= _actionsAsBitMap(node._actions);
|
||||
textDirection ??= node._textDirection;
|
||||
tags.addAll(node._tags);
|
||||
if (node.label.isNotEmpty) {
|
||||
String nestedLabel = node.label;
|
||||
if (textDirection != node.textDirection && node.textDirection != null) {
|
||||
switch (node.textDirection) {
|
||||
if (node.tags != null) {
|
||||
mergedTags ??= new Set<SemanticsTag>();
|
||||
mergedTags.addAll(node.tags);
|
||||
}
|
||||
if (node._label.isNotEmpty) {
|
||||
String nestedLabel = node._label;
|
||||
if (textDirection != node._textDirection && node._textDirection != null) {
|
||||
switch (node._textDirection) {
|
||||
case TextDirection.rtl:
|
||||
nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
@ -713,7 +594,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
textDirection: textDirection,
|
||||
rect: rect,
|
||||
transform: transform,
|
||||
tags: tags,
|
||||
tags: mergedTags,
|
||||
);
|
||||
}
|
||||
|
||||
@ -798,18 +679,13 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
||||
properties.add(new DiagnosticsProperty<Rect>('rect', rect, description: description, showName: false));
|
||||
}
|
||||
properties.add(new FlagProperty('wasAffectedByClip', value: wasAffectedByClip, ifTrue: 'clipped'));
|
||||
final List<String> actions = <String>[];
|
||||
for (SemanticsAction action in SemanticsAction.values.values) {
|
||||
if ((_actions & action.index) != 0)
|
||||
actions.add(describeEnum(action));
|
||||
}
|
||||
final List<String> actions = _actions.keys.map((SemanticsAction action) => describeEnum(action)).toList()..sort();
|
||||
properties.add(new IterableProperty<String>('actions', actions, ifEmpty: null));
|
||||
properties.add(new IterableProperty<SemanticsTag>('tags', _tags, ifEmpty: null));
|
||||
if (hasCheckedState)
|
||||
properties.add(new FlagProperty('isChecked', value: isChecked, ifTrue: 'checked', ifFalse: 'unchecked'));
|
||||
properties.add(new FlagProperty('isSelected', value: isSelected, ifTrue: 'selected'));
|
||||
properties.add(new StringProperty('label', label, defaultValue: ''));
|
||||
properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||
if (_hasFlag(SemanticsFlags.hasCheckedState))
|
||||
properties.add(new FlagProperty('isChecked', value: _hasFlag(SemanticsFlags.isChecked), ifTrue: 'checked', ifFalse: 'unchecked'));
|
||||
properties.add(new FlagProperty('isSelected', value: _hasFlag(SemanticsFlags.isSelected), ifTrue: 'selected'));
|
||||
properties.add(new StringProperty('label', _label, defaultValue: ''));
|
||||
properties.add(new EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
|
||||
}
|
||||
|
||||
/// Returns a string representation of this node and its descendants.
|
||||
@ -939,7 +815,7 @@ class SemanticsOwner extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
|
||||
VoidCallback _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
|
||||
SemanticsNode result = _nodes[id];
|
||||
if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
|
||||
result._visitDescendants((SemanticsNode node) {
|
||||
@ -952,7 +828,7 @@ class SemanticsOwner extends ChangeNotifier {
|
||||
}
|
||||
if (result == null || !result._canPerformAction(action))
|
||||
return null;
|
||||
return result._actionHandler;
|
||||
return result._actions[action];
|
||||
}
|
||||
|
||||
/// Asks the [SemanticsNode] with the given id to perform the given action.
|
||||
@ -961,9 +837,9 @@ class SemanticsOwner extends ChangeNotifier {
|
||||
/// this function does nothing.
|
||||
void performAction(int id, SemanticsAction action) {
|
||||
assert(action != null);
|
||||
final SemanticsActionHandler handler = _getSemanticsActionHandlerForId(id, action);
|
||||
final VoidCallback handler = _getSemanticsActionHandlerForId(id, action);
|
||||
if (handler != null) {
|
||||
handler.performAction(action);
|
||||
handler();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -972,7 +848,7 @@ class SemanticsOwner extends ChangeNotifier {
|
||||
_nodes[id]._showOnScreen();
|
||||
}
|
||||
|
||||
SemanticsActionHandler _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
|
||||
VoidCallback _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
|
||||
if (node.transform != null) {
|
||||
final Matrix4 inverse = new Matrix4.identity();
|
||||
if (inverse.copyInverse(node.transform) == 0.0)
|
||||
@ -990,16 +866,16 @@ class SemanticsOwner extends ChangeNotifier {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result?._actionHandler;
|
||||
return result?._actions[action];
|
||||
}
|
||||
if (node.hasChildren) {
|
||||
for (SemanticsNode child in node._children.reversed) {
|
||||
final SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(child, position, action);
|
||||
final VoidCallback handler = _getSemanticsActionHandlerForPosition(child, position, action);
|
||||
if (handler != null)
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return node._canPerformAction(action) ? node._actionHandler : null;
|
||||
return node._canPerformAction(action) ? node._actions[action] : null;
|
||||
}
|
||||
|
||||
/// Asks the [SemanticsNode] at the given position to perform the given action.
|
||||
@ -1011,10 +887,256 @@ class SemanticsOwner extends ChangeNotifier {
|
||||
final SemanticsNode node = rootSemanticsNode;
|
||||
if (node == null)
|
||||
return;
|
||||
final SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(node, position, action);
|
||||
handler?.performAction(action);
|
||||
final VoidCallback handler = _getSemanticsActionHandlerForPosition(node, position, action);
|
||||
if (handler != null)
|
||||
handler();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => describeIdentity(this);
|
||||
}
|
||||
|
||||
/// Describes the semantic information associated with the owning
|
||||
/// [RenderObject].
|
||||
///
|
||||
/// The information provided in the configuration is used to to generate the
|
||||
/// semantics tree.
|
||||
class SemanticsConfiguration {
|
||||
|
||||
// SEMANTIC BOUNDARY BEHAVIOR
|
||||
|
||||
/// Whether the [RenderObject] owner of this configuration wants to own its
|
||||
/// own [SemanticsNode].
|
||||
///
|
||||
/// When set to true semantic information associated with the [RenderObject]
|
||||
/// owner of this configuration or any of its defendants will not leak into
|
||||
/// parents. The [SemanticsNode] generated out of this configuration will
|
||||
/// act as a boundary.
|
||||
///
|
||||
/// Whether descendants of the owning [RenderObject] can add their semantic
|
||||
/// information to the [SemanticsNode] introduced by this configuration
|
||||
/// is controlled by [explicitChildNodes].
|
||||
///
|
||||
/// This has to be true if [isMergingDescendantsIntoOneNode] is also true.
|
||||
bool get isSemanticBoundary => _isSemanticBoundary;
|
||||
bool _isSemanticBoundary = false;
|
||||
set isSemanticBoundary(bool value) {
|
||||
assert(!isMergingDescendantsIntoOneNode || value);
|
||||
_isSemanticBoundary = value;
|
||||
}
|
||||
|
||||
/// Whether the configuration forces all children of the owning [RenderObject]
|
||||
/// that want to contribute semantic information to the semantics tree to do
|
||||
/// so in the form of explicit [SemanticsNode]s.
|
||||
///
|
||||
/// When set to false children of the owning [RenderObject] are allowed to
|
||||
/// annotate [SemanticNode]s of their parent with the semantic information
|
||||
/// they want to contribute to the semantic tree.
|
||||
/// When set to true the only way for children of the owning [RenderObject]
|
||||
/// to contribute semantic information to the semantic tree is to introduce
|
||||
/// new explicit [SemanticNode]s to the tree.
|
||||
///
|
||||
/// This setting is often used in combination with [isSemanticBoundary] to
|
||||
/// create semantic boundaries that are either writable or not for children.
|
||||
bool explicitChildNodes = false;
|
||||
|
||||
/// Whether the owning [RenderObject] makes other [RenderObjects] previously
|
||||
/// painted within the same semantic boundary unreachable for accessibility
|
||||
/// purposes.
|
||||
///
|
||||
/// If set to true, the semantic information 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 isBlockingSemanticsOfPreviouslyPaintedNodes = false;
|
||||
|
||||
/// Whether the semantics information of all descendants should be merged
|
||||
/// into the owning [RenderObject] semantics node.
|
||||
///
|
||||
/// When this is set to true the [SemanticsNode] of the owning [RenderObject]
|
||||
/// will not have any children.
|
||||
///
|
||||
/// Setting this to true requires that [isSemanticBoundary] is also true.
|
||||
bool get isMergingDescendantsIntoOneNode => _isMergingDescendantsIntoOneNode;
|
||||
bool _isMergingDescendantsIntoOneNode = false;
|
||||
set isMergingDescendantsIntoOneNode(bool value) {
|
||||
assert(isSemanticBoundary);
|
||||
_isMergingDescendantsIntoOneNode = isMergingDescendantsIntoOneNode;
|
||||
}
|
||||
|
||||
// SEMANTIC ANNOTATIONS
|
||||
// These will end up on [SemanticNode]s generated from
|
||||
// [SemanticsConfiguration]s.
|
||||
|
||||
/// Whether this configuration is empty.
|
||||
///
|
||||
/// An empty configuration doesn't contain any semantic information that it
|
||||
/// wants to contribute to the semantics tree.
|
||||
bool get hasBeenAnnotated => _hasBeenAnnotated;
|
||||
bool _hasBeenAnnotated = false;
|
||||
|
||||
/// The actions (with associated action handlers) that this configuration
|
||||
/// would like to contribute to the semantics tree.
|
||||
///
|
||||
/// See also:
|
||||
/// * [addAction] to add an action.
|
||||
final Map<SemanticsAction, VoidCallback> _actions = <SemanticsAction, VoidCallback>{};
|
||||
|
||||
/// Adds an `action` to the semantics tree.
|
||||
///
|
||||
/// Whenever the user performs `action` the provided `handler` is called.
|
||||
void addAction(SemanticsAction action, VoidCallback handler) {
|
||||
_actions[action] = handler;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// Returns the action handler registered for [action] or null if none was
|
||||
/// registered.
|
||||
///
|
||||
/// See also:
|
||||
/// * [addAction] to add an action.
|
||||
VoidCallback getActionHandler(SemanticsAction action) => _actions[action];
|
||||
|
||||
/// Whether the semantic information provided by the owning [RenderObject] and
|
||||
/// all of its descendants should be treated as one logical entity.
|
||||
///
|
||||
/// If set to true, the descendants of the owning [RenderObject]'s
|
||||
/// [SemanticsNode] will merge their semantic information into the
|
||||
/// [SemanticsNode] representing the owning [RenderObject].
|
||||
bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
|
||||
bool _isMergingSemanticsOfDescendants = false;
|
||||
set isMergingSemanticsOfDescendants(bool value) {
|
||||
_isMergingSemanticsOfDescendants = value;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// A textual description of the owning [RenderObject].
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get label => _label;
|
||||
String _label = '';
|
||||
set label(String label) {
|
||||
_label = label;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// The reading direction for the text in [label].
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection textDirection) {
|
||||
_textDirection = textDirection;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// Whether the owning [RenderObject] is selected (true) or not (false).
|
||||
set isSelected(bool value) {
|
||||
_setFlag(SemanticsFlags.isSelected, value);
|
||||
}
|
||||
|
||||
/// If this node has Boolean state that can be controlled by the user, whether
|
||||
/// that state is on or off, corresponding to true and false, respectively.
|
||||
///
|
||||
/// Do not set this to any value if the owning [RenderObject] doesn't have
|
||||
/// Booleans state that can be controlled by the user.
|
||||
set isChecked(bool value) {
|
||||
_setFlag(SemanticsFlags.hasCheckedState, true);
|
||||
_setFlag(SemanticsFlags.isChecked, value);
|
||||
}
|
||||
|
||||
// TAGS
|
||||
|
||||
Iterable<SemanticsTag> get tagsForChildren => _tagsForChildren;
|
||||
Set<SemanticsTag> _tagsForChildren;
|
||||
|
||||
void addTagForChildren(SemanticsTag tag) {
|
||||
_tagsForChildren ??= new Set<SemanticsTag>();
|
||||
_tagsForChildren.add(tag);
|
||||
}
|
||||
|
||||
// INTERNAL FLAG MANAGEMENT
|
||||
|
||||
int _flags = 0;
|
||||
void _setFlag(SemanticsFlags flag, bool value) {
|
||||
if (value) {
|
||||
_flags |= flag.index;
|
||||
} else {
|
||||
_flags &= ~flag.index;
|
||||
}
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
// CONFIGURATION COMBINATION LOGIC
|
||||
|
||||
/// Whether this configuration is compatible with the provided `other`
|
||||
/// configuration.
|
||||
///
|
||||
/// Two configurations are said to be compatible if they can be added to the
|
||||
/// same [SemanticsNode] without losing any semantics information.
|
||||
bool isCompatibleWith(SemanticsConfiguration other) {
|
||||
if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated)
|
||||
return true;
|
||||
if (_actions.keys.toSet().intersection(other._actions.keys.toSet()).isNotEmpty)
|
||||
return false;
|
||||
if ((_flags & other._flags) != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Absorb the semantic information from `other` into this configuration.
|
||||
///
|
||||
/// This adds the semantic information of both configurations and saves the
|
||||
/// result in this configuration.
|
||||
///
|
||||
/// Only configurations that have [explicitChildNodes] set to false can
|
||||
/// absorb other configurations and its recommended to only absorb compatible
|
||||
/// configurations as determined by [isCompatibleWith].
|
||||
void absorb(SemanticsConfiguration other) {
|
||||
assert(!explicitChildNodes);
|
||||
|
||||
if (!other.hasBeenAnnotated)
|
||||
return;
|
||||
|
||||
_actions.addAll(other._actions);
|
||||
_flags |= other._flags;
|
||||
|
||||
textDirection ??= other.textDirection;
|
||||
if (other.label.isNotEmpty) {
|
||||
String nestedLabel = other.label;
|
||||
if (textDirection != other.textDirection && other.textDirection != null) {
|
||||
switch (other.textDirection) {
|
||||
case TextDirection.rtl:
|
||||
nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (label.isEmpty)
|
||||
label = nestedLabel;
|
||||
else
|
||||
label = '$label\n$nestedLabel';
|
||||
}
|
||||
|
||||
_hasBeenAnnotated = _hasBeenAnnotated || other._hasBeenAnnotated;
|
||||
}
|
||||
|
||||
/// Returns an exact copy of this configuration.
|
||||
SemanticsConfiguration copy() {
|
||||
return new SemanticsConfiguration()
|
||||
..isSemanticBoundary = isSemanticBoundary
|
||||
..explicitChildNodes = explicitChildNodes
|
||||
.._hasBeenAnnotated = _hasBeenAnnotated
|
||||
.._textDirection = _textDirection
|
||||
.._label = _label
|
||||
.._flags = _flags
|
||||
.._actions.addAll(_actions);
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,10 +221,11 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
|
||||
}
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _excludeFromSemanticsScrolling ? _annotate : null;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
node.addTag(RenderSemanticsGestureHandler.excludeFromScrolling);
|
||||
if (_excludeFromSemanticsScrolling)
|
||||
config.addTagForChildren(RenderSemanticsGestureHandler.excludeFromScrolling);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -91,11 +91,12 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
_crossAxisDirection = crossAxisDirection,
|
||||
_offset = offset;
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
node.addTag(RenderSemanticsGestureHandler.useTwoPaneSemantics);
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
config.addTagForChildren(RenderSemanticsGestureHandler.useTwoPaneSemantics);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -4434,6 +4434,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
Key key,
|
||||
Widget child,
|
||||
this.container: false,
|
||||
this.explicitChildNodes: false,
|
||||
this.checked,
|
||||
this.selected,
|
||||
this.label,
|
||||
@ -4441,18 +4442,29 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
}) : assert(container != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
/// If 'container' is true, this Widget will introduce a new node in
|
||||
/// the semantics tree. Otherwise, the semantics will be merged with
|
||||
/// the semantics of any ancestors.
|
||||
/// If 'container' is true, this widget will introduce a new
|
||||
/// node in the semantics tree. Otherwise, the semantics will be
|
||||
/// merged with the semantics of any ancestors (if the ancestor allows that).
|
||||
///
|
||||
/// The 'container' flag is implicitly set to true on the immediate
|
||||
/// semantics-providing descendants of a node where multiple
|
||||
/// children have semantics or have descendants providing semantics.
|
||||
/// In other words, the semantics of siblings are not merged. To
|
||||
/// merge the semantics of an entire subtree, including siblings,
|
||||
/// you can use a [MergeSemantics] widget.
|
||||
/// Whether descendants of this widget can add their semantic information to the
|
||||
/// [SemanticsNode] introduced by this configuration is controlled by
|
||||
/// [explicitChildNodes].
|
||||
final bool container;
|
||||
|
||||
/// Whether descendants of this widget are allowed to add semantic information
|
||||
/// to the [SemanticsNode] annotated by this widget.
|
||||
///
|
||||
/// When set to false descendants are allowed to annotate [SemanticNode]s of
|
||||
/// their parent with the semantic information they want to contribute to the
|
||||
/// semantic tree.
|
||||
/// When set to true the only way for descendants to contribute semantic
|
||||
/// information to the semantic tree is to introduce new explicit
|
||||
/// [SemanticNode]s to the tree.
|
||||
///
|
||||
/// This setting is often used in combination with [isSemanticBoundary] to
|
||||
/// create semantic boundaries that are either writable or not for children.
|
||||
final bool explicitChildNodes;
|
||||
|
||||
/// If non-null, indicates that this subtree represents a checkbox
|
||||
/// or similar widget with a "checked" state, and what its current
|
||||
/// state is.
|
||||
@ -4484,6 +4496,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
RenderSemanticsAnnotations createRenderObject(BuildContext context) {
|
||||
return new RenderSemanticsAnnotations(
|
||||
container: container,
|
||||
explicitChildNodes: explicitChildNodes,
|
||||
checked: checked,
|
||||
selected: selected,
|
||||
label: label,
|
||||
@ -4495,6 +4508,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
void updateRenderObject(BuildContext context, RenderSemanticsAnnotations renderObject) {
|
||||
renderObject
|
||||
..container = container
|
||||
..explicitChildNodes = explicitChildNodes
|
||||
..checked = checked
|
||||
..selected = selected
|
||||
..label = label
|
||||
|
||||
@ -104,7 +104,7 @@ class _FocusScopeState extends State<FocusScope> {
|
||||
Widget build(BuildContext context) {
|
||||
FocusScope.of(context).reparentScopeIfNeeded(widget.node);
|
||||
return new Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: new _FocusScopeMarker(
|
||||
node: widget.node,
|
||||
child: widget.child,
|
||||
|
||||
62
packages/flutter/test/material/card_test.dart
Normal file
62
packages/flutter/test/material/card_test.dart
Normal file
@ -0,0 +1,62 @@
|
||||
// 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/rendering.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Card can take semantic text from multiple children', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Card(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Text('I am text!'),
|
||||
const Text('Moar text!!1'),
|
||||
new MaterialButton(
|
||||
child: const Text('Button'),
|
||||
onPressed: () { },
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'I am text!\nMoar text!!1',
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'Button',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: SemanticsAction.tap.index,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreTransform: true,
|
||||
ignoreRect: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
}
|
||||
@ -95,7 +95,7 @@ void main() {
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 7,
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
|
||||
transform: null,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
@ -103,7 +103,7 @@ void main() {
|
||||
label: 'aaa\nAAA',
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 6,
|
||||
id: 8,
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
|
||||
transform: new Matrix4.translationValues(0.0, 56.0, 0.0),
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
@ -111,7 +111,7 @@ void main() {
|
||||
label: 'bbb\nBBB',
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 11,
|
||||
id: 9,
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
|
||||
transform: new Matrix4.translationValues(0.0, 112.0, 0.0),
|
||||
flags: SemanticsFlags.hasCheckedState.index,
|
||||
|
||||
@ -1011,11 +1011,11 @@ void main() {
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 3,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
id: 1,
|
||||
actions: SemanticsAction.tap.index,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
label: 'TAB #0\nTab 1 of 2',
|
||||
@ -1023,13 +1023,14 @@ void main() {
|
||||
transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 5,
|
||||
id: 2,
|
||||
actions: SemanticsAction.tap.index,
|
||||
label: 'TAB #1\nTab 2 of 2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, kTextTabBarHeight),
|
||||
transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -1064,15 +1065,19 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
const String tab0title = 'This is a very wide tab #0\nTab 1 of 20';
|
||||
const String tab10title = 'This is a very wide tab #10\nTab 11 of 20';
|
||||
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
|
||||
expect(semantics, isNot(includesNodeWith(label: 'This is a very wide tab #10')));
|
||||
expect(semantics, includesNodeWith(label: tab0title));
|
||||
expect(semantics, isNot(includesNodeWith(label: tab10title)));
|
||||
|
||||
controller.index = 10;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(semantics, isNot(includesNodeWith(label: 'This is a very wide tab #0')));
|
||||
expect(semantics, isNot(includesNodeWith(label: tab0title)));
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight]));
|
||||
expect(semantics, includesNodeWith(label: 'This is a very wide tab #10'));
|
||||
expect(semantics, includesNodeWith(label: tab10title));
|
||||
|
||||
controller.index = 19;
|
||||
await tester.pumpAndSettle();
|
||||
@ -1083,7 +1088,8 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
|
||||
expect(semantics, includesNodeWith(label: 'This is a very wide tab #0'));
|
||||
expect(semantics, includesNodeWith(label: tab0title));
|
||||
expect(semantics, isNot(includesNodeWith(label: tab10title)));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
@ -473,7 +473,7 @@ void main() {
|
||||
child: new Tooltip(
|
||||
key: key,
|
||||
message: tooltipText,
|
||||
child: new Container(width: 0.0, height: 0.0),
|
||||
child: new Container(width: 10.0, height: 10.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -485,14 +485,23 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText)));
|
||||
final TestSemantics expected = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: tooltipText,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
|
||||
|
||||
// before using "as dynamic" in your code, see note top of file
|
||||
(key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes
|
||||
|
||||
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText)));
|
||||
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
@ -55,6 +55,9 @@ class TestRenderObject extends RenderObject {
|
||||
Rect get semanticBounds => new Rect.fromLTWH(0.0, 0.0, 10.0, 20.0);
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => true;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config.isSemanticBoundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -72,23 +72,23 @@ void main() {
|
||||
});
|
||||
|
||||
test('RenderSemanticsGestureHandler adds/removes correct semantic actions', () {
|
||||
SemanticsNode node = new SemanticsNode();
|
||||
final RenderSemanticsGestureHandler renderObj = new RenderSemanticsGestureHandler(
|
||||
onTap: () {},
|
||||
onHorizontalDragUpdate: (DragUpdateDetails details) {},
|
||||
);
|
||||
|
||||
renderObj.semanticsAnnotator(node);
|
||||
expect(node.getSemanticsData().hasAction(SemanticsAction.tap), isTrue);
|
||||
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollLeft), isTrue);
|
||||
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollRight), isTrue);
|
||||
SemanticsConfiguration config = new SemanticsConfiguration();
|
||||
renderObj.describeSemanticsConfiguration(config);
|
||||
expect(config.getActionHandler(SemanticsAction.tap), isNotNull);
|
||||
expect(config.getActionHandler(SemanticsAction.scrollLeft), isNotNull);
|
||||
expect(config.getActionHandler(SemanticsAction.scrollRight), isNotNull);
|
||||
|
||||
node = new SemanticsNode();
|
||||
config = new SemanticsConfiguration();
|
||||
renderObj.validActions = <SemanticsAction>[SemanticsAction.tap, SemanticsAction.scrollLeft].toSet();
|
||||
|
||||
renderObj.semanticsAnnotator(node);
|
||||
expect(node.getSemanticsData().hasAction(SemanticsAction.tap), isTrue);
|
||||
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollLeft), isTrue);
|
||||
expect(node.getSemanticsData().hasAction(SemanticsAction.scrollRight), isFalse);
|
||||
renderObj.describeSemanticsConfiguration(config);
|
||||
expect(config.getActionHandler(SemanticsAction.tap), isNotNull);
|
||||
expect(config.getActionHandler(SemanticsAction.scrollLeft), isNotNull);
|
||||
expect(config.getActionHandler(SemanticsAction.scrollRight), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ class TestTree {
|
||||
child: new RenderPositionedBox(
|
||||
child: child = new RenderConstrainedBox(
|
||||
additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
|
||||
child: new RenderSemanticsAnnotations(label: 'Hello there foo', textDirection: TextDirection.ltr)
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -127,7 +128,7 @@ void main() {
|
||||
layout(testTree.root, phase: EnginePhase.paint);
|
||||
expect(testTree.painted, isTrue);
|
||||
});
|
||||
test('objects can be detached and re-attached: semantics', () {
|
||||
test('objects can be detached and re-attached: semantics (no change)', () {
|
||||
final TestTree testTree = new TestTree();
|
||||
int semanticsUpdateCount = 0;
|
||||
final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
|
||||
@ -147,7 +148,31 @@ void main() {
|
||||
expect(semanticsUpdateCount, 0);
|
||||
// Lay out, composite, paint, and update semantics again
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(semanticsUpdateCount, 0); // no semantics have changed.
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
test('objects can be detached and re-attached: semantics (with change)', () {
|
||||
final TestTree testTree = new TestTree();
|
||||
int semanticsUpdateCount = 0;
|
||||
final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
|
||||
listener: () {
|
||||
++semanticsUpdateCount;
|
||||
}
|
||||
);
|
||||
// Lay out, composite, paint, and update semantics
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(semanticsUpdateCount, 1);
|
||||
// Remove testTree from the custom render view
|
||||
renderer.renderView.child = null;
|
||||
expect(testTree.child.owner, isNull);
|
||||
// Dirty one of the elements
|
||||
semanticsUpdateCount = 0;
|
||||
testTree.child.additionalConstraints = const BoxConstraints.tightFor(height: 20.0, width: 30.0);
|
||||
testTree.child.markNeedsSemanticsUpdate();
|
||||
expect(semanticsUpdateCount, 0);
|
||||
// Lay out, composite, paint, and update semantics again
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(semanticsUpdateCount, 1); // semantics have changed.
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -18,41 +18,45 @@ void main() {
|
||||
test('tagging', () {
|
||||
final SemanticsNode node = new SemanticsNode();
|
||||
|
||||
expect(node.hasTag(tag1), isFalse);
|
||||
expect(node.hasTag(tag2), isFalse);
|
||||
expect(node.isTagged(tag1), isFalse);
|
||||
expect(node.isTagged(tag2), isFalse);
|
||||
|
||||
node.addTag(tag1);
|
||||
expect(node.hasTag(tag1), isTrue);
|
||||
expect(node.hasTag(tag2), isFalse);
|
||||
node.tags = new Set<SemanticsTag>()..add(tag1);
|
||||
expect(node.isTagged(tag1), isTrue);
|
||||
expect(node.isTagged(tag2), isFalse);
|
||||
|
||||
node.addTag(tag2);
|
||||
expect(node.hasTag(tag1), isTrue);
|
||||
expect(node.hasTag(tag2), isTrue);
|
||||
node.tags.add(tag2);
|
||||
expect(node.isTagged(tag1), isTrue);
|
||||
expect(node.isTagged(tag2), isTrue);
|
||||
});
|
||||
|
||||
test('getSemanticsData includes tags', () {
|
||||
final SemanticsNode node = new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0)
|
||||
..addTag(tag1)
|
||||
..addTag(tag2);
|
||||
|
||||
final Set<SemanticsTag> expected = new Set<SemanticsTag>()
|
||||
final Set<SemanticsTag> tags = new Set<SemanticsTag>()
|
||||
..add(tag1)
|
||||
..add(tag2);
|
||||
|
||||
expect(node.getSemanticsData().tags, expected);
|
||||
final SemanticsNode node = new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0)
|
||||
..tags = tags;
|
||||
|
||||
node.mergeAllDescendantsIntoThisNode = true;
|
||||
node.addChildren(<SemanticsNode>[
|
||||
new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(5.0, 5.0, 10.0, 10.0)
|
||||
..addTag(tag3),
|
||||
]);
|
||||
node.finalizeChildren();
|
||||
expect(node.getSemanticsData().tags, tags);
|
||||
|
||||
expected.add(tag3);
|
||||
tags.add(tag3);
|
||||
|
||||
expect(node.getSemanticsData().tags, expected);
|
||||
final SemanticsConfiguration config = new SemanticsConfiguration()
|
||||
..isMergingSemanticsOfDescendants = true;
|
||||
|
||||
node.updateWith(
|
||||
config: config,
|
||||
childrenInInversePaintOrder: <SemanticsNode>[
|
||||
new SemanticsNode()
|
||||
..isMergedIntoParent = true
|
||||
..rect = new Rect.fromLTRB(5.0, 5.0, 10.0, 10.0)
|
||||
..tags = tags,
|
||||
],
|
||||
);
|
||||
|
||||
expect(node.getSemanticsData().tags, tags);
|
||||
});
|
||||
|
||||
test('after markNeedsSemanticsUpdate(onlyLocalUpdates: true) all render objects between two semantic boundaries are asked for annotations', () {
|
||||
@ -88,7 +92,6 @@ void main() {
|
||||
|
||||
middle.action = SemanticsAction.scrollDown;
|
||||
middle.markNeedsSemanticsUpdate(onlyLocalUpdates: true);
|
||||
expect(root.debugSemantics.getSemanticsData().actions, 0); // SemanticsNode is reset
|
||||
|
||||
pumpFrame(phase: EnginePhase.flushSemantics);
|
||||
|
||||
@ -104,8 +107,10 @@ void main() {
|
||||
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0);
|
||||
final SemanticsNode root = new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
|
||||
root.addChildren(<SemanticsNode>[child1, child2]);
|
||||
root.finalizeChildren();
|
||||
root.updateWith(
|
||||
config: null,
|
||||
childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
|
||||
);
|
||||
|
||||
expect(root.transform, isNull);
|
||||
expect(child1.transform, isNull);
|
||||
@ -126,8 +131,10 @@ void main() {
|
||||
..rect = new Rect.fromLTRB(10.0, 0.0, 15.0, 5.0);
|
||||
final SemanticsNode root = new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 20.0, 5.0);
|
||||
root.addChildren(<SemanticsNode>[child1, child2]);
|
||||
root.finalizeChildren();
|
||||
root.updateWith(
|
||||
config: null,
|
||||
childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
|
||||
);
|
||||
expect(
|
||||
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
|
||||
'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
|
||||
@ -144,18 +151,22 @@ void main() {
|
||||
|
||||
final SemanticsNode child3 = new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
|
||||
child3.addChildren(<SemanticsNode>[
|
||||
new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0),
|
||||
new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 5.0, 5.0),
|
||||
]);
|
||||
child3.finalizeChildren();
|
||||
child3.updateWith(
|
||||
config: null,
|
||||
childrenInInversePaintOrder: <SemanticsNode>[
|
||||
new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0),
|
||||
new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 5.0, 5.0),
|
||||
],
|
||||
);
|
||||
|
||||
final SemanticsNode rootComplex = new SemanticsNode()
|
||||
..rect = new Rect.fromLTRB(0.0, 0.0, 25.0, 5.0);
|
||||
rootComplex.addChildren(<SemanticsNode>[child1, child2, child3]);
|
||||
rootComplex.finalizeChildren();
|
||||
rootComplex.updateWith(
|
||||
config: null,
|
||||
childrenInInversePaintOrder: <SemanticsNode>[child1, child2, child3]
|
||||
);
|
||||
|
||||
expect(
|
||||
rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
|
||||
@ -187,28 +198,30 @@ void main() {
|
||||
|
||||
expect(
|
||||
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
|
||||
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), wasAffectedByClip: false, actions: [], tags: [], isSelected: false, label: "", textDirection: null)\n',
|
||||
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), wasAffectedByClip: false, actions: [], isSelected: false, label: "", textDirection: null)\n',
|
||||
);
|
||||
|
||||
final SemanticsNode allProperties = new SemanticsNode()
|
||||
..rect = new Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
|
||||
..mergeAllDescendantsIntoThisNode = true
|
||||
..transform = new Matrix4.translation(new Vector3(10.0, 10.0, 0.0))
|
||||
..wasAffectedByClip = true
|
||||
..addAction(SemanticsAction.scrollUp)
|
||||
..addAction(SemanticsAction.longPress)
|
||||
..addAction(SemanticsAction.showOnScreen)
|
||||
final SemanticsConfiguration config = new SemanticsConfiguration()
|
||||
..isMergingSemanticsOfDescendants = true
|
||||
..addAction(SemanticsAction.scrollUp, () { })
|
||||
..addAction(SemanticsAction.longPress, () { })
|
||||
..addAction(SemanticsAction.showOnScreen, () { })
|
||||
..isChecked = false
|
||||
..isSelected = true
|
||||
..label = "Use all the properties"
|
||||
..textDirection = TextDirection.rtl;
|
||||
final SemanticsNode allProperties = new SemanticsNode()
|
||||
..rect = new Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
|
||||
..transform = new Matrix4.translation(new Vector3(10.0, 10.0, 0.0))
|
||||
..wasAffectedByClip = true
|
||||
..updateWith(config: config, childrenInInversePaintOrder: null);
|
||||
expect(
|
||||
allProperties.toStringDeep(),
|
||||
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), clipped, actions: [longPress, scrollUp, showOnScreen], selected, label: "Use all the properties", textDirection: rtl)\n',
|
||||
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), clipped, actions: [longPress, scrollUp, showOnScreen], unchecked, selected, label: "Use all the properties", textDirection: rtl)\n',
|
||||
);
|
||||
expect(
|
||||
allProperties.getSemanticsData().toString(),
|
||||
'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [1.0,0.0,0.0,10.0; 0.0,1.0,0.0,10.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0], actions: [longPress, scrollUp, showOnScreen], flags: [isSelected], label: "Use all the properties", textDirection: rtl)',
|
||||
'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [1.0,0.0,0.0,10.0; 0.0,1.0,0.0,10.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0], actions: [longPress, scrollUp, showOnScreen], flags: [hasCheckedState, isSelected], label: "Use all the properties", textDirection: rtl)',
|
||||
);
|
||||
|
||||
final SemanticsNode scaled = new SemanticsNode()
|
||||
@ -223,37 +236,22 @@ void main() {
|
||||
'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [10.0,0.0,0.0,0.0; 0.0,10.0,0.0,0.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0])',
|
||||
);
|
||||
});
|
||||
|
||||
test('reset clears tags', () {
|
||||
const SemanticsTag tag = const SemanticsTag('tag for testing');
|
||||
final SemanticsNode node = new SemanticsNode();
|
||||
|
||||
expect(node.hasTag(tag), isFalse);
|
||||
|
||||
node.addTag(tag);
|
||||
|
||||
expect(node.hasTag(tag), isTrue);
|
||||
|
||||
node.reset();
|
||||
|
||||
expect(node.hasTag(tag), isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
class TestRender extends RenderProxyBox {
|
||||
|
||||
TestRender({ this.action, this.isSemanticBoundary, RenderObject child }) : super(child);
|
||||
|
||||
@override
|
||||
final bool isSemanticBoundary;
|
||||
|
||||
SemanticsAction action;
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
node.addAction(action);
|
||||
config
|
||||
..isSemanticBoundary = isSemanticBoundary
|
||||
..addAction(action, () { });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
70
packages/flutter/test/rendering/simple_semantics_test.dart
Normal file
70
packages/flutter/test/rendering/simple_semantics_test.dart
Normal file
@ -0,0 +1,70 @@
|
||||
// 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/rendering.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'rendering_tester.dart';
|
||||
|
||||
|
||||
void main() {
|
||||
test('only send semantics update if semantics have changed', () {
|
||||
final TestRender testRender = new TestRender()
|
||||
..label = 'hello'
|
||||
..textDirection = TextDirection.ltr;
|
||||
|
||||
final RenderObject tree = new RenderConstrainedBox(
|
||||
additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
|
||||
child: testRender,
|
||||
);
|
||||
int semanticsUpdateCount = 0;
|
||||
final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
|
||||
listener: () {
|
||||
++semanticsUpdateCount;
|
||||
}
|
||||
);
|
||||
|
||||
layout(tree, phase: EnginePhase.flushSemantics);
|
||||
|
||||
// Initial render does semantics.
|
||||
expect(semanticsUpdateCount, 1);
|
||||
expect(testRender.describeSemanticsConfigurationCallCount, isNot(0));
|
||||
|
||||
testRender.describeSemanticsConfigurationCallCount = 0;
|
||||
semanticsUpdateCount = 0;
|
||||
|
||||
// Request semantics update even though nothing changed.
|
||||
testRender.markNeedsSemanticsUpdate();
|
||||
pumpFrame(phase: EnginePhase.flushSemantics);
|
||||
|
||||
// Object is asked for semantics, but no update is sent.
|
||||
expect(semanticsUpdateCount, 0);
|
||||
expect(testRender.describeSemanticsConfigurationCallCount, 1);
|
||||
|
||||
testRender.describeSemanticsConfigurationCallCount = 0;
|
||||
semanticsUpdateCount = 0;
|
||||
|
||||
// Change semantics and request update.
|
||||
testRender.label = 'bye';
|
||||
testRender.markNeedsSemanticsUpdate();
|
||||
pumpFrame(phase: EnginePhase.flushSemantics);
|
||||
|
||||
// Object is asked for semantics, and update is sent.
|
||||
expect(semanticsUpdateCount, 1);
|
||||
expect(testRender.describeSemanticsConfigurationCallCount, 1);
|
||||
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
class TestRender extends RenderSemanticsAnnotations {
|
||||
int describeSemanticsConfigurationCallCount = 0;
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
describeSemanticsConfigurationCallCount += 1;
|
||||
}
|
||||
}
|
||||
@ -141,7 +141,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: 'a label')));
|
||||
expect(semantics, includesNodeWith(label: 'a label'));
|
||||
});
|
||||
|
||||
testWidgets('Null icon with semantic label', (WidgetTester tester) async {
|
||||
@ -159,7 +159,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: 'a label')));
|
||||
expect(semantics, includesNodeWith(label: 'a label'));
|
||||
});
|
||||
|
||||
testWidgets('Changing semantic label from null doesn\'t rebuild tree ', (WidgetTester tester) async {
|
||||
|
||||
260
packages/flutter/test/widgets/implicit_semantics_test.dart
Normal file
260
packages/flutter/test/widgets/implicit_semantics_test.dart
Normal file
@ -0,0 +1,260 @@
|
||||
// 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 'dart:ui' show SemanticsFlags;
|
||||
|
||||
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() {
|
||||
testWidgets('Implicit Semantics merge behavior', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: false,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Text('Michael Goderbauer'),
|
||||
const Text('goderbauer@google.com'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// SemanticsNode#0()
|
||||
// └SemanticsNode#1(label: "Michael Goderbauer\ngoderbauer@google.com", textDirection: ltr)
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'Michael Goderbauer\ngoderbauer@google.com',
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Text('Michael Goderbauer'),
|
||||
const Text('goderbauer@google.com'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// SemanticsNode#0()
|
||||
// └SemanticsNode#1()
|
||||
// ├SemanticsNode#2(label: "Michael Goderbauer", textDirection: ltr)
|
||||
// └SemanticsNode#3(label: "goderbauer@google.com", textDirection: ltr)
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'Michael Goderbauer',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
label: 'goderbauer@google.com',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: new Semantics(
|
||||
label: 'Signed in as',
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Text('Michael Goderbauer'),
|
||||
const Text('goderbauer@google.com'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// SemanticsNode#0()
|
||||
// └SemanticsNode#1()
|
||||
// └SemanticsNode#4(label: "Signed in as\nMichael Goderbauer\ngoderbauer@google.com", textDirection: ltr)
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
label: 'Signed in as\nMichael Goderbauer\ngoderbauer@google.com',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: false,
|
||||
child: new Semantics(
|
||||
label: 'Signed in as',
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Text('Michael Goderbauer'),
|
||||
const Text('goderbauer@google.com'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// SemanticsNode#0()
|
||||
// └SemanticsNode#1(label: "Signed in as\nMichael Goderbauer\ngoderbauer@google.com", textDirection: ltr)
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'Signed in as\nMichael Goderbauer\ngoderbauer@google.com',
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
),
|
||||
);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Do not merge with conflicts', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: false,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Semantics(
|
||||
label: 'node 1',
|
||||
selected: true,
|
||||
child: new Container(
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
),
|
||||
),
|
||||
new Semantics(
|
||||
label: 'node 2',
|
||||
selected: true,
|
||||
child: new Container(
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
),
|
||||
),
|
||||
new Semantics(
|
||||
label: 'node 3',
|
||||
selected: true,
|
||||
child: new Container(
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// SemanticsNode#0()
|
||||
// └SemanticsNode#8()
|
||||
// ├SemanticsNode#5(selected, label: "node 1", textDirection: ltr)
|
||||
// ├SemanticsNode#6(selected, label: "node 2", textDirection: ltr)
|
||||
// └SemanticsNode#7(selected, label: "node 3", textDirection: ltr)
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 8,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 5,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
label: 'node 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 6,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
label: 'node 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 7,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
label: 'node 3',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
),
|
||||
);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
@ -104,11 +104,11 @@ void main() {
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 2,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
id: 1,
|
||||
rect: TestSemantics.fullScreen,
|
||||
actions: SemanticsAction.tap.index,
|
||||
),
|
||||
|
||||
@ -94,12 +94,19 @@ class TestWidget extends SingleChildRenderObjectWidget {
|
||||
}
|
||||
|
||||
class RenderTest extends RenderProxyBox {
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => isSemanticBoundary ? _annotate : null;
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
node.label = _label;
|
||||
node.textDirection = TextDirection.ltr;
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
if (!_isSemanticBoundary)
|
||||
return;
|
||||
|
||||
config
|
||||
..isSemanticBoundary = _isSemanticBoundary
|
||||
..label = _label
|
||||
..textDirection = TextDirection.ltr;
|
||||
|
||||
}
|
||||
|
||||
String _label;
|
||||
@ -111,8 +118,7 @@ class RenderTest extends RenderProxyBox {
|
||||
callLog.add('markNeedsSemanticsUpdate(onlyChanges: true)');
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => _isSemanticBoundary;
|
||||
|
||||
bool _isSemanticBoundary;
|
||||
set isSemanticBoundary(bool value) {
|
||||
if (_isSemanticBoundary == value)
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
// 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/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('markNeedsSemanticsUpdate allways resets node', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(const TestWidget());
|
||||
final RenderTest renderObj = tester.renderObject(find.byType(TestWidget));
|
||||
expect(renderObj.labelWasReset, hasLength(1));
|
||||
expect(renderObj.labelWasReset.last, true);
|
||||
expect(semantics, includesNodeWith(label: 'Label 1'));
|
||||
|
||||
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: false, noGeometry: false);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(renderObj.labelWasReset, hasLength(2));
|
||||
expect(renderObj.labelWasReset.last, true);
|
||||
expect(semantics, includesNodeWith(label: 'Label 2'));
|
||||
|
||||
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: false);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(renderObj.labelWasReset, hasLength(3));
|
||||
expect(renderObj.labelWasReset.last, true);
|
||||
expect(semantics, includesNodeWith(label: 'Label 3'));
|
||||
|
||||
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: true, noGeometry: true);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(renderObj.labelWasReset, hasLength(4));
|
||||
expect(renderObj.labelWasReset.last, true);
|
||||
expect(semantics, includesNodeWith(label: 'Label 4'));
|
||||
|
||||
renderObj.markNeedsSemanticsUpdate(onlyLocalUpdates: false, noGeometry: true);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(renderObj.labelWasReset, hasLength(5));
|
||||
expect(renderObj.labelWasReset.last, true);
|
||||
expect(semantics, includesNodeWith(label: 'Label 5'));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
class TestWidget extends SingleChildRenderObjectWidget {
|
||||
const TestWidget({
|
||||
Key key,
|
||||
Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
@override
|
||||
RenderTest createRenderObject(BuildContext context) {
|
||||
return new RenderTest();
|
||||
}
|
||||
}
|
||||
|
||||
class RenderTest extends RenderProxyBox {
|
||||
List<bool> labelWasReset = <bool>[];
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
labelWasReset.add(node.label == '');
|
||||
node.label = 'Label ${labelWasReset.length}';
|
||||
node.textDirection = TextDirection.ltr;
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -15,136 +17,221 @@ void main() {
|
||||
|
||||
// smoketest
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
child: new Semantics(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Container()
|
||||
)
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
child: new Semantics(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Container(),
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: 'test1')));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'test1',
|
||||
rect: TestSemantics.fullScreen,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
)
|
||||
]
|
||||
)));
|
||||
|
||||
// control for forking
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: true,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: true,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: 'child1')));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
rect: TestSemantics.fullScreen,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
)
|
||||
],
|
||||
)));
|
||||
|
||||
// forking semantics
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true));
|
||||
|
||||
// toggle a branch off
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr)
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: true,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr)
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: true,
|
||||
child: const Semantics(
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: 'child1')));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
rect: TestSemantics.fullScreen,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
)
|
||||
],
|
||||
)));
|
||||
|
||||
// toggle a branch back on
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr)
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr)
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -19,105 +21,153 @@ void main() {
|
||||
|
||||
// forking semantics
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr)
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr)
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true));
|
||||
|
||||
// toggle a branch off
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr)
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: true,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr)
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: true,
|
||||
child: const Semantics(
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(label: 'child1')));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
label: 'child1',
|
||||
rect: TestSemantics.fullScreen,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
)
|
||||
],
|
||||
)));
|
||||
|
||||
// toggle a branch back on
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(label: 'child1', textDirection: TextDirection.ltr)
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(label: 'child2', textDirection: TextDirection.ltr)
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const Semantics(
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 10.0,
|
||||
child: const IgnorePointer(
|
||||
ignoring: false,
|
||||
child: const Semantics(
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
selected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
label: 'child1',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
@ -16,81 +16,112 @@ void main() {
|
||||
|
||||
// implicit annotators
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
child: new Semantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Container(
|
||||
child: const Semantics(
|
||||
checked: true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
child: new Semantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Container(
|
||||
child: const Semantics(
|
||||
checked: true
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'test',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'test',
|
||||
rect: TestSemantics.fullScreen,
|
||||
)
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
// remove one
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
child: const Semantics(
|
||||
checked: true
|
||||
)
|
||||
)
|
||||
)
|
||||
checked: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
// change what it says
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
child: const Semantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
label: 'test',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
// add a node
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
child: new Semantics(
|
||||
checked: true,
|
||||
child: new Container(
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
child: const Semantics(
|
||||
checked: true,
|
||||
child: const Semantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'test',
|
||||
)
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'test',
|
||||
rect: TestSemantics.fullScreen,
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
int changeCount = 0;
|
||||
@ -100,17 +131,18 @@ void main() {
|
||||
|
||||
// make no changes
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
child: new Semantics(
|
||||
checked: true,
|
||||
child: new Container(
|
||||
new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
child: const Semantics(
|
||||
checked: true,
|
||||
child: const Semantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(changeCount, 0);
|
||||
|
||||
@ -6,6 +6,7 @@ import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
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';
|
||||
@ -26,10 +27,12 @@ void main() {
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
const Semantics(
|
||||
container: true,
|
||||
label: 'L1',
|
||||
),
|
||||
new Semantics(
|
||||
label: 'L2',
|
||||
container: true,
|
||||
child: new Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
@ -50,22 +53,22 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 3,
|
||||
label: 'L1',
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
id: 4,
|
||||
label: 'L2',
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
id: 1,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
id: 2,
|
||||
flags: SemanticsFlags.hasCheckedState.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
@ -87,9 +90,11 @@ void main() {
|
||||
children: <Widget>[
|
||||
const Semantics(
|
||||
label: 'L1',
|
||||
container: true,
|
||||
),
|
||||
new Semantics(
|
||||
label: 'L2',
|
||||
container: true,
|
||||
child: new Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
@ -108,12 +113,12 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 3,
|
||||
label: 'L1',
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
id: 4,
|
||||
label: 'L2',
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
@ -134,6 +139,7 @@ void main() {
|
||||
const Semantics(),
|
||||
new Semantics(
|
||||
label: 'L2',
|
||||
container: true,
|
||||
child: new Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
@ -150,8 +156,14 @@ void main() {
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
label: 'L2',
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 4,
|
||||
label: 'L2',
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 3,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: label,
|
||||
rect: TestSemantics.fullScreen,
|
||||
@ -111,7 +111,7 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 3,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: label,
|
||||
rect: TestSemantics.fullScreen,
|
||||
|
||||
@ -39,9 +39,15 @@ void main() {
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'label',
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'label',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: TestSemantics.fullScreen,
|
||||
)
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
@ -71,10 +77,16 @@ void main() {
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'label',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'label',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: TestSemantics.fullScreen,
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
|
||||
@ -61,6 +61,7 @@ void main() {
|
||||
new Semantics(
|
||||
label: '#2',
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: new Stack(
|
||||
children: <Widget>[
|
||||
new Semantics(
|
||||
@ -146,9 +147,12 @@ class RenderBoundaryBlockSemantics extends RenderProxyBox {
|
||||
RenderBoundaryBlockSemantics({ RenderBox child }) : super(child);
|
||||
|
||||
@override
|
||||
bool get isBlockingSemanticsOfPreviouslyPaintedNodes => true;
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
||||
@override
|
||||
bool get isSemanticBoundary => true;
|
||||
config
|
||||
..isBlockingSemanticsOfPreviouslyPaintedNodes = true
|
||||
..isSemanticBoundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -62,15 +62,9 @@ void main() {
|
||||
|
||||
final SemanticsNode node1 = tester.renderObject(find.byWidget(const Text('1'))).debugSemantics;
|
||||
final SemanticsNode node2 = tester.renderObject(find.byWidget(const Text('2'))).debugSemantics;
|
||||
final SemanticsNode node3 = tester.renderObject(find.byWidget(const Text('3'))).debugSemantics;
|
||||
|
||||
expect(node1.wasAffectedByClip, false);
|
||||
expect(node2.wasAffectedByClip, true);
|
||||
expect(node3.wasAffectedByClip, true);
|
||||
|
||||
expect(node1.isInvisible, isFalse);
|
||||
expect(node2.isInvisible, isFalse);
|
||||
expect(node3.isInvisible, isTrue);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
@ -117,12 +111,12 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
id: 3,
|
||||
label: '1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 75.0, 14.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 5,
|
||||
id: 4,
|
||||
label: '2\n3',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 25.0, 14.0), // clipped form original 75.0 to 25.0
|
||||
),
|
||||
|
||||
@ -53,7 +53,14 @@ void main() {
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(label: 'test1\ntest2'),
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 3,
|
||||
label: 'test1\ntest2',
|
||||
),
|
||||
]
|
||||
),
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
));
|
||||
@ -74,8 +81,8 @@ void main() {
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(id: 5, label: 'test1'),
|
||||
new TestSemantics.rootChild(id: 6, label: 'test2'),
|
||||
new TestSemantics.rootChild(id: 4, label: 'test1'),
|
||||
new TestSemantics.rootChild(id: 5, label: 'test2'),
|
||||
],
|
||||
),
|
||||
ignoreRect: true,
|
||||
|
||||
@ -13,8 +13,12 @@ void main() {
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -27,7 +31,12 @@ void main() {
|
||||
)
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
expect(semantics, hasSemantics(
|
||||
expectedSemantics,
|
||||
ignoreTransform: true,
|
||||
ignoreRect: true,
|
||||
ignoreId: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
semantics = null;
|
||||
@ -37,7 +46,12 @@ void main() {
|
||||
expect(tester.binding.hasScheduledFrame, isTrue);
|
||||
await tester.pump();
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
expect(semantics, hasSemantics(
|
||||
expectedSemantics,
|
||||
ignoreTransform: true,
|
||||
ignoreRect: true,
|
||||
ignoreId: true,
|
||||
));
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
@ -62,15 +76,20 @@ void main() {
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
label: 'test1',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'test2a',
|
||||
rect: TestSemantics.fullScreen,
|
||||
label: 'test1',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test2a',
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
ignoreId: true,
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(new Directionality(
|
||||
@ -94,22 +113,25 @@ void main() {
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
label: 'test1',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'middle',
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'test2b',
|
||||
rect: TestSemantics.fullScreen,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'test1',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'middle',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test2b',
|
||||
),
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
ignoreId: true,
|
||||
ignoreRect: true,
|
||||
ignoreTransform: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
@ -118,11 +140,6 @@ void main() {
|
||||
testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.rtl,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
@ -133,17 +150,12 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.rtl));
|
||||
});
|
||||
|
||||
testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@ -154,15 +166,19 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
expect(semantics, includesNodeWith(label: 'test1', textDirection: TextDirection.ltr));
|
||||
});
|
||||
|
||||
testWidgets('Semantics and Directionality - overriding RTL with LTR', (WidgetTester tester) async {
|
||||
testWidgets('Semantics and Directionality - cannot override RTL with LTR', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -176,15 +192,19 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
|
||||
testWidgets('Semantics and Directionality - overriding LTR with RTL', (WidgetTester tester) async {
|
||||
testWidgets('Semantics and Directionality - cannot override LTR with RTL', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.rtl,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'test1',
|
||||
textDirection: TextDirection.rtl,
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -198,6 +218,6 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ class TestSemantics {
|
||||
/// * [TestSemantics.fullScreen] 800x600, the test screen's size in logical
|
||||
/// pixels, useful for other full-screen widgets.
|
||||
TestSemantics({
|
||||
@required this.id,
|
||||
this.id,
|
||||
this.flags: 0,
|
||||
this.actions: 0,
|
||||
this.label: '',
|
||||
@ -40,8 +40,7 @@ class TestSemantics {
|
||||
this.transform,
|
||||
this.children: const <TestSemantics>[],
|
||||
Iterable<SemanticsTag> tags,
|
||||
}) : assert(id != null),
|
||||
assert(flags != null),
|
||||
}) : assert(flags != null),
|
||||
assert(label != null),
|
||||
assert(children != null),
|
||||
tags = tags?.toSet() ?? new Set<SemanticsTag>();
|
||||
@ -73,7 +72,7 @@ class TestSemantics {
|
||||
/// [TestSemantics.fullScreen] property may be useful as a value; it describes
|
||||
/// an 800x600 rectangle, which is the test screen's size in logical pixels.
|
||||
TestSemantics.rootChild({
|
||||
@required this.id,
|
||||
this.id,
|
||||
this.flags: 0,
|
||||
this.actions: 0,
|
||||
this.label: '',
|
||||
@ -152,7 +151,7 @@ class TestSemantics {
|
||||
/// The tags of this node.
|
||||
final Set<SemanticsTag> tags;
|
||||
|
||||
bool _matches(SemanticsNode node, Map<dynamic, dynamic> matchState, { bool ignoreRect: false, bool ignoreTransform: false }) {
|
||||
bool _matches(SemanticsNode node, Map<dynamic, dynamic> matchState, { bool ignoreRect: false, bool ignoreTransform: false, bool ignoreId: false }) {
|
||||
final SemanticsData nodeData = node.getSemanticsData();
|
||||
|
||||
bool fail(String message) {
|
||||
@ -162,7 +161,7 @@ class TestSemantics {
|
||||
|
||||
if (node == null)
|
||||
return fail('could not find node with id $id.');
|
||||
if (id != node.id)
|
||||
if (!ignoreId && id != node.id)
|
||||
return fail('expected node id $id but found id ${node.id}.');
|
||||
if (flags != nodeData.flags)
|
||||
return fail('expected node id $id to have flags $flags but found flags ${nodeData.flags}.');
|
||||
@ -188,7 +187,7 @@ class TestSemantics {
|
||||
final Iterator<TestSemantics> it = children.iterator;
|
||||
node.visitChildren((SemanticsNode node) {
|
||||
it.moveNext();
|
||||
if (!it.current._matches(node, matchState, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform)) {
|
||||
if (!it.current._matches(node, matchState, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform, ignoreId: ignoreId)) {
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
@ -233,15 +232,16 @@ class SemanticsTester {
|
||||
}
|
||||
|
||||
class _HasSemantics extends Matcher {
|
||||
const _HasSemantics(this._semantics, { this.ignoreRect: false, this.ignoreTransform: false }) : assert(_semantics != null), assert(ignoreRect != null), assert(ignoreTransform != null);
|
||||
const _HasSemantics(this._semantics, { this.ignoreRect: false, this.ignoreTransform: false, this.ignoreId: false }) : assert(_semantics != null), assert(ignoreRect != null), assert(ignoreId != null), assert(ignoreTransform != null);
|
||||
|
||||
final TestSemantics _semantics;
|
||||
final bool ignoreRect;
|
||||
final bool ignoreTransform;
|
||||
final bool ignoreId;
|
||||
|
||||
@override
|
||||
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||
return _semantics._matches(item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, matchState, ignoreTransform: ignoreTransform, ignoreRect: ignoreRect);
|
||||
return _semantics._matches(item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, matchState, ignoreTransform: ignoreTransform, ignoreRect: ignoreRect, ignoreId: ignoreId);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -259,7 +259,8 @@ class _HasSemantics extends Matcher {
|
||||
Matcher hasSemantics(TestSemantics semantics, {
|
||||
bool ignoreRect: false,
|
||||
bool ignoreTransform: false,
|
||||
}) => new _HasSemantics(semantics, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform);
|
||||
bool ignoreId: false,
|
||||
}) => new _HasSemantics(semantics, ignoreRect: ignoreRect, ignoreTransform: ignoreTransform, ignoreId: ignoreId);
|
||||
|
||||
class _IncludesNodeWith extends Matcher {
|
||||
const _IncludesNodeWith({
|
||||
|
||||
67
packages/flutter/test/widgets/simple_semantics_test.dart
Normal file
67
packages/flutter/test/widgets/simple_semantics_test.dart
Normal file
@ -0,0 +1,67 @@
|
||||
// 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/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Simple tree is simple', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const Center(
|
||||
child: const Text('Hello!', textDirection: TextDirection.ltr)
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
label: 'Hello!',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 84.0, 14.0),
|
||||
transform: new Matrix4.translationValues(358.0, 293.0, 0.0),
|
||||
)
|
||||
],
|
||||
)));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Simple tree is simple - material', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
// Not using Text widget because of https://github.com/flutter/flutter/issues/12357.
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Center(
|
||||
child: new Semantics(
|
||||
label: 'Hello!',
|
||||
child: new Container(
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 2,
|
||||
label: 'Hello!',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
|
||||
transform: new Matrix4.translationValues(395.0, 295.0, 0.0),
|
||||
)
|
||||
],
|
||||
)));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
@ -50,7 +50,7 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 4,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
@ -58,15 +58,15 @@ void main() {
|
||||
actions: SemanticsAction.scrollUp.index,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
id: 1,
|
||||
label: 'Item 0',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
id: 2,
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
id: 3,
|
||||
label: 'Semantics Test with Slivers',
|
||||
),
|
||||
],
|
||||
@ -88,7 +88,7 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 4,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
@ -96,11 +96,11 @@ void main() {
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
id: 1,
|
||||
label: 'Item 0',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
id: 2,
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
@ -110,7 +110,7 @@ void main() {
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 7,
|
||||
id: 3,
|
||||
label: 'Semantics Test with Slivers',
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
),
|
||||
@ -131,7 +131,7 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
id: 4,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
@ -139,11 +139,11 @@ void main() {
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
id: 1,
|
||||
label: 'Item 0',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
id: 2,
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
@ -151,7 +151,7 @@ void main() {
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 8,
|
||||
id: 3,
|
||||
label: 'Semantics Test with Slivers',
|
||||
),
|
||||
],
|
||||
@ -206,16 +206,16 @@ void main() {
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 12,
|
||||
id: 10,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 10,
|
||||
id: 7,
|
||||
label: 'Item 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 11,
|
||||
id: 8,
|
||||
label: 'Item 1',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
@ -256,34 +256,34 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 13,
|
||||
id: 16,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 19,
|
||||
id: 17,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 14,
|
||||
id: 11,
|
||||
label: 'Item 4',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 15,
|
||||
id: 12,
|
||||
label: 'Item 3',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 16,
|
||||
id: 13,
|
||||
label: 'Item 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 17,
|
||||
id: 14,
|
||||
label: 'Item 1',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 18,
|
||||
id: 15,
|
||||
label: 'Item 0',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
@ -319,6 +319,7 @@ void main() {
|
||||
const SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
title: const Text('AppBar'),
|
||||
),
|
||||
new SliverList(
|
||||
delegate: new SliverChildListDelegate(listChildren),
|
||||
@ -336,31 +337,31 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 20,
|
||||
id: 22,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 25,
|
||||
id: 23,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
// Item 0 is missing because its covered by the app bar.
|
||||
new TestSemantics(
|
||||
id: 21,
|
||||
id: 18,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 22,
|
||||
id: 19,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 23,
|
||||
id: 20,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
@ -368,14 +369,16 @@ void main() {
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 24,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
id: 21,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
label: 'AppBar',
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
ignoreTransform: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
@ -403,6 +406,7 @@ void main() {
|
||||
const SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
title: const Text('AppBar'),
|
||||
),
|
||||
]..addAll(slivers),
|
||||
),
|
||||
@ -416,29 +420,29 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 26,
|
||||
id: 28,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 31,
|
||||
id: 29,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 27,
|
||||
id: 24,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 28,
|
||||
id: 25,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 29,
|
||||
id: 26,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
|
||||
@ -448,14 +452,16 @@ void main() {
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 30,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
id: 27,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
label: 'AppBar'
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
ignoreTransform: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
@ -481,6 +487,7 @@ void main() {
|
||||
const SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
title: const Text('AppBar'),
|
||||
),
|
||||
new SliverList(
|
||||
delegate: new SliverChildListDelegate(listChildren),
|
||||
@ -498,31 +505,31 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 32,
|
||||
id: 34,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 37,
|
||||
id: 35,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
// Item 0 is missing because its covered by the app bar.
|
||||
new TestSemantics(
|
||||
id: 33,
|
||||
id: 30,
|
||||
// Item 1 ends at 580dp, so there would be 20dp space for Item 0.
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 34,
|
||||
id: 31,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 35,
|
||||
id: 32,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
@ -530,15 +537,17 @@ void main() {
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 36,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
id: 33,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
label: 'AppBar'
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
ignoreTransform: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
@ -567,6 +576,7 @@ void main() {
|
||||
const SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
title: const Text('AppBar'),
|
||||
),
|
||||
]..addAll(slivers),
|
||||
),
|
||||
@ -580,29 +590,29 @@ void main() {
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 38,
|
||||
id: 40,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 43,
|
||||
id: 41,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 39,
|
||||
id: 36,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 40,
|
||||
id: 37,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 41,
|
||||
id: 38,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
// Item 1 ends at 580dp, so there would be 20dp space for Item 0.
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
|
||||
@ -612,15 +622,17 @@ void main() {
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 42,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
id: 39,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 120.0, 20.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
label: 'AppBar'
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
ignoreTransform: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user