diff --git a/packages/flutter/benchmark/stocks/layout_bench.dart b/packages/flutter/benchmark/stocks/layout_bench.dart index 903a3209ad6..1ce9f4295ee 100644 --- a/packages/flutter/benchmark/stocks/layout_bench.dart +++ b/packages/flutter/benchmark/stocks/layout_bench.dart @@ -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(); diff --git a/packages/flutter/lib/src/rendering/README.md b/packages/flutter/lib/src/rendering/README.md index 0f85d8ca00f..3c78f9b82bc 100644 --- a/packages/flutter/lib/src/rendering/README.md +++ b/packages/flutter/lib/src/rendering/README.md @@ -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 diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 9b0f8ce8460..ba88d31a7a6 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -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(); } } diff --git a/packages/flutter/lib/src/rendering/block.dart b/packages/flutter/lib/src/rendering/block.dart index 834e8ff764b..f4d7f098a2a 100644 --- a/packages/flutter/lib/src/rendering/block.dart +++ b/packages/flutter/lib/src/rendering/block.dart @@ -337,8 +337,8 @@ class RenderBlockViewport extends RenderBlockBase { } @override - void attach() { - super.attach(); + void attach(PipelineOwner owner) { + super.attach(owner); _overlayPainter?.attach(this); } diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index dde63c8ad66..3ebd8f80101 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -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); diff --git a/packages/flutter/lib/src/rendering/child_view.dart b/packages/flutter/lib/src/rendering/child_view.dart index 500dd723dd9..a66da202188 100644 --- a/packages/flutter/lib/src/rendering/child_view.dart +++ b/packages/flutter/lib/src/rendering/child_view.dart @@ -171,8 +171,8 @@ class RenderChildView extends RenderBox { } @override - void attach() { - super.attach(); + void attach(PipelineOwner owner) { + super.attach(owner); _child?._attach(); } diff --git a/packages/flutter/lib/src/rendering/node.dart b/packages/flutter/lib/src/rendering/node.dart index 552fba1e584..d20a89e6c33 100644 --- a/packages/flutter/lib/src/rendering/node.dart +++ b/packages/flutter/lib/src/rendering/node.dart @@ -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); } diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 4bfd5ef53e0..87a43d7acbd 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -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 _nodesNeedingLayout = []; + 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 dirtyNodes = _nodesNeedingLayout; + _nodesNeedingLayout = []; + 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 _nodesNeedingCompositingBitsUpdate = []; + /// 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 _nodesNeedingPaint = []; + 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 dirtyNodes = _nodesNeedingPaint; + _nodesNeedingPaint = []; + // 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 _nodesNeedingSemantics = []; + + 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 _nodesNeedingLayout = []; + @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 dirtyNodes = _nodesNeedingLayout; - _nodesNeedingLayout = []; - 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 _nodesNeedingPaint = []; - static List _nodesNeedingCompositingBitsUpdate = []; - /// 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 dirtyNodes = _nodesNeedingPaint; - _nodesNeedingPaint = []; - // 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 _nodesNeedingSemantics = []; - /// 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 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 _detachedNodes = new Set(); @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); diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index df99ec1af55..ab4c1ad7716 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -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 { RenderView({ diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index d56b23071ee..fa39e3355f7 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -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); } diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index a5537c7aa7d..47e561c0b59 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -335,7 +335,11 @@ class RawGestureDetectorState extends State { /// the gesture detector should be enabled. void replaceGestureRecognizers(Map 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. ' diff --git a/packages/flutter/test/rendering/independent_layout_test.dart b/packages/flutter/test/rendering/independent_layout_test.dart new file mode 100644 index 00000000000..3b2e6c74444 --- /dev/null +++ b/packages/flutter/test/rendering/independent_layout_test.dart @@ -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))); + }); +} diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart index 7719a4738bd..8d1f2921f2c 100644 --- a/packages/flutter/test/rendering/rendering_tester.dart +++ b/packages/flutter/test/rendering/rendering_tester.dart @@ -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(); } } diff --git a/packages/flutter_sprites/lib/src/sprite_box.dart b/packages/flutter_sprites/lib/src/sprite_box.dart index 2c132bdfaec..a6d7be288bb 100644 --- a/packages/flutter_sprites/lib/src/sprite_box.dart +++ b/packages/flutter_sprites/lib/src/sprite_box.dart @@ -58,8 +58,8 @@ class SpriteBox extends RenderBox { } @override - void attach() { - super.attach(); + void attach(PipelineOwner owner) { + super.attach(owner); _scheduleTick(); } diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index a09562ab8e0..2848f9330d5 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -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(); diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart index 1a53b746d93..e44699b7ea0 100644 --- a/packages/flutter_tools/lib/src/commands/analyze.dart +++ b/packages/flutter_tools/lib/src/commands/analyze.dart @@ -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'^$'), ];