mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Add semantics for Sliders (#4808)
Also, make SemanticsOwner into a real class and use it instead of a static in several places.
This commit is contained in:
parent
f0671edfdb
commit
5ed8f1a1fd
|
@ -180,6 +180,8 @@ final Tween<double> _kLabelBalloonRadiusTween = new Tween<double>(begin: _kThumb
|
|||
final Tween<double> _kLabelBalloonTipTween = new Tween<double>(begin: 0.0, end: -8.0);
|
||||
final double _kLabelBalloonTipAttachmentRatio = math.sin(math.PI / 4.0);
|
||||
|
||||
const double _kAdjustmentUnit = 0.1; // Matches iOS implementation of material slider.
|
||||
|
||||
double _getAdditionalHeightForLabel(String label) {
|
||||
return label == null ? 0.0 : _kLabelBalloonRadius * 2.0;
|
||||
}
|
||||
|
@ -191,7 +193,7 @@ BoxConstraints _getAdditionalConstraints(String label) {
|
|||
);
|
||||
}
|
||||
|
||||
class _RenderSlider extends RenderConstrainedBox {
|
||||
class _RenderSlider extends RenderConstrainedBox implements SemanticActionHandler {
|
||||
_RenderSlider({
|
||||
double value,
|
||||
int divisions,
|
||||
|
@ -291,8 +293,10 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||
return dragValue;
|
||||
}
|
||||
|
||||
bool get isInteractive => onChanged != null;
|
||||
|
||||
void _handleDragStart(DragStartDetails details) {
|
||||
if (onChanged != null) {
|
||||
if (isInteractive) {
|
||||
_active = true;
|
||||
_currentDragValue = (globalToLocal(details.globalPosition).x - _kReactionRadius) / _trackLength;
|
||||
onChanged(_discretizedCurrentDragValue);
|
||||
|
@ -302,7 +306,7 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||
}
|
||||
|
||||
void _handleDragUpdate(DragUpdateDetails details) {
|
||||
if (onChanged != null) {
|
||||
if (isInteractive) {
|
||||
_currentDragValue += details.primaryDelta / _trackLength;
|
||||
onChanged(_discretizedCurrentDragValue);
|
||||
}
|
||||
|
@ -322,7 +326,7 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||
|
||||
@override
|
||||
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
||||
if (event is PointerDownEvent && onChanged != null)
|
||||
if (event is PointerDownEvent && isInteractive)
|
||||
_drag.addPointer(event);
|
||||
}
|
||||
|
||||
|
@ -331,7 +335,7 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||
final Canvas canvas = context.canvas;
|
||||
|
||||
final double trackLength = _trackLength;
|
||||
final bool enabled = onChanged != null;
|
||||
final bool enabled = isInteractive;
|
||||
final double value = _position.value;
|
||||
|
||||
final double additionalHeightForLabel = _getAdditionalHeightForLabel(label);
|
||||
|
@ -417,4 +421,32 @@ class _RenderSlider extends RenderConstrainedBox {
|
|||
}
|
||||
canvas.drawCircle(thumbCenter, thumbRadius + thumbRadiusDelta, thumbPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get hasSemantics => isInteractive;
|
||||
|
||||
@override
|
||||
Iterable<SemanticAnnotator> getSemanticAnnotators() sync* {
|
||||
yield (SemanticsNode semantics) {
|
||||
if (isInteractive)
|
||||
semantics.addAdjustmentActions();
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(SemanticAction action) {
|
||||
switch (action) {
|
||||
case SemanticAction.increase:
|
||||
if (isInteractive)
|
||||
onChanged((value + _kAdjustmentUnit).clamp(0.0, 1.0));
|
||||
break;
|
||||
case SemanticAction.decrease:
|
||||
if (isInteractive)
|
||||
onChanged((value - _kAdjustmentUnit).clamp(0.0, 1.0));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,13 +133,19 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
|
|||
///
|
||||
/// Called automatically when the binding is created.
|
||||
void initSemantics() {
|
||||
SemanticsNode.onSemanticsEnabled = renderView.scheduleInitialSemantics;
|
||||
shell.provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
|
||||
ensureSemantics();
|
||||
mojom.SemanticsServerStub server = new mojom.SemanticsServerStub.fromEndpoint(endpoint);
|
||||
server.impl = new SemanticsServer();
|
||||
server.impl = new SemanticsServer(semanticsOwner: pipelineOwner.semanticsOwner);
|
||||
});
|
||||
}
|
||||
|
||||
void ensureSemantics() {
|
||||
if (pipelineOwner.semanticsOwner == null)
|
||||
renderView.scheduleInitialSemantics();
|
||||
assert(pipelineOwner.semanticsOwner != null);
|
||||
}
|
||||
|
||||
void _handlePersistentFrameCallback(Duration timeStamp) {
|
||||
beginFrame();
|
||||
}
|
||||
|
@ -153,10 +159,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
|
|||
pipelineOwner.flushCompositingBits();
|
||||
pipelineOwner.flushPaint();
|
||||
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||
if (SemanticsNode.hasListeners) {
|
||||
pipelineOwner.flushSemantics();
|
||||
SemanticsNode.sendSemanticsTree();
|
||||
}
|
||||
pipelineOwner.flushSemantics();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -507,12 +507,12 @@ class _SemanticsGeometry {
|
|||
|
||||
abstract class _SemanticsFragment {
|
||||
_SemanticsFragment({
|
||||
RenderObject owner,
|
||||
RenderObject renderObjectOwner,
|
||||
Iterable<SemanticAnnotator> annotators,
|
||||
List<_SemanticsFragment> children
|
||||
}) {
|
||||
assert(owner != null);
|
||||
_ancestorChain = <RenderObject>[owner];
|
||||
assert(renderObjectOwner != null);
|
||||
_ancestorChain = <RenderObject>[renderObjectOwner];
|
||||
if (annotators != null)
|
||||
addAnnotators(annotators);
|
||||
assert(() {
|
||||
|
@ -531,7 +531,7 @@ abstract class _SemanticsFragment {
|
|||
_ancestorChain.add(ancestor);
|
||||
}
|
||||
|
||||
RenderObject get owner => _ancestorChain.first;
|
||||
RenderObject get renderObjectOwner => _ancestorChain.first;
|
||||
|
||||
List<SemanticAnnotator> _annotators;
|
||||
void addAnnotators(Iterable<SemanticAnnotator> moreAnnotators) {
|
||||
|
@ -555,20 +555,20 @@ abstract class _SemanticsFragment {
|
|||
/// that comes from the (dirty) ancestors.)
|
||||
class _CleanSemanticsFragment extends _SemanticsFragment {
|
||||
_CleanSemanticsFragment({
|
||||
RenderObject owner
|
||||
}) : super(owner: owner) {
|
||||
assert(owner._semantics != null);
|
||||
RenderObject renderObjectOwner
|
||||
}) : super(renderObjectOwner: renderObjectOwner) {
|
||||
assert(renderObjectOwner._semantics != null);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<SemanticsNode> compile({ _SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics }) sync* {
|
||||
assert(!_debugCompiled);
|
||||
assert(() { _debugCompiled = true; return true; });
|
||||
SemanticsNode node = owner._semantics;
|
||||
SemanticsNode node = renderObjectOwner._semantics;
|
||||
assert(node != null);
|
||||
if (geometry != null) {
|
||||
geometry.applyAncestorChain(_ancestorChain);
|
||||
geometry.updateSemanticsNode(rendering: owner, semantics: node, parentSemantics: parentSemantics);
|
||||
geometry.updateSemanticsNode(rendering: renderObjectOwner, semantics: node, parentSemantics: parentSemantics);
|
||||
} else {
|
||||
assert(_ancestorChain.length == 1);
|
||||
}
|
||||
|
@ -578,10 +578,10 @@ class _CleanSemanticsFragment extends _SemanticsFragment {
|
|||
|
||||
abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
|
||||
_InterestingSemanticsFragment({
|
||||
RenderObject owner,
|
||||
RenderObject renderObjectOwner,
|
||||
Iterable<SemanticAnnotator> annotators,
|
||||
Iterable<_SemanticsFragment> children
|
||||
}) : super(owner: owner, annotators: annotators, children: children);
|
||||
}) : super(renderObjectOwner: renderObjectOwner, annotators: annotators, children: children);
|
||||
|
||||
bool get haveConcreteNode => true;
|
||||
|
||||
|
@ -593,7 +593,7 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
|
|||
for (SemanticAnnotator annotator in _annotators)
|
||||
annotator(node);
|
||||
for (_SemanticsFragment child in _children) {
|
||||
assert(child._ancestorChain.last == owner);
|
||||
assert(child._ancestorChain.last == renderObjectOwner);
|
||||
node.addChildren(child.compile(
|
||||
geometry: createSemanticsGeometryForChild(geometry),
|
||||
currentSemantics: _children.length > 1 ? null : node,
|
||||
|
@ -612,10 +612,10 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
|
|||
|
||||
class _RootSemanticsFragment extends _InterestingSemanticsFragment {
|
||||
_RootSemanticsFragment({
|
||||
RenderObject owner,
|
||||
RenderObject renderObjectOwner,
|
||||
Iterable<SemanticAnnotator> annotators,
|
||||
Iterable<_SemanticsFragment> children
|
||||
}) : super(owner: owner, annotators: annotators, children: children);
|
||||
}) : super(renderObjectOwner: renderObjectOwner, annotators: annotators, children: children);
|
||||
|
||||
@override
|
||||
SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) {
|
||||
|
@ -623,14 +623,14 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
|
|||
assert(geometry == null);
|
||||
assert(currentSemantics == null);
|
||||
assert(parentSemantics == null);
|
||||
owner._semantics ??= new SemanticsNode.root(
|
||||
handler: owner is SemanticActionHandler ? owner as dynamic : null,
|
||||
owner: owner.owner
|
||||
renderObjectOwner._semantics ??= new SemanticsNode.root(
|
||||
handler: renderObjectOwner is SemanticActionHandler ? renderObjectOwner as dynamic : null,
|
||||
owner: renderObjectOwner.owner.semanticsOwner
|
||||
);
|
||||
SemanticsNode node = owner._semantics;
|
||||
SemanticsNode node = renderObjectOwner._semantics;
|
||||
assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity()));
|
||||
assert(!node.wasAffectedByClip);
|
||||
node.rect = owner.semanticBounds;
|
||||
node.rect = renderObjectOwner.semanticBounds;
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -642,20 +642,20 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
|
|||
|
||||
class _ConcreteSemanticsFragment extends _InterestingSemanticsFragment {
|
||||
_ConcreteSemanticsFragment({
|
||||
RenderObject owner,
|
||||
RenderObject renderObjectOwner,
|
||||
Iterable<SemanticAnnotator> annotators,
|
||||
Iterable<_SemanticsFragment> children
|
||||
}) : super(owner: owner, annotators: annotators, children: children);
|
||||
}) : super(renderObjectOwner: renderObjectOwner, annotators: annotators, children: children);
|
||||
|
||||
@override
|
||||
SemanticsNode establishSemanticsNode(_SemanticsGeometry geometry, SemanticsNode currentSemantics, SemanticsNode parentSemantics) {
|
||||
owner._semantics ??= new SemanticsNode(
|
||||
handler: owner is SemanticActionHandler ? owner as dynamic : null
|
||||
renderObjectOwner._semantics ??= new SemanticsNode(
|
||||
handler: renderObjectOwner is SemanticActionHandler ? renderObjectOwner as dynamic : null
|
||||
);
|
||||
SemanticsNode node = owner._semantics;
|
||||
SemanticsNode node = renderObjectOwner._semantics;
|
||||
if (geometry != null) {
|
||||
geometry.applyAncestorChain(_ancestorChain);
|
||||
geometry.updateSemanticsNode(rendering: owner, semantics: node, parentSemantics: parentSemantics);
|
||||
geometry.updateSemanticsNode(rendering: renderObjectOwner, semantics: node, parentSemantics: parentSemantics);
|
||||
} else {
|
||||
assert(_ancestorChain.length == 1);
|
||||
}
|
||||
|
@ -670,10 +670,10 @@ class _ConcreteSemanticsFragment extends _InterestingSemanticsFragment {
|
|||
|
||||
class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment {
|
||||
_ImplicitSemanticsFragment({
|
||||
RenderObject owner,
|
||||
RenderObject renderObjectOwner,
|
||||
Iterable<SemanticAnnotator> annotators,
|
||||
Iterable<_SemanticsFragment> children
|
||||
}) : super(owner: owner, annotators: annotators, children: children);
|
||||
}) : super(renderObjectOwner: renderObjectOwner, annotators: annotators, children: children);
|
||||
|
||||
@override
|
||||
bool get haveConcreteNode => _haveConcreteNode;
|
||||
|
@ -685,18 +685,18 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment {
|
|||
assert(_haveConcreteNode == null);
|
||||
_haveConcreteNode = currentSemantics == null && _annotators.isNotEmpty;
|
||||
if (haveConcreteNode) {
|
||||
owner._semantics ??= new SemanticsNode(
|
||||
handler: owner is SemanticActionHandler ? owner as dynamic : null
|
||||
renderObjectOwner._semantics ??= new SemanticsNode(
|
||||
handler: renderObjectOwner is SemanticActionHandler ? renderObjectOwner as dynamic : null
|
||||
);
|
||||
node = owner._semantics;
|
||||
node = renderObjectOwner._semantics;
|
||||
} else {
|
||||
owner._semantics = null;
|
||||
renderObjectOwner._semantics = null;
|
||||
node = currentSemantics;
|
||||
}
|
||||
if (geometry != null) {
|
||||
geometry.applyAncestorChain(_ancestorChain);
|
||||
if (haveConcreteNode)
|
||||
geometry.updateSemanticsNode(rendering: owner, semantics: node, parentSemantics: parentSemantics);
|
||||
geometry.updateSemanticsNode(rendering: renderObjectOwner, semantics: node, parentSemantics: parentSemantics);
|
||||
} else {
|
||||
assert(_ancestorChain.length == 1);
|
||||
}
|
||||
|
@ -713,9 +713,9 @@ class _ImplicitSemanticsFragment extends _InterestingSemanticsFragment {
|
|||
|
||||
class _ForkingSemanticsFragment extends _SemanticsFragment {
|
||||
_ForkingSemanticsFragment({
|
||||
RenderObject owner,
|
||||
RenderObject renderObjectOwner,
|
||||
Iterable<_SemanticsFragment> children
|
||||
}) : super(owner: owner, children: children) {
|
||||
}) : super(renderObjectOwner: renderObjectOwner, children: children) {
|
||||
assert(children != null);
|
||||
assert(children.length > 1);
|
||||
}
|
||||
|
@ -727,7 +727,7 @@ class _ForkingSemanticsFragment extends _SemanticsFragment {
|
|||
assert(geometry != null);
|
||||
geometry.applyAncestorChain(_ancestorChain);
|
||||
for (_SemanticsFragment child in _children) {
|
||||
assert(child._ancestorChain.last == owner);
|
||||
assert(child._ancestorChain.last == renderObjectOwner);
|
||||
yield* child.compile(
|
||||
geometry: new _SemanticsGeometry.copy(geometry),
|
||||
currentSemantics: null,
|
||||
|
@ -880,7 +880,8 @@ class PipelineOwner {
|
|||
}
|
||||
}
|
||||
|
||||
bool _semanticsEnabled = false;
|
||||
SemanticsOwner get semanticsOwner => _semanticsOwner;
|
||||
SemanticsOwner _semanticsOwner;
|
||||
bool _debugDoingSemantics = false;
|
||||
List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
|
||||
|
||||
|
@ -892,8 +893,10 @@ class PipelineOwner {
|
|||
///
|
||||
/// See [RendererBinding] for an example of how this function is used.
|
||||
void flushSemantics() {
|
||||
if (_semanticsOwner == null)
|
||||
return;
|
||||
Timeline.startSync('Semantics');
|
||||
assert(_semanticsEnabled);
|
||||
assert(_semanticsOwner != null);
|
||||
assert(() { _debugDoingSemantics = true; return true; });
|
||||
try {
|
||||
_nodesNeedingSemantics.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||
|
@ -906,6 +909,7 @@ class PipelineOwner {
|
|||
assert(() { _debugDoingSemantics = false; return true; });
|
||||
Timeline.finishSync();
|
||||
}
|
||||
_semanticsOwner.sendSemanticsTree();
|
||||
}
|
||||
|
||||
/// Cause the entire subtree rooted at the given [RenderObject] to
|
||||
|
@ -1858,8 +1862,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
assert(!owner._debugDoingSemantics);
|
||||
assert(_semantics == null);
|
||||
assert(_needsSemanticsUpdate);
|
||||
assert(owner._semanticsEnabled == false);
|
||||
owner._semanticsEnabled = true;
|
||||
assert(owner._semanticsOwner == null);
|
||||
owner._semanticsOwner = new SemanticsOwner();
|
||||
owner._nodesNeedingSemantics.add(this);
|
||||
owner.requestVisualUpdate();
|
||||
}
|
||||
|
@ -1917,7 +1921,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
/// tree will be out of date.
|
||||
void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) {
|
||||
assert(!attached || !owner._debugDoingSemantics);
|
||||
if ((attached && !owner._semanticsEnabled) || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
|
||||
if ((attached && owner._semanticsOwner == null) || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
|
||||
return;
|
||||
if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) {
|
||||
// Since the geometry might have changed, we need to make sure to reapply any clips.
|
||||
|
@ -1981,7 +1985,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
// early-exit if we're not dirty and have our own semantics
|
||||
if (!_needsSemanticsUpdate && hasSemantics) {
|
||||
assert(_semantics != null);
|
||||
return new _CleanSemanticsFragment(owner: this);
|
||||
return new _CleanSemanticsFragment(renderObjectOwner: this);
|
||||
}
|
||||
List<_SemanticsFragment> children;
|
||||
visitChildrenForSemantics((RenderObject child) {
|
||||
|
@ -2004,16 +2008,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
_needsSemanticsGeometryUpdate = false;
|
||||
Iterable<SemanticAnnotator> annotators = getSemanticAnnotators();
|
||||
if (parent is! RenderObject)
|
||||
return new _RootSemanticsFragment(owner: this, annotators: annotators, children: children);
|
||||
return new _RootSemanticsFragment(renderObjectOwner: this, annotators: annotators, children: children);
|
||||
if (hasSemantics)
|
||||
return new _ConcreteSemanticsFragment(owner: this, annotators: annotators, children: children);
|
||||
return new _ConcreteSemanticsFragment(renderObjectOwner: this, annotators: annotators, children: children);
|
||||
if (annotators.isNotEmpty)
|
||||
return new _ImplicitSemanticsFragment(owner: this, annotators: annotators, children: children);
|
||||
return new _ImplicitSemanticsFragment(renderObjectOwner: this, annotators: annotators, children: children);
|
||||
_semantics = null;
|
||||
if (children == null)
|
||||
return null;
|
||||
if (children.length > 1)
|
||||
return new _ForkingSemanticsFragment(owner: this, children: children);
|
||||
return new _ForkingSemanticsFragment(renderObjectOwner: this, children: children);
|
||||
assert(children.length == 1);
|
||||
return children.single;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ class SemanticsNode extends AbstractNode {
|
|||
/// The root node is assigned an identifier of zero.
|
||||
SemanticsNode.root({
|
||||
SemanticActionHandler handler,
|
||||
Object owner
|
||||
SemanticsOwner owner
|
||||
}) : _id = 0,
|
||||
_actionHandler = handler {
|
||||
attach(owner);
|
||||
|
@ -131,21 +131,34 @@ class SemanticsNode extends AbstractNode {
|
|||
|
||||
final Set<SemanticAction> _actions = new Set<SemanticAction>();
|
||||
|
||||
/// Adds the given action to the set of semantic actions.
|
||||
///
|
||||
/// If the user chooses to perform an action,
|
||||
/// [SemanticActionHandler.performAction] will be called with the chosen
|
||||
/// action.
|
||||
void addAction(SemanticAction action) {
|
||||
if (_actions.add(action))
|
||||
_markDirty();
|
||||
}
|
||||
|
||||
/// Adds the [SemanticAction.scrollLeft] and [SemanticAction.scrollRight] actions.
|
||||
void addHorizontalScrollingActions() {
|
||||
addAction(SemanticAction.scrollLeft);
|
||||
addAction(SemanticAction.scrollRight);
|
||||
}
|
||||
|
||||
/// Adds the [SemanticAction.scrollUp] and [SemanticAction.scrollDown] actions.
|
||||
void addVerticalScrollingActions() {
|
||||
addAction(SemanticAction.scrollUp);
|
||||
addAction(SemanticAction.scrollDown);
|
||||
}
|
||||
|
||||
/// Adds the [SemanticAction.increase] and [SemanticAction.decrease] actions.
|
||||
void addAdjustmentActions() {
|
||||
addAction(SemanticAction.increase);
|
||||
addAction(SemanticAction.decrease);
|
||||
}
|
||||
|
||||
bool _hasAction(SemanticAction action) {
|
||||
return _actionHandler != null && _actions.contains(action);
|
||||
}
|
||||
|
@ -285,6 +298,9 @@ class SemanticsNode extends AbstractNode {
|
|||
_markDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
SemanticsOwner get owner => super.owner;
|
||||
|
||||
@override
|
||||
SemanticsNode get parent => super.parent;
|
||||
|
||||
|
@ -309,15 +325,16 @@ class SemanticsNode extends AbstractNode {
|
|||
return true;
|
||||
}
|
||||
|
||||
static Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
|
||||
static Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
|
||||
|
||||
@override
|
||||
void attach(Object owner) {
|
||||
void attach(SemanticsOwner owner) {
|
||||
super.attach(owner);
|
||||
assert(!_nodes.containsKey(_id));
|
||||
_nodes[_id] = this;
|
||||
_detachedNodes.remove(this);
|
||||
assert(!owner._nodes.containsKey(_id));
|
||||
owner._nodes[_id] = this;
|
||||
owner._detachedNodes.remove(this);
|
||||
if (_dirty) {
|
||||
_dirty = false;
|
||||
_markDirty();
|
||||
}
|
||||
if (parent != null)
|
||||
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
|
||||
if (_children != null) {
|
||||
|
@ -328,26 +345,27 @@ class SemanticsNode extends AbstractNode {
|
|||
|
||||
@override
|
||||
void detach() {
|
||||
assert(owner._nodes.containsKey(_id));
|
||||
assert(!owner._detachedNodes.contains(this));
|
||||
owner._nodes.remove(_id);
|
||||
owner._detachedNodes.add(this);
|
||||
super.detach();
|
||||
assert(_nodes.containsKey(_id));
|
||||
assert(!_detachedNodes.contains(this));
|
||||
_nodes.remove(_id);
|
||||
_detachedNodes.add(this);
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
child.detach();
|
||||
}
|
||||
}
|
||||
|
||||
static List<SemanticsNode> _dirtyNodes = <SemanticsNode>[];
|
||||
bool _dirty = false;
|
||||
void _markDirty() {
|
||||
if (_dirty)
|
||||
return;
|
||||
_dirty = true;
|
||||
assert(!_dirtyNodes.contains(this));
|
||||
assert(!_detachedNodes.contains(this));
|
||||
_dirtyNodes.add(this);
|
||||
if (attached) {
|
||||
assert(!owner._dirtyNodes.contains(this));
|
||||
assert(!owner._detachedNodes.contains(this));
|
||||
owner._dirtyNodes.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
mojom.SemanticsNode _serialize() {
|
||||
|
@ -397,35 +415,71 @@ class SemanticsNode extends AbstractNode {
|
|||
return result;
|
||||
}
|
||||
|
||||
static List<mojom.SemanticsListener> _listeners;
|
||||
@override
|
||||
String toString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.write('$runtimeType($_id');
|
||||
if (_dirty)
|
||||
buffer.write(" (${ owner != null && owner._dirtyNodes.contains(this) ? 'dirty' : 'STALE' })");
|
||||
if (_shouldMergeAllDescendantsIntoThisNode)
|
||||
buffer.write(' (leaf merge)');
|
||||
buffer.write('; $rect');
|
||||
if (wasAffectedByClip)
|
||||
buffer.write(' (clipped)');
|
||||
for (SemanticAction action in _actions) {
|
||||
buffer.write('; $action');
|
||||
}
|
||||
if (hasCheckedState) {
|
||||
if (isChecked)
|
||||
buffer.write('; checked');
|
||||
else
|
||||
buffer.write('; unchecked');
|
||||
}
|
||||
if (label.isNotEmpty)
|
||||
buffer.write('; "$label"');
|
||||
buffer.write(')');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// Returns a string representation of this node and its descendants.
|
||||
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
|
||||
String result = '$prefixLineOne$this\n';
|
||||
if (_children != null && _children.isNotEmpty) {
|
||||
for (int index = 0; index < _children.length - 1; index += 1) {
|
||||
SemanticsNode child = _children[index];
|
||||
result += '${child.toStringDeep("$prefixOtherLines \u251C", "$prefixOtherLines \u2502")}';
|
||||
}
|
||||
result += '${_children.last.toStringDeep("$prefixOtherLines \u2514", "$prefixOtherLines ")}';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class SemanticsOwner {
|
||||
final List<SemanticsNode> _dirtyNodes = <SemanticsNode>[];
|
||||
final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
|
||||
final Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
|
||||
|
||||
List<mojom.SemanticsListener> _listeners;
|
||||
|
||||
/// Whether there are currently any consumers of semantic data.
|
||||
///
|
||||
/// If there are no consumers of semantic data, there is no need to compile
|
||||
/// semantic data into a [SemanticsNode] tree.
|
||||
static bool get hasListeners => _listeners != null && _listeners.length > 0;
|
||||
|
||||
/// Called when the first consumer of semantic data arrives.
|
||||
///
|
||||
/// Typically set by [RendererBinding].
|
||||
static VoidCallback onSemanticsEnabled;
|
||||
bool get hasListeners => _listeners != null && _listeners.length > 0;
|
||||
|
||||
/// Add a consumer of semantic data.
|
||||
///
|
||||
/// After the [PipelineOwner] updates the semantic data for a given frame, it
|
||||
/// calls [sendSemanticsTree], which uploads the data to each listener
|
||||
/// registered with this function.
|
||||
static void addListener(mojom.SemanticsListener listener) {
|
||||
if (!hasListeners) {
|
||||
assert(onSemanticsEnabled != null); // initialise the binding _before_ adding listeners
|
||||
onSemanticsEnabled();
|
||||
}
|
||||
void addListener(mojom.SemanticsListener listener) {
|
||||
_listeners ??= <mojom.SemanticsListener>[];
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/// Uploads the semantics tree to the listeners registered with [addListener].
|
||||
static void sendSemanticsTree() {
|
||||
void sendSemanticsTree() {
|
||||
assert(hasListeners);
|
||||
for (SemanticsNode oldNode in _detachedNodes) {
|
||||
// The other side will have forgotten this node if we even send
|
||||
|
@ -491,7 +545,7 @@ class SemanticsNode extends AbstractNode {
|
|||
_dirtyNodes.clear();
|
||||
}
|
||||
|
||||
static SemanticActionHandler _getSemanticActionHandlerForId(int id, { @required SemanticAction action }) {
|
||||
SemanticActionHandler _getSemanticActionHandlerForId(int id, { @required SemanticAction action }) {
|
||||
assert(action != null);
|
||||
SemanticsNode result = _nodes[id];
|
||||
if (result != null && result._shouldMergeAllDescendantsIntoThisNode && !result._hasAction(action)) {
|
||||
|
@ -508,59 +562,29 @@ class SemanticsNode extends AbstractNode {
|
|||
return result._actionHandler;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.write('$runtimeType($_id');
|
||||
if (_dirty)
|
||||
buffer.write(" (${ _dirtyNodes.contains(this) ? 'dirty' : 'STALE' })");
|
||||
if (_shouldMergeAllDescendantsIntoThisNode)
|
||||
buffer.write(' (leaf merge)');
|
||||
buffer.write('; $rect');
|
||||
if (wasAffectedByClip)
|
||||
buffer.write(' (clipped)');
|
||||
for (SemanticAction action in _actions) {
|
||||
buffer.write('; $action');
|
||||
}
|
||||
if (hasCheckedState) {
|
||||
if (isChecked)
|
||||
buffer.write('; checked');
|
||||
else
|
||||
buffer.write('; unchecked');
|
||||
}
|
||||
if (label.isNotEmpty)
|
||||
buffer.write('; "$label"');
|
||||
buffer.write(')');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// Returns a string representation of this node and its descendants.
|
||||
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
|
||||
String result = '$prefixLineOne$this\n';
|
||||
if (_children != null && _children.isNotEmpty) {
|
||||
for (int index = 0; index < _children.length - 1; index += 1) {
|
||||
SemanticsNode child = _children[index];
|
||||
result += '${child.toStringDeep("$prefixOtherLines \u251C", "$prefixOtherLines \u2502")}';
|
||||
}
|
||||
result += '${_children.last.toStringDeep("$prefixOtherLines \u2514", "$prefixOtherLines ")}';
|
||||
}
|
||||
return result;
|
||||
void performAction(int id, SemanticAction action) {
|
||||
SemanticActionHandler handler = _getSemanticActionHandlerForId(id, action: action);
|
||||
handler?.performAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// Exposes the [SemanticsNode] tree to the underlying platform.
|
||||
class SemanticsServer extends mojom.SemanticsServer {
|
||||
SemanticsServer({ @required this.semanticsOwner }) {
|
||||
assert(semanticsOwner != null);
|
||||
}
|
||||
|
||||
final SemanticsOwner semanticsOwner;
|
||||
|
||||
@override
|
||||
void addSemanticsListener(mojom.SemanticsListenerProxy listener) {
|
||||
// TODO(abarth): We should remove the listener when this pipe closes.
|
||||
// See <https://github.com/flutter/flutter/issues/3342>.
|
||||
SemanticsNode.addListener(listener);
|
||||
semanticsOwner.addListener(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(int id, mojom.SemanticAction encodedAction) {
|
||||
SemanticAction action = SemanticAction.values[encodedAction.mojoEnumValue];
|
||||
SemanticActionHandler node = SemanticsNode._getSemanticActionHandlerForId(id, action: action);
|
||||
node?.performAction(action);
|
||||
semanticsOwner.performAction(id, SemanticAction.values[encodedAction.mojoEnumValue]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
|
||||
|
||||
import 'basic.dart';
|
||||
import 'binding.dart';
|
||||
import 'framework.dart';
|
||||
import 'gesture_detector.dart';
|
||||
|
||||
|
@ -30,16 +31,23 @@ class SemanticsDebugger extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _SemanticsDebuggerState extends State<SemanticsDebugger> {
|
||||
_SemanticsClient _client;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_SemanticsDebuggerListener.ensureInstantiated();
|
||||
_SemanticsDebuggerListener.instance.addListener(_update);
|
||||
// TODO(abarth): We shouldn't reach out to the WidgetsBinding.instance
|
||||
// static here because we might not be in a tree that's attached to that
|
||||
// binding. Instead, we should find a way to get to the PipelineOwner from
|
||||
// the BuildContext.
|
||||
WidgetsBinding.instance.ensureSemantics();
|
||||
_client = new _SemanticsClient(WidgetsBinding.instance.pipelineOwner.semanticsOwner)
|
||||
..addListener(_update);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_SemanticsDebuggerListener.instance.removeListener(_update);
|
||||
_client.removeListener(_update);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -58,21 +66,21 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
|
|||
|
||||
void _handleTap() {
|
||||
assert(_lastPointerDownLocation != null);
|
||||
_SemanticsDebuggerListener.instance._performAction(_lastPointerDownLocation, SemanticAction.tap);
|
||||
_client._performAction(_lastPointerDownLocation, SemanticAction.tap);
|
||||
setState(() {
|
||||
_lastPointerDownLocation = null;
|
||||
});
|
||||
}
|
||||
void _handleLongPress() {
|
||||
assert(_lastPointerDownLocation != null);
|
||||
_SemanticsDebuggerListener.instance._performAction(_lastPointerDownLocation, SemanticAction.longPress);
|
||||
_client._performAction(_lastPointerDownLocation, SemanticAction.longPress);
|
||||
setState(() {
|
||||
_lastPointerDownLocation = null;
|
||||
});
|
||||
}
|
||||
void _handlePanEnd(DragEndDetails details) {
|
||||
assert(_lastPointerDownLocation != null);
|
||||
_SemanticsDebuggerListener.instance.handlePanEnd(_lastPointerDownLocation, details.velocity);
|
||||
_client.handlePanEnd(_lastPointerDownLocation, details.velocity);
|
||||
setState(() {
|
||||
_lastPointerDownLocation = null;
|
||||
});
|
||||
|
@ -81,7 +89,7 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new CustomPaint(
|
||||
foregroundPainter: new _SemanticsDebuggerPainter(_SemanticsDebuggerListener.instance.generation, _lastPointerDownLocation),
|
||||
foregroundPainter: new _SemanticsDebuggerPainter(_client.generation, _client, _lastPointerDownLocation),
|
||||
child: new GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleTap,
|
||||
|
@ -196,6 +204,11 @@ class _SemanticsDebuggerEntry {
|
|||
|| actions.contains(SemanticAction.scrollDown);
|
||||
}
|
||||
|
||||
bool get _isAdjustable {
|
||||
return actions.contains(SemanticAction.increase)
|
||||
|| actions.contains(SemanticAction.decrease);
|
||||
}
|
||||
|
||||
TextPainter textPainter;
|
||||
void _updateMessage() {
|
||||
List<String> annotations = <String>[];
|
||||
|
@ -215,6 +228,8 @@ class _SemanticsDebuggerEntry {
|
|||
annotations.add('long-pressable');
|
||||
if (_isScrollable)
|
||||
annotations.add('scrollable');
|
||||
if (_isAdjustable)
|
||||
annotations.add('adjustable');
|
||||
String message;
|
||||
if (annotations.isEmpty) {
|
||||
assert(label != null);
|
||||
|
@ -295,16 +310,12 @@ class _SemanticsDebuggerEntry {
|
|||
}
|
||||
}
|
||||
|
||||
class _SemanticsDebuggerListener extends ChangeNotifier implements mojom.SemanticsListener {
|
||||
_SemanticsDebuggerListener._() {
|
||||
SemanticsNode.addListener(this);
|
||||
class _SemanticsClient extends ChangeNotifier implements mojom.SemanticsListener {
|
||||
_SemanticsClient(this.semanticsOwner) {
|
||||
semanticsOwner.addListener(this);
|
||||
}
|
||||
|
||||
static _SemanticsDebuggerListener instance;
|
||||
static final SemanticsServer _server = new SemanticsServer();
|
||||
static void ensureInstantiated() {
|
||||
instance ??= new _SemanticsDebuggerListener._();
|
||||
}
|
||||
final SemanticsOwner semanticsOwner;
|
||||
|
||||
_SemanticsDebuggerEntry get rootNode => _nodes[0];
|
||||
final Map<int, _SemanticsDebuggerEntry> _nodes = <int, _SemanticsDebuggerEntry>{};
|
||||
|
@ -357,7 +368,7 @@ class _SemanticsDebuggerListener extends ChangeNotifier implements mojom.Semanti
|
|||
|
||||
void _performAction(Point position, SemanticAction action) {
|
||||
_SemanticsDebuggerEntry entry = _hitTest(position, (_SemanticsDebuggerEntry entry) => entry.actions.contains(action));
|
||||
_server.performAction(entry?.id ?? 0, mojom.SemanticAction.values[action.index]);
|
||||
semanticsOwner.performAction(entry?.id ?? 0, action);
|
||||
}
|
||||
|
||||
void handlePanEnd(Point position, Velocity velocity) {
|
||||
|
@ -366,10 +377,13 @@ class _SemanticsDebuggerListener extends ChangeNotifier implements mojom.Semanti
|
|||
if (vx.abs() == vy.abs())
|
||||
return;
|
||||
if (vx.abs() > vy.abs()) {
|
||||
if (vx.sign < 0)
|
||||
if (vx.sign < 0) {
|
||||
_performAction(position, SemanticAction.decrease);
|
||||
_performAction(position, SemanticAction.scrollLeft);
|
||||
else
|
||||
} else {
|
||||
_performAction(position, SemanticAction.increase);
|
||||
_performAction(position, SemanticAction.scrollRight);
|
||||
}
|
||||
} else {
|
||||
if (vy.sign < 0)
|
||||
_performAction(position, SemanticAction.scrollUp);
|
||||
|
@ -380,14 +394,15 @@ class _SemanticsDebuggerListener extends ChangeNotifier implements mojom.Semanti
|
|||
}
|
||||
|
||||
class _SemanticsDebuggerPainter extends CustomPainter {
|
||||
const _SemanticsDebuggerPainter(this.generation, this.pointerPosition);
|
||||
const _SemanticsDebuggerPainter(this.generation, this.client, this.pointerPosition);
|
||||
|
||||
final int generation;
|
||||
final _SemanticsClient client;
|
||||
final Point pointerPosition;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
_SemanticsDebuggerEntry rootNode = _SemanticsDebuggerListener.instance.rootNode;
|
||||
_SemanticsDebuggerEntry rootNode = client.rootNode;
|
||||
rootNode?.paint(canvas, rootNode.findDepth());
|
||||
if (pointerPosition != null) {
|
||||
Paint paint = new Paint();
|
||||
|
@ -399,6 +414,7 @@ class _SemanticsDebuggerPainter extends CustomPainter {
|
|||
@override
|
||||
bool shouldRepaint(_SemanticsDebuggerPainter oldDelegate) {
|
||||
return generation != oldDelegate.generation
|
||||
|| client != oldDelegate.client
|
||||
|| pointerPosition != oldDelegate.pointerPosition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -394,7 +394,7 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('Does tooltip contribute semantics', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
GlobalKey key = new GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
new Overlay(
|
||||
|
|
|
@ -136,9 +136,10 @@ void main() {
|
|||
test('objects can be detached and re-attached: semantics', () {
|
||||
TestTree testTree = new TestTree();
|
||||
TestSemanticsListener listener = new TestSemanticsListener();
|
||||
SemanticsNode.addListener(listener);
|
||||
renderer.ensureSemantics();
|
||||
renderer.pipelineOwner.semanticsOwner.addListener(listener);
|
||||
// Lay out, composite, paint, and update semantics
|
||||
layout(testTree.root, phase: EnginePhase.sendSemanticsTree);
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(listener.updates.length, equals(1));
|
||||
// Remove testTree from the custom render view
|
||||
renderer.renderView.child = null;
|
||||
|
@ -148,7 +149,7 @@ void main() {
|
|||
testTree.child.markNeedsSemanticsUpdate();
|
||||
expect(listener.updates.length, equals(0));
|
||||
// Lay out, composite, paint, and update semantics again
|
||||
layout(testTree.root, phase: EnginePhase.sendSemanticsTree);
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(listener.updates.length, equals(1));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@ enum EnginePhase {
|
|||
compositingBits,
|
||||
paint,
|
||||
composite,
|
||||
flushSemantics,
|
||||
sendSemanticsTree
|
||||
flushSemantics
|
||||
}
|
||||
|
||||
class TestRenderingFlutterBinding extends BindingBase with SchedulerBinding, ServicesBinding, RendererBinding, GestureBinding {
|
||||
|
@ -33,24 +32,21 @@ class TestRenderingFlutterBinding extends BindingBase with SchedulerBinding, Ser
|
|||
renderView.compositeFrame();
|
||||
if (phase == EnginePhase.composite)
|
||||
return;
|
||||
if (SemanticsNode.hasListeners) {
|
||||
pipelineOwner.flushSemantics();
|
||||
if (phase == EnginePhase.flushSemantics)
|
||||
return;
|
||||
SemanticsNode.sendSemanticsTree();
|
||||
}
|
||||
pipelineOwner.flushSemantics();
|
||||
assert(phase == EnginePhase.flushSemantics);
|
||||
}
|
||||
}
|
||||
|
||||
TestRenderingFlutterBinding _renderer;
|
||||
TestRenderingFlutterBinding get renderer => _renderer;
|
||||
TestRenderingFlutterBinding get renderer {
|
||||
_renderer ??= new TestRenderingFlutterBinding();
|
||||
return _renderer;
|
||||
}
|
||||
|
||||
void layout(RenderBox box, { BoxConstraints constraints, EnginePhase phase: EnginePhase.layout }) {
|
||||
assert(box != null); // If you want to just repump the last box, call pumpFrame().
|
||||
assert(box.parent == null); // We stick the box in another, so you can't reuse it easily, sorry.
|
||||
|
||||
_renderer ??= new TestRenderingFlutterBinding();
|
||||
|
||||
renderer.renderView.child = null;
|
||||
if (constraints != null) {
|
||||
box = new RenderPositionedBox(
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
await tester.pumpWidget(
|
||||
new Material(
|
||||
child: new Center(
|
||||
|
|
|
@ -43,10 +43,7 @@ class OffscreenWidgetTree {
|
|||
pipelineOwner.flushCompositingBits();
|
||||
pipelineOwner.flushPaint();
|
||||
renderView.compositeFrame();
|
||||
if (SemanticsNode.hasListeners) {
|
||||
pipelineOwner.flushSemantics();
|
||||
SemanticsNode.sendSemanticsTree();
|
||||
}
|
||||
pipelineOwner.flushSemantics();
|
||||
buildOwner.finalizeTree();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 1', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
// smoketest
|
||||
await tester.pumpWidget(
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 2', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
// this test is the same as the test in Semantics 1, but
|
||||
// starting with the second branch being ignored and then
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 3', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
// implicit annotators
|
||||
await tester.pumpWidget(
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 4', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
// O
|
||||
// / \ O=root
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 5', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Stack(
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 7 - Merging', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
String label;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'test_semantics.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('Semantics 8 - Merging with reset', (WidgetTester tester) async {
|
||||
TestSemanticsListener client = new TestSemanticsListener();
|
||||
TestSemanticsListener client = new TestSemanticsListener(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new MergeSemantics(
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
// 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_test/flutter_test.dart';
|
||||
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
|
||||
|
||||
class TestSemanticsListener implements mojom.SemanticsListener {
|
||||
TestSemanticsListener() {
|
||||
SemanticsNode.addListener(this);
|
||||
TestSemanticsListener(WidgetTester tester) {
|
||||
tester.binding.ensureSemantics();
|
||||
tester.binding.pipelineOwner.semanticsOwner.addListener(this);
|
||||
}
|
||||
|
||||
final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
|
||||
|
|
|
@ -463,12 +463,9 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||
if (_phase == EnginePhase.composite)
|
||||
return;
|
||||
if (SemanticsNode.hasListeners) {
|
||||
pipelineOwner.flushSemantics();
|
||||
if (_phase == EnginePhase.flushSemantics)
|
||||
return;
|
||||
SemanticsNode.sendSemanticsTree();
|
||||
}
|
||||
pipelineOwner.flushSemantics();
|
||||
if (_phase == EnginePhase.flushSemantics)
|
||||
return;
|
||||
buildOwner.finalizeTree();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue