Commit 5ed8f1a1 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add semantics for Sliders (#4808)

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