| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:math' as math; | 5 import 'dart:math' as math; |
| 6 import 'dart:sky' as sky; | 6 import 'dart:sky' as sky; |
| 7 import 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; | 7 import 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; |
| 8 | 8 |
| 9 import '../base/hit_test.dart'; | 9 import '../base/hit_test.dart'; |
| 10 import '../base/node.dart'; | 10 import '../base/node.dart'; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 39 child._paintOnCanvas(this, point.toOffset()); | 39 child._paintOnCanvas(this, point.toOffset()); |
| 40 } | 40 } |
| 41 } | 41 } |
| 42 } | 42 } |
| 43 | 43 |
| 44 abstract class Constraints { | 44 abstract class Constraints { |
| 45 const Constraints(); | 45 const Constraints(); |
| 46 bool get isTight; | 46 bool get isTight; |
| 47 } | 47 } |
| 48 | 48 |
| 49 typedef void LayoutCallback(Constraints constraints); |
| 50 |
| 49 abstract class RenderObject extends AbstractNode implements HitTestTarget { | 51 abstract class RenderObject extends AbstractNode implements HitTestTarget { |
| 50 | 52 |
| 51 // LAYOUT | 53 // LAYOUT |
| 52 | 54 |
| 53 // parentData is only for use by the RenderObject that actually lays this | 55 // parentData is only for use by the RenderObject that actually lays this |
| 54 // node out, and any other nodes who happen to know exactly what | 56 // node out, and any other nodes who happen to know exactly what |
| 55 // kind of node that is. | 57 // kind of node that is. |
| 56 dynamic parentData; // TODO(ianh): change the type of this back to ParentData
once the analyzer is cleverer | 58 dynamic parentData; // TODO(ianh): change the type of this back to ParentData
once the analyzer is cleverer |
| 57 void setupParentData(RenderObject child) { | 59 void setupParentData(RenderObject child) { |
| 58 // override this to setup .parentData correctly for your class | 60 // override this to setup .parentData correctly for your class |
| 59 assert(!debugDoingLayout); | 61 assert(debugCanPerformMutations); |
| 60 assert(!debugDoingPaint); | |
| 61 if (child.parentData is! ParentData) | 62 if (child.parentData is! ParentData) |
| 62 child.parentData = new ParentData(); | 63 child.parentData = new ParentData(); |
| 63 } | 64 } |
| 64 | 65 |
| 65 void adoptChild(RenderObject child) { // only for use by subclasses | 66 void adoptChild(RenderObject child) { // only for use by subclasses |
| 66 // call this whenever you decide a node is a child | 67 // call this whenever you decide a node is a child |
| 67 assert(!debugDoingLayout); | 68 assert(debugCanPerformMutations); |
| 68 assert(!debugDoingPaint); | |
| 69 assert(child != null); | 69 assert(child != null); |
| 70 setupParentData(child); | 70 setupParentData(child); |
| 71 super.adoptChild(child); | 71 super.adoptChild(child); |
| 72 markNeedsLayout(); | 72 markNeedsLayout(); |
| 73 } | 73 } |
| 74 void dropChild(RenderObject child) { // only for use by subclasses | 74 void dropChild(RenderObject child) { // only for use by subclasses |
| 75 assert(!debugDoingLayout); | 75 assert(debugCanPerformMutations); |
| 76 assert(!debugDoingPaint); | |
| 77 assert(child != null); | 76 assert(child != null); |
| 78 assert(child.parentData != null); | 77 assert(child.parentData != null); |
| 79 child._cleanRelayoutSubtreeRoot(); | 78 child._cleanRelayoutSubtreeRoot(); |
| 80 child.parentData.detach(); | 79 child.parentData.detach(); |
| 81 super.dropChild(child); | 80 super.dropChild(child); |
| 82 markNeedsLayout(); | 81 markNeedsLayout(); |
| 83 } | 82 } |
| 84 | 83 |
| 85 static List<RenderObject> _nodesNeedingLayout = new List<RenderObject>(); | |
| 86 static bool _debugDoingLayout = false; | 84 static bool _debugDoingLayout = false; |
| 87 static bool get debugDoingLayout => _debugDoingLayout; | 85 static bool get debugDoingLayout => _debugDoingLayout; |
| 88 bool _debugDoingThisResize = false; | 86 bool _debugDoingThisResize = false; |
| 89 bool get debugDoingThisResize => _debugDoingThisResize; | 87 bool get debugDoingThisResize => _debugDoingThisResize; |
| 90 bool _debugDoingThisLayout = false; | 88 bool _debugDoingThisLayout = false; |
| 91 bool get debugDoingThisLayout => _debugDoingThisLayout; | 89 bool get debugDoingThisLayout => _debugDoingThisLayout; |
| 92 static RenderObject _debugActiveLayout = null; | 90 static RenderObject _debugActiveLayout = null; |
| 93 static RenderObject get debugActiveLayout => _debugActiveLayout; | 91 static RenderObject get debugActiveLayout => _debugActiveLayout; |
| 92 bool _debugDoingThisLayoutWithCallback = false; |
| 93 bool _debugMutationsLocked = false; |
| 94 bool _debugCanParentUseSize; | 94 bool _debugCanParentUseSize; |
| 95 bool get debugCanParentUseSize => _debugCanParentUseSize; | 95 bool get debugCanParentUseSize => _debugCanParentUseSize; |
| 96 bool get debugCanPerformMutations { |
| 97 RenderObject node = this; |
| 98 while (true) { |
| 99 if (node._debugDoingThisLayoutWithCallback) |
| 100 return true; |
| 101 if (node._debugMutationsLocked) |
| 102 return false; |
| 103 if (node.parent is! RenderObject) |
| 104 return true; |
| 105 node = node.parent; |
| 106 } |
| 107 } |
| 108 |
| 109 static List<RenderObject> _nodesNeedingLayout = new List<RenderObject>(); |
| 96 bool _needsLayout = true; | 110 bool _needsLayout = true; |
| 97 bool get needsLayout => _needsLayout; | 111 bool get needsLayout => _needsLayout; |
| 98 RenderObject _relayoutSubtreeRoot; | 112 RenderObject _relayoutSubtreeRoot; |
| 99 Constraints _constraints; | 113 Constraints _constraints; |
| 100 Constraints get constraints => _constraints; | 114 Constraints get constraints => _constraints; |
| 101 bool debugDoesMeetConstraints(); // override this in a subclass to verify that
your state matches the constraints object | 115 bool debugDoesMeetConstraints(); // override this in a subclass to verify that
your state matches the constraints object |
| 102 bool debugAncestorsAlreadyMarkedNeedsLayout() { | 116 bool debugAncestorsAlreadyMarkedNeedsLayout() { |
| 103 if (_relayoutSubtreeRoot == null) | 117 if (_relayoutSubtreeRoot == null) |
| 104 return true; // we haven't yet done layout even once, so there's nothing f
or us to do | 118 return true; // we haven't yet done layout even once, so there's nothing f
or us to do |
| 105 RenderObject node = this; | 119 RenderObject node = this; |
| 106 while (node != _relayoutSubtreeRoot) { | 120 while (node != _relayoutSubtreeRoot) { |
| 107 assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot); | 121 assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot); |
| 108 assert(node.parent != null); | 122 assert(node.parent != null); |
| 109 node = node.parent as RenderObject; | 123 node = node.parent as RenderObject; |
| 110 if (!node._needsLayout) | 124 if (!node._needsLayout) |
| 111 return false; | 125 return false; |
| 112 } | 126 } |
| 113 assert(node._relayoutSubtreeRoot == node); | 127 assert(node._relayoutSubtreeRoot == node); |
| 114 return true; | 128 return true; |
| 115 } | 129 } |
| 116 void markNeedsLayout() { | 130 void markNeedsLayout() { |
| 117 assert(!debugDoingLayout); | 131 assert(debugCanPerformMutations); |
| 118 assert(!debugDoingPaint); | |
| 119 if (_needsLayout) { | 132 if (_needsLayout) { |
| 120 assert(debugAncestorsAlreadyMarkedNeedsLayout()); | 133 assert(debugAncestorsAlreadyMarkedNeedsLayout()); |
| 121 return; | 134 return; |
| 122 } | 135 } |
| 123 _needsLayout = true; | 136 _needsLayout = true; |
| 124 assert(_relayoutSubtreeRoot != null); | 137 assert(_relayoutSubtreeRoot != null); |
| 125 if (_relayoutSubtreeRoot != this) { | 138 if (_relayoutSubtreeRoot != this) { |
| 126 final parent = this.parent; // TODO(ianh): Remove this once the analyzer i
s cleverer | 139 final parent = this.parent; // TODO(ianh): Remove this once the analyzer i
s cleverer |
| 127 assert(parent is RenderObject); | 140 assert(parent is RenderObject); |
| 128 parent.markNeedsLayout(); | 141 parent.markNeedsLayout(); |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 160 node.layoutWithoutResize(); | 173 node.layoutWithoutResize(); |
| 161 }); | 174 }); |
| 162 } finally { | 175 } finally { |
| 163 _debugDoingLayout = false; | 176 _debugDoingLayout = false; |
| 164 sky.tracing.end('RenderObject.flushLayout'); | 177 sky.tracing.end('RenderObject.flushLayout'); |
| 165 } | 178 } |
| 166 } | 179 } |
| 167 void layoutWithoutResize() { | 180 void layoutWithoutResize() { |
| 168 try { | 181 try { |
| 169 assert(_relayoutSubtreeRoot == this); | 182 assert(_relayoutSubtreeRoot == this); |
| 170 _debugCanParentUseSize = false; | 183 RenderObject debugPreviousActiveLayout; |
| 171 _debugDoingThisLayout = true; | 184 assert(!_debugMutationsLocked); |
| 172 RenderObject debugPreviousActiveLayout = _debugActiveLayout; | 185 assert(!_debugDoingThisLayoutWithCallback); |
| 173 _debugActiveLayout = this; | 186 assert(() { |
| 187 _debugMutationsLocked = true; |
| 188 _debugCanParentUseSize = false; |
| 189 _debugDoingThisLayout = true; |
| 190 debugPreviousActiveLayout = _debugActiveLayout; |
| 191 _debugActiveLayout = this; |
| 192 return true; |
| 193 }); |
| 174 performLayout(); | 194 performLayout(); |
| 175 _debugActiveLayout = debugPreviousActiveLayout; | 195 assert(() { |
| 176 _debugDoingThisLayout = false; | 196 _debugActiveLayout = debugPreviousActiveLayout; |
| 177 _debugCanParentUseSize = null; | 197 _debugDoingThisLayout = false; |
| 198 _debugCanParentUseSize = null; |
| 199 _debugMutationsLocked = false; |
| 200 return true; |
| 201 }); |
| 178 } catch (e) { | 202 } catch (e) { |
| 179 print('Exception raised during layout:\n${e}\nContext:\n${this}'); | 203 print('Exception raised during layout:\n${e}\nContext:\n${this}'); |
| 180 return; | 204 return; |
| 181 } | 205 } |
| 182 _needsLayout = false; | 206 _needsLayout = false; |
| 183 markNeedsPaint(); | 207 markNeedsPaint(); |
| 184 } | 208 } |
| 185 void layout(Constraints constraints, { bool parentUsesSize: false }) { | 209 void layout(Constraints constraints, { bool parentUsesSize: false }) { |
| 186 final parent = this.parent; // TODO(ianh): Remove this once the analyzer is
cleverer | 210 final parent = this.parent; // TODO(ianh): Remove this once the analyzer is
cleverer |
| 187 RenderObject relayoutSubtreeRoot; | 211 RenderObject relayoutSubtreeRoot; |
| 188 if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! Re
nderObject) | 212 if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! Re
nderObject) |
| 189 relayoutSubtreeRoot = this; | 213 relayoutSubtreeRoot = this; |
| 190 else | 214 else |
| 191 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; | 215 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; |
| 192 assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer
is cleverer | 216 assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer
is cleverer |
| 193 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) | 217 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) |
| 194 return; | 218 return; |
| 195 _constraints = constraints; | 219 _constraints = constraints; |
| 196 _relayoutSubtreeRoot = relayoutSubtreeRoot; | 220 _relayoutSubtreeRoot = relayoutSubtreeRoot; |
| 197 _debugCanParentUseSize = parentUsesSize; | 221 assert(!_debugMutationsLocked); |
| 222 assert(!_debugDoingThisLayoutWithCallback); |
| 223 assert(() { |
| 224 _debugMutationsLocked = true; |
| 225 _debugCanParentUseSize = parentUsesSize; |
| 226 return true; |
| 227 }); |
| 198 if (sizedByParent) { | 228 if (sizedByParent) { |
| 199 _debugDoingThisResize = true; | 229 assert(() { _debugDoingThisResize = true; return true; }); |
| 200 performResize(); | 230 performResize(); |
| 201 _debugDoingThisResize = false; | 231 assert(() { _debugDoingThisResize = false; return true; }); |
| 202 } | 232 } |
| 203 _debugDoingThisLayout = true; | 233 RenderObject debugPreviousActiveLayout; |
| 204 RenderObject debugPreviousActiveLayout = _debugActiveLayout; | 234 assert(() { |
| 205 _debugActiveLayout = this; | 235 _debugDoingThisLayout = true; |
| 236 debugPreviousActiveLayout = _debugActiveLayout; |
| 237 _debugActiveLayout = this; |
| 238 return true; |
| 239 }); |
| 206 performLayout(); | 240 performLayout(); |
| 207 _debugActiveLayout = debugPreviousActiveLayout; | 241 assert(() { |
| 208 _debugDoingThisLayout = false; | 242 _debugActiveLayout = debugPreviousActiveLayout; |
| 209 _debugCanParentUseSize = null; | 243 _debugDoingThisLayout = false; |
| 244 _debugCanParentUseSize = null; |
| 245 _debugMutationsLocked = false; |
| 246 return true; |
| 247 }); |
| 210 assert(debugDoesMeetConstraints()); | 248 assert(debugDoesMeetConstraints()); |
| 211 _needsLayout = false; | 249 _needsLayout = false; |
| 212 markNeedsPaint(); | 250 markNeedsPaint(); |
| 213 assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer
is cleverer | 251 assert(parent == this.parent); // TODO(ianh): Remove this once the analyzer
is cleverer |
| 214 } | 252 } |
| 215 bool get sizedByParent => false; // return true if the constraints are the onl
y input to the sizing algorithm (in particular, child nodes have no impact) | 253 bool get sizedByParent => false; // return true if the constraints are the onl
y input to the sizing algorithm (in particular, child nodes have no impact) |
| 216 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) | 254 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) |
| 217 void performLayout(); | 255 void performLayout(); |
| 218 // Override this to perform relayout without your parent's | 256 // Override this to perform relayout without your parent's |
| 219 // involvement. | 257 // involvement. |
| 220 // | 258 // |
| 221 // This is called during layout. If sizedByParent is true, then | 259 // This is called during layout. If sizedByParent is true, then |
| 222 // performLayout() should not change your dimensions, only do that | 260 // performLayout() should not change your dimensions, only do that |
| 223 // in performResize(). If sizedByParent is false, then set both | 261 // in performResize(). If sizedByParent is false, then set both |
| 224 // your dimensions and do your children's layout here. | 262 // your dimensions and do your children's layout here. |
| 225 // | 263 // |
| 226 // When calling layout() on your children, pass in | 264 // When calling layout() on your children, pass in |
| 227 // "parentUsesSize: true" if your size or layout is dependent on | 265 // "parentUsesSize: true" if your size or layout is dependent on |
| 228 // your child's size or intrinsic dimensions. | 266 // your child's size or intrinsic dimensions. |
| 267 void invokeLayoutCallback(LayoutCallback callback) { |
| 268 assert(_debugMutationsLocked); |
| 269 assert(_debugDoingThisLayout); |
| 270 assert(!_debugDoingThisLayoutWithCallback); |
| 271 assert(() { |
| 272 _debugDoingThisLayoutWithCallback = true; |
| 273 return true; |
| 274 }); |
| 275 callback(constraints); |
| 276 assert(() { |
| 277 _debugDoingThisLayoutWithCallback = false; |
| 278 return true; |
| 279 }); |
| 280 } |
| 229 | 281 |
| 230 // when the parent has rotated (e.g. when the screen has been turned | 282 // when the parent has rotated (e.g. when the screen has been turned |
| 231 // 90 degrees), immediately prior to layout() being called for the | 283 // 90 degrees), immediately prior to layout() being called for the |
| 232 // new dimensions, rotate() is called with the old and new angles. | 284 // new dimensions, rotate() is called with the old and new angles. |
| 233 // The next time paint() is called, the coordinate space will have | 285 // The next time paint() is called, the coordinate space will have |
| 234 // been rotated N quarter-turns clockwise, where: | 286 // been rotated N quarter-turns clockwise, where: |
| 235 // N = newAngle-oldAngle | 287 // N = newAngle-oldAngle |
| 236 // ...but the rendering is expected to remain the same, pixel for | 288 // ...but the rendering is expected to remain the same, pixel for |
| 237 // pixel, on the output device. Then, the layout() method or | 289 // pixel, on the output device. Then, the layout() method or |
| 238 // equivalent will be invoked. | 290 // equivalent will be invoked. |
| (...skipping 289 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 528 } | 580 } |
| 529 void addAll(List<ChildType> children) { | 581 void addAll(List<ChildType> children) { |
| 530 if (children != null) | 582 if (children != null) |
| 531 for (ChildType child in children) | 583 for (ChildType child in children) |
| 532 add(child); | 584 add(child); |
| 533 } | 585 } |
| 534 void _removeFromChildList(ChildType child) { | 586 void _removeFromChildList(ChildType child) { |
| 535 assert(child.parentData is ParentDataType); | 587 assert(child.parentData is ParentDataType); |
| 536 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | 588 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); |
| 537 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | 589 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); |
| 538 _childCount -= 1; | |
| 539 assert(_childCount >= 0); | 590 assert(_childCount >= 0); |
| 540 if (child.parentData.previousSibling == null) { | 591 if (child.parentData.previousSibling == null) { |
| 541 assert(_firstChild == child); | 592 assert(_firstChild == child); |
| 542 _firstChild = child.parentData.nextSibling; | 593 _firstChild = child.parentData.nextSibling; |
| 543 } else { | 594 } else { |
| 544 assert(child.parentData.previousSibling.parentData is ParentDataType); | 595 assert(child.parentData.previousSibling.parentData is ParentDataType); |
| 545 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | 596 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; |
| 546 } | 597 } |
| 547 if (child.parentData.nextSibling == null) { | 598 if (child.parentData.nextSibling == null) { |
| 548 assert(_lastChild == child); | 599 assert(_lastChild == child); |
| 549 _lastChild = child.parentData.previousSibling; | 600 _lastChild = child.parentData.previousSibling; |
| 550 } else { | 601 } else { |
| 551 assert(child.parentData.nextSibling.parentData is ParentDataType); | 602 assert(child.parentData.nextSibling.parentData is ParentDataType); |
| 552 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | 603 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; |
| 553 } | 604 } |
| 554 child.parentData.previousSibling = null; | 605 child.parentData.previousSibling = null; |
| 555 child.parentData.nextSibling = null; | 606 child.parentData.nextSibling = null; |
| 607 _childCount -= 1; |
| 556 } | 608 } |
| 557 void remove(ChildType child) { | 609 void remove(ChildType child) { |
| 558 _removeFromChildList(child); | 610 _removeFromChildList(child); |
| 559 dropChild(child); | 611 dropChild(child); |
| 560 } | 612 } |
| 613 void removeAll() { |
| 614 ChildType child = _firstChild; |
| 615 while (child != null) { |
| 616 assert(child.parentData is ParentDataType); |
| 617 ChildType next = child.parentData.nextSibling; |
| 618 child.parentData.previousSibling = null; |
| 619 child.parentData.nextSibling = null; |
| 620 dropChild(child); |
| 621 child = next; |
| 622 } |
| 623 _firstChild = null; |
| 624 _lastChild = null; |
| 625 _childCount = 0; |
| 626 } |
| 561 void move(ChildType child, { ChildType before }) { | 627 void move(ChildType child, { ChildType before }) { |
| 562 assert(child != this); | 628 assert(child != this); |
| 563 assert(before != this); | 629 assert(before != this); |
| 564 assert(child != before); | 630 assert(child != before); |
| 565 assert(child.parent == this); | 631 assert(child.parent == this); |
| 566 assert(child.parentData is ParentDataType); | 632 assert(child.parentData is ParentDataType); |
| 567 if (child.parentData.nextSibling == before) | 633 if (child.parentData.nextSibling == before) |
| 568 return; | 634 return; |
| 569 _removeFromChildList(child); | 635 _removeFromChildList(child); |
| 570 _addToChildList(child, before: before); | 636 _addToChildList(child, before: before); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 614 int count = 1; | 680 int count = 1; |
| 615 ChildType child = _firstChild; | 681 ChildType child = _firstChild; |
| 616 while (child != null) { | 682 while (child != null) { |
| 617 result += '${prefix}child ${count}: ${child.toString(prefix)}'; | 683 result += '${prefix}child ${count}: ${child.toString(prefix)}'; |
| 618 count += 1; | 684 count += 1; |
| 619 child = child.parentData.nextSibling; | 685 child = child.parentData.nextSibling; |
| 620 } | 686 } |
| 621 return result; | 687 return result; |
| 622 } | 688 } |
| 623 } | 689 } |
| OLD | NEW |