mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
parent
e074af8099
commit
bb5a82a726
|
@ -31,7 +31,7 @@ void main() {
|
|||
|
||||
for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) {
|
||||
renderView.configuration = (i % 2 == 0) ? big : small;
|
||||
RenderObject.flushLayout();
|
||||
WidgetFlutterBinding.instance.pipelineOwner.flushLayout();
|
||||
}
|
||||
|
||||
watch.stop();
|
||||
|
|
|
@ -20,8 +20,8 @@ The last phase of a frame is the Semantics phase. This only occurs if
|
|||
a semantics server has been installed, for example if the user is
|
||||
using an accessibility tool.
|
||||
|
||||
Each frame, the semantics phase starts with a call to the static
|
||||
`RenderObject.flushSemantics()` method from the `Renderer` binding's
|
||||
Each frame, the semantics phase starts with a call to the
|
||||
`PipelineOwner.flushSemantics()` method from the `Renderer` binding's
|
||||
`beginFrame()` method.
|
||||
|
||||
Each node marked as needing semantics (which initially is just the
|
||||
|
|
|
@ -48,6 +48,10 @@ abstract class Renderer extends Object with Scheduler, Services
|
|||
handleMetricsChanged(); // configures renderView's metrics
|
||||
}
|
||||
|
||||
/// The render tree's owner, which maintains dirty state for layout,
|
||||
/// composite, paint, and accessibility semantics
|
||||
final PipelineOwner pipelineOwner = new PipelineOwner();
|
||||
|
||||
/// The render tree that's attached to the output surface.
|
||||
RenderView get renderView => _renderView;
|
||||
RenderView _renderView;
|
||||
|
@ -58,7 +62,7 @@ abstract class Renderer extends Object with Scheduler, Services
|
|||
if (_renderView != null)
|
||||
_renderView.detach();
|
||||
_renderView = value;
|
||||
_renderView.attach();
|
||||
_renderView.attach(pipelineOwner);
|
||||
}
|
||||
|
||||
void handleMetricsChanged() {
|
||||
|
@ -81,12 +85,12 @@ abstract class Renderer extends Object with Scheduler, Services
|
|||
/// Pump the rendering pipeline to generate a frame.
|
||||
void beginFrame() {
|
||||
assert(renderView != null);
|
||||
RenderObject.flushLayout();
|
||||
RenderObject.flushCompositingBits();
|
||||
RenderObject.flushPaint();
|
||||
pipelineOwner.flushLayout();
|
||||
pipelineOwner.flushCompositingBits();
|
||||
pipelineOwner.flushPaint();
|
||||
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||
if (SemanticsNode.hasListeners) {
|
||||
RenderObject.flushSemantics();
|
||||
pipelineOwner.flushSemantics();
|
||||
SemanticsNode.sendSemanticsTree();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -337,8 +337,8 @@ class RenderBlockViewport extends RenderBlockBase {
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
_overlayPainter?.attach(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -388,12 +388,7 @@ class BoxHitTestEntry extends HitTestEntry {
|
|||
/// Parent data used by [RenderBox] and its subclasses.
|
||||
class BoxParentData extends ParentData {
|
||||
/// The offset at which to paint the child in the parent's coordinate system
|
||||
Offset get offset => _offset;
|
||||
Offset _offset = Offset.zero;
|
||||
void set offset(Offset value) {
|
||||
assert(RenderObject.debugDoingLayout);
|
||||
_offset = value;
|
||||
}
|
||||
Offset offset = Offset.zero;
|
||||
|
||||
@override
|
||||
String toString() => 'offset=$offset';
|
||||
|
@ -556,9 +551,9 @@ abstract class RenderBox extends RenderObject {
|
|||
assert(!_debugDoingBaseline);
|
||||
assert(() {
|
||||
final RenderObject parent = this.parent;
|
||||
if (RenderObject.debugDoingLayout)
|
||||
if (owner.debugDoingLayout)
|
||||
return (RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout;
|
||||
if (RenderObject.debugDoingPaint)
|
||||
if (owner.debugDoingPaint)
|
||||
return ((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) ||
|
||||
((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
|
||||
assert(parent == this.parent);
|
||||
|
|
|
@ -171,8 +171,8 @@ class RenderChildView extends RenderBox {
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
_child?._attach();
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class AbstractNode {
|
|||
|
||||
/// Call only from overrides of [redepthChildren]
|
||||
void redepthChild(AbstractNode child) {
|
||||
assert(child._attached == _attached);
|
||||
assert(child.owner == owner);
|
||||
if (child._depth <= _depth) {
|
||||
child._depth = _depth + 1;
|
||||
child.redepthChildren();
|
||||
|
@ -62,16 +62,21 @@ class AbstractNode {
|
|||
/// redepthChild(child) for each child. Do not call directly.
|
||||
void redepthChildren() { }
|
||||
|
||||
bool _attached = false;
|
||||
/// Whether this node is in a tree whose root is attached to something.
|
||||
bool get attached => _attached;
|
||||
Object _owner;
|
||||
/// The owner for this node (null if unattached).
|
||||
Object get owner => _owner;
|
||||
|
||||
/// Mark this node as attached.
|
||||
/// Whether this node is in a tree whose root is attached to something.
|
||||
bool get attached => _owner != null;
|
||||
|
||||
/// Mark this node as attached to the given owner.
|
||||
///
|
||||
/// Typically called only from the parent's attach(), and to mark the root of
|
||||
/// a tree attached.
|
||||
void attach() {
|
||||
_attached = true;
|
||||
void attach(Object owner) {
|
||||
assert(owner != null);
|
||||
assert(_owner == null);
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
/// Mark this node as detached.
|
||||
|
@ -79,7 +84,8 @@ class AbstractNode {
|
|||
/// Typically called only from the parent's detach(), and to mark the root of
|
||||
/// a tree detached.
|
||||
void detach() {
|
||||
_attached = false;
|
||||
assert(_owner != null);
|
||||
_owner = null;
|
||||
}
|
||||
|
||||
AbstractNode _parent;
|
||||
|
@ -99,7 +105,7 @@ class AbstractNode {
|
|||
});
|
||||
child._parent = this;
|
||||
if (attached)
|
||||
child.attach();
|
||||
child.attach(_owner);
|
||||
redepthChild(child);
|
||||
}
|
||||
|
||||
|
|
|
@ -559,7 +559,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
|
|||
assert(currentSemantics == null);
|
||||
assert(parentSemantics == null);
|
||||
owner._semantics ??= new SemanticsNode.root(
|
||||
handler: owner is SemanticActionHandler ? owner as dynamic : null
|
||||
handler: owner is SemanticActionHandler ? owner as dynamic : null,
|
||||
owner: owner.owner
|
||||
);
|
||||
SemanticsNode node = owner._semantics;
|
||||
assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity()));
|
||||
|
@ -671,6 +672,105 @@ class _ForkingSemanticsFragment extends _SemanticsFragment {
|
|||
}
|
||||
}
|
||||
|
||||
class PipelineOwner {
|
||||
|
||||
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
|
||||
bool _debugDoingLayout = false;
|
||||
bool get debugDoingLayout => _debugDoingLayout;
|
||||
/// Update the layout information for all dirty render objects.
|
||||
///
|
||||
/// This function is one of the core stages of the rendering pipeline. Layout
|
||||
/// information is cleaned prior to painting so that render objects will
|
||||
/// appear on screen in their up-to-date locations.
|
||||
///
|
||||
/// See [FlutterBinding] for an example of how this function is used.
|
||||
void flushLayout() {
|
||||
Timeline.startSync('Layout');
|
||||
_debugDoingLayout = true;
|
||||
try {
|
||||
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves
|
||||
while (_nodesNeedingLayout.isNotEmpty) {
|
||||
List<RenderObject> dirtyNodes = _nodesNeedingLayout;
|
||||
_nodesNeedingLayout = <RenderObject>[];
|
||||
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
|
||||
if (node._needsLayout && node.owner == this)
|
||||
node._layoutWithoutResize();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_debugDoingLayout = false;
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
|
||||
/// Updates the [needsCompositing] bits.
|
||||
///
|
||||
/// Called as part of the rendering pipeline after [flushLayout] and before
|
||||
/// [flushPaint].
|
||||
void flushCompositingBits() {
|
||||
Timeline.startSync('Compositing Bits');
|
||||
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
|
||||
if (node.owner == this)
|
||||
node._updateCompositingBits();
|
||||
}
|
||||
_nodesNeedingCompositingBitsUpdate.clear();
|
||||
Timeline.finishSync();
|
||||
}
|
||||
|
||||
List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
|
||||
bool _debugDoingPaint = false;
|
||||
bool get debugDoingPaint => _debugDoingPaint;
|
||||
/// Update the display lists for all render objects.
|
||||
///
|
||||
/// This function is one of the core stages of the rendering pipeline.
|
||||
/// Painting occurs after layout and before the scene is recomposited so that
|
||||
/// scene is composited with up-to-date display lists for every render object.
|
||||
///
|
||||
/// See [FlutterBinding] for an example of how this function is used.
|
||||
void flushPaint() {
|
||||
Timeline.startSync('Paint');
|
||||
_debugDoingPaint = true;
|
||||
try {
|
||||
List<RenderObject> dirtyNodes = _nodesNeedingPaint;
|
||||
_nodesNeedingPaint = <RenderObject>[];
|
||||
// Sort the dirty nodes in reverse order (deepest first).
|
||||
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
|
||||
assert(node._needsPaint);
|
||||
if (node.owner == this)
|
||||
PaintingContext.repaintCompositedChild(node);
|
||||
};
|
||||
assert(_nodesNeedingPaint.length == 0);
|
||||
} finally {
|
||||
_debugDoingPaint = false;
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
bool _semanticsEnabled = false;
|
||||
bool _debugDoingSemantics = false;
|
||||
List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
|
||||
|
||||
void flushSemantics() {
|
||||
Timeline.startSync('Semantics');
|
||||
assert(_semanticsEnabled);
|
||||
assert(() { _debugDoingSemantics = true; return true; });
|
||||
try {
|
||||
_nodesNeedingSemantics.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||
for (RenderObject node in _nodesNeedingSemantics) {
|
||||
if (node._needsSemanticsUpdate && node.owner == this)
|
||||
node._updateSemantics();
|
||||
}
|
||||
} finally {
|
||||
_nodesNeedingSemantics.clear();
|
||||
assert(() { _debugDoingSemantics = false; return true; });
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// An object in the render tree.
|
||||
///
|
||||
/// The [RenderObject] class hierarchy is the core of the rendering
|
||||
|
@ -811,8 +911,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
}
|
||||
}
|
||||
|
||||
static bool _debugDoingLayout = false;
|
||||
static bool get debugDoingLayout => _debugDoingLayout;
|
||||
bool _debugDoingThisResize = false;
|
||||
bool get debugDoingThisResize => _debugDoingThisResize;
|
||||
bool _debugDoingThisLayout = false;
|
||||
|
@ -835,7 +933,9 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
}
|
||||
}
|
||||
|
||||
static List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
|
||||
@override
|
||||
PipelineOwner get owner => super.owner;
|
||||
|
||||
bool _needsLayout = true;
|
||||
/// Whether this render object's layout information is dirty.
|
||||
bool get needsLayout => _needsLayout;
|
||||
|
@ -912,7 +1012,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
debugPrintStack();
|
||||
return true;
|
||||
});
|
||||
_nodesNeedingLayout.add(this);
|
||||
if (owner != null)
|
||||
owner._nodesNeedingLayout.add(this);
|
||||
Scheduler.instance.ensureVisualUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -936,41 +1037,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
void scheduleInitialLayout() {
|
||||
assert(attached);
|
||||
assert(parent is! RenderObject);
|
||||
assert(!_debugDoingLayout);
|
||||
assert(!owner._debugDoingLayout);
|
||||
assert(_relayoutSubtreeRoot == null);
|
||||
_relayoutSubtreeRoot = this;
|
||||
assert(() {
|
||||
_debugCanParentUseSize = false;
|
||||
return true;
|
||||
});
|
||||
_nodesNeedingLayout.add(this);
|
||||
owner._nodesNeedingLayout.add(this);
|
||||
}
|
||||
|
||||
/// Update the layout information for all dirty render objects.
|
||||
///
|
||||
/// This function is one of the core stages of the rendering pipeline. Layout
|
||||
/// information is cleaned prior to painting so that render objects will
|
||||
/// appear on screen in their up-to-date locations.
|
||||
///
|
||||
/// See [FlutterBinding] for an example of how this function is used.
|
||||
static void flushLayout() {
|
||||
Timeline.startSync('Layout');
|
||||
_debugDoingLayout = true;
|
||||
try {
|
||||
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves
|
||||
while (_nodesNeedingLayout.isNotEmpty) {
|
||||
List<RenderObject> dirtyNodes = _nodesNeedingLayout;
|
||||
_nodesNeedingLayout = <RenderObject>[];
|
||||
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
|
||||
if (node._needsLayout && node.attached)
|
||||
node._layoutWithoutResize();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_debugDoingLayout = false;
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
void _layoutWithoutResize() {
|
||||
assert(_relayoutSubtreeRoot == this);
|
||||
RenderObject debugPreviousActiveLayout;
|
||||
|
@ -1180,16 +1256,11 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
|
||||
// PAINTING
|
||||
|
||||
static bool _debugDoingPaint = false;
|
||||
static bool get debugDoingPaint => _debugDoingPaint;
|
||||
bool _debugDoingThisPaint = false;
|
||||
bool get debugDoingThisPaint => _debugDoingThisPaint;
|
||||
static RenderObject _debugActivePaint;
|
||||
static RenderObject get debugActivePaint => _debugActivePaint;
|
||||
|
||||
static List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
|
||||
static List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
|
||||
|
||||
/// Whether this render object repaints separately from its parent.
|
||||
///
|
||||
/// Override this in subclasses to indicate that instances of your class ought
|
||||
|
@ -1260,22 +1331,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
return true;
|
||||
});
|
||||
// parent is fine (or there isn't one), but we are dirty
|
||||
_nodesNeedingCompositingBitsUpdate.add(this);
|
||||
}
|
||||
|
||||
/// Updates the [needsCompositing] bits.
|
||||
///
|
||||
/// Called as part of the rendering pipeline after [flushLayout] and before
|
||||
/// [flushPaint].
|
||||
static void flushCompositingBits() {
|
||||
Timeline.startSync('Compositing Bits');
|
||||
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
|
||||
if (node.attached)
|
||||
node._updateCompositingBits();
|
||||
}
|
||||
_nodesNeedingCompositingBitsUpdate.clear();
|
||||
Timeline.finishSync();
|
||||
if (owner != null)
|
||||
owner._nodesNeedingCompositingBitsUpdate.add(this);
|
||||
}
|
||||
|
||||
bool _needsCompositing; // initialised in the constructor
|
||||
|
@ -1319,7 +1376,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
/// This mechanism batches the painting work so that multiple sequential
|
||||
/// writes are coalesced, removing redundant computation.
|
||||
void markNeedsPaint() {
|
||||
assert(!debugDoingPaint);
|
||||
assert(owner == null || !owner.debugDoingPaint);
|
||||
if (!attached)
|
||||
return; // Don't try painting things that aren't in the hierarchy
|
||||
if (_needsPaint)
|
||||
|
@ -1334,7 +1391,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
// If we always have our own layer, then we can just repaint
|
||||
// ourselves without involving any other nodes.
|
||||
assert(_layer != null);
|
||||
_nodesNeedingPaint.add(this);
|
||||
if (owner != null)
|
||||
owner._nodesNeedingPaint.add(this);
|
||||
Scheduler.instance.ensureVisualUpdate();
|
||||
} else if (parent is RenderObject) {
|
||||
// We don't have our own layer; one of our ancestors will take
|
||||
|
@ -1353,32 +1411,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
}
|
||||
}
|
||||
|
||||
/// Update the display lists for all render objects.
|
||||
///
|
||||
/// This function is one of the core stages of the rendering pipeline.
|
||||
/// Painting occurs after layout and before the scene is recomposited so that
|
||||
/// scene is composited with up-to-date display lists for every render object.
|
||||
///
|
||||
/// See [FlutterBinding] for an example of how this function is used.
|
||||
static void flushPaint() {
|
||||
Timeline.startSync('Paint');
|
||||
_debugDoingPaint = true;
|
||||
try {
|
||||
List<RenderObject> dirtyNodes = _nodesNeedingPaint;
|
||||
_nodesNeedingPaint = <RenderObject>[];
|
||||
// Sort the dirty nodes in reverse order (deepest first).
|
||||
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
|
||||
assert(node._needsPaint);
|
||||
if (node.attached)
|
||||
PaintingContext.repaintCompositedChild(node);
|
||||
};
|
||||
assert(_nodesNeedingPaint.length == 0);
|
||||
} finally {
|
||||
_debugDoingPaint = false;
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
/// Bootstrap the rendering pipeline by scheduling the very first paint.
|
||||
///
|
||||
/// Requires that this render object is attached, is the root of the render
|
||||
|
@ -1388,12 +1420,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
void scheduleInitialPaint(ContainerLayer rootLayer) {
|
||||
assert(attached);
|
||||
assert(parent is! RenderObject);
|
||||
assert(!_debugDoingPaint);
|
||||
assert(!owner._debugDoingPaint);
|
||||
assert(isRepaintBoundary);
|
||||
assert(_layer == null);
|
||||
_layer = rootLayer;
|
||||
assert(_needsPaint);
|
||||
_nodesNeedingPaint.add(this);
|
||||
owner._nodesNeedingPaint.add(this);
|
||||
}
|
||||
void _paintWithContext(PaintingContext context, Offset offset) {
|
||||
assert(!_debugDoingThisPaint);
|
||||
|
@ -1477,10 +1509,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
|
||||
// SEMANTICS
|
||||
|
||||
static bool _semanticsEnabled = false;
|
||||
static bool _debugDoingSemantics = false;
|
||||
static List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
|
||||
|
||||
/// Bootstrap the semantics reporting mechanism by marking this node
|
||||
/// as needing a semantics update.
|
||||
///
|
||||
|
@ -1491,32 +1519,15 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
void scheduleInitialSemantics() {
|
||||
assert(attached);
|
||||
assert(parent is! RenderObject);
|
||||
assert(!_debugDoingSemantics);
|
||||
assert(!owner._debugDoingSemantics);
|
||||
assert(_semantics == null);
|
||||
assert(_needsSemanticsUpdate);
|
||||
assert(_semanticsEnabled == false);
|
||||
_semanticsEnabled = true;
|
||||
_nodesNeedingSemantics.add(this);
|
||||
assert(owner._semanticsEnabled == false);
|
||||
owner._semanticsEnabled = true;
|
||||
owner._nodesNeedingSemantics.add(this);
|
||||
Scheduler.instance.ensureVisualUpdate();
|
||||
}
|
||||
|
||||
static void flushSemantics() {
|
||||
Timeline.startSync('Semantics');
|
||||
assert(_semanticsEnabled);
|
||||
assert(() { _debugDoingSemantics = true; return true; });
|
||||
try {
|
||||
_nodesNeedingSemantics.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||
for (RenderObject node in _nodesNeedingSemantics) {
|
||||
if (node._needsSemanticsUpdate)
|
||||
node._updateSemantics();
|
||||
}
|
||||
} finally {
|
||||
_nodesNeedingSemantics.clear();
|
||||
assert(() { _debugDoingSemantics = false; return true; });
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this RenderObject introduces a new box for accessibility purposes.
|
||||
bool get hasSemantics => false;
|
||||
|
||||
|
@ -1561,8 +1572,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
/// 'noGeometry: true' when the geometry did change, the semantic
|
||||
/// tree will be out of date.
|
||||
void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) {
|
||||
assert(!_debugDoingSemantics);
|
||||
if (!_semanticsEnabled || !attached || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
|
||||
assert(!attached || !owner._debugDoingSemantics);
|
||||
if (!attached || !owner._semanticsEnabled || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
|
||||
return;
|
||||
if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) {
|
||||
// Since the geometry might have changed, we need to make sure to reapply any clips.
|
||||
|
@ -1582,7 +1593,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
}
|
||||
if (!node._needsSemanticsUpdate) {
|
||||
node._needsSemanticsUpdate = true;
|
||||
_nodesNeedingSemantics.add(node);
|
||||
owner._nodesNeedingSemantics.add(node);
|
||||
}
|
||||
} else {
|
||||
// The shape of the semantics tree around us may have changed.
|
||||
|
@ -1601,7 +1612,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||
node._semantics?.reset();
|
||||
if (!node._needsSemanticsUpdate) {
|
||||
node._needsSemanticsUpdate = true;
|
||||
_nodesNeedingSemantics.add(node);
|
||||
owner._nodesNeedingSemantics.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1814,10 +1825,10 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
if (_child != null)
|
||||
_child.attach();
|
||||
_child.attach(owner);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2034,11 +2045,11 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.attach();
|
||||
child.attach(owner);
|
||||
final ParentDataType childParentData = child.parentData;
|
||||
child = childParentData.nextSibling;
|
||||
}
|
||||
|
|
|
@ -1035,8 +1035,8 @@ class RenderDecoratedBox extends RenderProxyBox {
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
_addListenerIfNeeded();
|
||||
}
|
||||
|
||||
|
|
|
@ -58,10 +58,11 @@ class SemanticsNode extends AbstractNode {
|
|||
_actionHandler = handler;
|
||||
|
||||
SemanticsNode.root({
|
||||
SemanticActionHandler handler
|
||||
SemanticActionHandler handler,
|
||||
Object owner
|
||||
}) : _id = 0,
|
||||
_actionHandler = handler {
|
||||
attach();
|
||||
attach(owner);
|
||||
}
|
||||
|
||||
static int _lastIdentifier = 0;
|
||||
|
@ -265,8 +266,8 @@ class SemanticsNode extends AbstractNode {
|
|||
static Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(Object owner) {
|
||||
super.attach(owner);
|
||||
assert(!_nodes.containsKey(_id));
|
||||
_nodes[_id] = this;
|
||||
_detachedNodes.remove(this);
|
||||
|
@ -274,7 +275,7 @@ class SemanticsNode extends AbstractNode {
|
|||
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
child.attach();
|
||||
child.attach(owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,7 +405,7 @@ class SemanticsNode extends AbstractNode {
|
|||
child._inheritedMergeAllDescendantsIntoThisNode = false; // this can add the node to the dirty list
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(_dirtyNodes[index] == node); // make sure nothing went in front of us in the list
|
||||
}
|
||||
_dirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
|
||||
|
|
|
@ -33,7 +33,7 @@ class ViewConfiguration {
|
|||
/// The root of the render tree.
|
||||
///
|
||||
/// The view represents the total output surface of the render tree and handles
|
||||
/// bootstraping the rendering pipeline. The view has a unique child
|
||||
/// bootstrapping the rendering pipeline. The view has a unique child
|
||||
/// [RenderBox], which is required to fill the entire output surface.
|
||||
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
|
||||
RenderView({
|
||||
|
|
|
@ -168,8 +168,8 @@ class RenderViewportBase extends RenderBox implements HasMainAxis {
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
_overlayPainter?.attach(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -335,7 +335,11 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
|
|||
/// the gesture detector should be enabled.
|
||||
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
|
||||
assert(() {
|
||||
if (!RenderObject.debugDoingLayout) {
|
||||
// TODO kgiesing This assert will trigger if the owner of the current
|
||||
// tree is different from the owner assigned to the renderer instance.
|
||||
// Once elements have a notion of owners this assertion can be written
|
||||
// more clearly.
|
||||
if (!Renderer.instance.pipelineOwner.debugDoingLayout) {
|
||||
throw new FlutterError(
|
||||
'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
|
||||
'The replaceGestureRecognizers() method can only be called during the layout phase. '
|
||||
|
|
84
packages/flutter/test/rendering/independent_layout_test.dart
Normal file
84
packages/flutter/test/rendering/independent_layout_test.dart
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'rendering_tester.dart';
|
||||
|
||||
class TestLayout {
|
||||
TestLayout() {
|
||||
// viewport incoming constraints are tight 800x600
|
||||
// viewport is vertical by default
|
||||
root = new RenderViewport(
|
||||
child: new RenderCustomPaint(
|
||||
painter: new TestCallbackPainter(
|
||||
onPaint: () { painted = true; }
|
||||
),
|
||||
child: child = new RenderConstrainedBox(
|
||||
additionalConstraints: new BoxConstraints.tightFor(height: 10.0, width: 10.0)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
RenderBox root;
|
||||
RenderBox child;
|
||||
bool painted = false;
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('onscreen layout does not affect offscreen', () {
|
||||
TestLayout onscreen = new TestLayout();
|
||||
TestLayout offscreen = new TestLayout();
|
||||
expect(onscreen.child.hasSize, isFalse);
|
||||
expect(onscreen.painted, isFalse);
|
||||
expect(offscreen.child.hasSize, isFalse);
|
||||
expect(offscreen.painted, isFalse);
|
||||
// Attach the offscreen to a custom render view and owner
|
||||
RenderView renderView = new TestRenderView();
|
||||
PipelineOwner pipelineOwner = new PipelineOwner();
|
||||
renderView.attach(pipelineOwner);
|
||||
renderView.child = offscreen.root;
|
||||
renderView.scheduleInitialFrame();
|
||||
// Lay out the onscreen in the default binding
|
||||
layout(onscreen.root, phase: EnginePhase.layout);
|
||||
expect(onscreen.child.hasSize, isTrue);
|
||||
expect(onscreen.painted, isFalse);
|
||||
expect(onscreen.child.size, equals(const Size(800.0, 10.0)));
|
||||
// Make sure the offscreen didn't get laid out
|
||||
expect(offscreen.child.hasSize, isFalse);
|
||||
expect(offscreen.painted, isFalse);
|
||||
// Now lay out the offscreen
|
||||
pipelineOwner.flushLayout();
|
||||
expect(offscreen.child.hasSize, isTrue);
|
||||
expect(offscreen.painted, isFalse);
|
||||
});
|
||||
test('offscreen layout does not affect onscreen', () {
|
||||
TestLayout onscreen = new TestLayout();
|
||||
TestLayout offscreen = new TestLayout();
|
||||
expect(onscreen.child.hasSize, isFalse);
|
||||
expect(onscreen.painted, isFalse);
|
||||
expect(offscreen.child.hasSize, isFalse);
|
||||
expect(offscreen.painted, isFalse);
|
||||
// Attach the offscreen to a custom render view and owner
|
||||
RenderView renderView = new TestRenderView();
|
||||
PipelineOwner pipelineOwner = new PipelineOwner();
|
||||
renderView.attach(pipelineOwner);
|
||||
renderView.child = offscreen.root;
|
||||
renderView.scheduleInitialFrame();
|
||||
// Lay out the offscreen
|
||||
pipelineOwner.flushLayout();
|
||||
expect(offscreen.child.hasSize, isTrue);
|
||||
expect(offscreen.painted, isFalse);
|
||||
// Make sure the onscreen didn't get laid out
|
||||
expect(onscreen.child.hasSize, isFalse);
|
||||
expect(onscreen.painted, isFalse);
|
||||
// Now lay out the onscreen in the default binding
|
||||
layout(onscreen.root, phase: EnginePhase.layout);
|
||||
expect(onscreen.child.hasSize, isTrue);
|
||||
expect(onscreen.painted, isFalse);
|
||||
expect(onscreen.child.size, equals(const Size(800.0, 10.0)));
|
||||
});
|
||||
}
|
|
@ -41,16 +41,16 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services,
|
|||
|
||||
@override
|
||||
void beginFrame() {
|
||||
RenderObject.flushLayout();
|
||||
pipelineOwner.flushLayout();
|
||||
if (phase == EnginePhase.layout)
|
||||
return;
|
||||
RenderObject.flushCompositingBits();
|
||||
pipelineOwner.flushCompositingBits();
|
||||
if (phase == EnginePhase.compositingBits)
|
||||
return;
|
||||
RenderObject.flushPaint();
|
||||
pipelineOwner.flushPaint();
|
||||
if (phase == EnginePhase.paint)
|
||||
return;
|
||||
renderer.renderView.compositeFrame();
|
||||
renderView.compositeFrame();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,8 +58,8 @@ class SpriteBox extends RenderBox {
|
|||
}
|
||||
|
||||
@override
|
||||
void attach() {
|
||||
super.attach();
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
_scheduleTick();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,20 +46,20 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
|
|||
// Cloned from Renderer.beginFrame() but with early-exit semantics.
|
||||
void _beginFrame() {
|
||||
assert(renderView != null);
|
||||
RenderObject.flushLayout();
|
||||
pipelineOwner.flushLayout();
|
||||
if (phase == EnginePhase.layout)
|
||||
return;
|
||||
RenderObject.flushCompositingBits();
|
||||
pipelineOwner.flushCompositingBits();
|
||||
if (phase == EnginePhase.compositingBits)
|
||||
return;
|
||||
RenderObject.flushPaint();
|
||||
pipelineOwner.flushPaint();
|
||||
if (phase == EnginePhase.paint)
|
||||
return;
|
||||
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||
if (phase == EnginePhase.composite)
|
||||
return;
|
||||
if (SemanticsNode.hasListeners) {
|
||||
RenderObject.flushSemantics();
|
||||
pipelineOwner.flushSemantics();
|
||||
if (phase == EnginePhase.flushSemantics)
|
||||
return;
|
||||
SemanticsNode.sendSemanticsTree();
|
||||
|
|
|
@ -347,6 +347,7 @@ class AnalyzeCommand extends FlutterCommand {
|
|||
new RegExp('^\\[(hint|error)\\] Unused import \\(${mainFile.path},'),
|
||||
new RegExp(r'^\[.+\] .+ \(.+/\.pub-cache/.+'),
|
||||
new RegExp('\\[warning\\] Missing concrete implementation of \'RenderObject\\.applyPaintTransform\''), // https://github.com/dart-lang/sdk/issues/25232
|
||||
new RegExp('\\[warning\\] Missing concrete implementation of \'AbstractNode\\.attach\''), // https://github.com/dart-lang/sdk/issues/25232
|
||||
new RegExp(r'[0-9]+ (error|warning|hint|lint).+found\.'),
|
||||
new RegExp(r'^$'),
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue