mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +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 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) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
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,
|
|||||||
///
|
///
|
||||||
/// 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,
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
_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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
///
|
///
|
||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
// 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 {
|
|||||||
_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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
_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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
_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 {
|
|||||||
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';
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
|
|
||||||
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> {
|
|||||||
@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 {
|
|||||||
|| 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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
@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() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
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 {
|
|||||||
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
|
|||||||
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';
|
|||||||
|
|
||||||
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 {
|
|||||||
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';
|
|||||||
|
|
||||||
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';
|
|||||||
|
|
||||||
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';
|
|||||||
|
|
||||||
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';
|
|||||||
|
|
||||||
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';
|
|||||||
|
|
||||||
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;
|
|||||||
|
|
||||||
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';
|
|||||||
|
|
||||||
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 @@
|
|||||||
// 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 {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user