| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 import '../node.dart'; | |
| 6 import '../scheduler.dart' as scheduler; | |
| 7 import 'dart:math' as math; | |
| 8 import 'dart:sky' as sky; | |
| 9 | |
| 10 class ParentData { | |
| 11 void detach() { | |
| 12 detachSiblings(); | |
| 13 } | |
| 14 void detachSiblings() { } // workaround for lack of inter-class mixins in Dart | |
| 15 void merge(ParentData other) { | |
| 16 // override this in subclasses to merge in data from other into this | |
| 17 assert(other.runtimeType == this.runtimeType); | |
| 18 } | |
| 19 } | |
| 20 | |
| 21 const kLayoutDirections = 4; | |
| 22 | |
| 23 double clamp({double min: 0.0, double value: 0.0, double max: double.INFINITY})
{ | |
| 24 assert(min != null); | |
| 25 assert(value != null); | |
| 26 assert(max != null); | |
| 27 return math.max(min, math.min(max, value)); | |
| 28 } | |
| 29 | |
| 30 class RenderNodeDisplayList extends sky.PictureRecorder { | |
| 31 RenderNodeDisplayList(double width, double height) : super(width, height); | |
| 32 void paintChild(RenderNode child, sky.Point position) { | |
| 33 translate(position.x, position.y); | |
| 34 child.paint(this); | |
| 35 translate(-position.x, -position.y); | |
| 36 } | |
| 37 } | |
| 38 | |
| 39 abstract class RenderNode extends AbstractNode { | |
| 40 | |
| 41 // LAYOUT | |
| 42 | |
| 43 // parentData is only for use by the RenderNode that actually lays this | |
| 44 // node out, and any other nodes who happen to know exactly what | |
| 45 // kind of node that is. | |
| 46 ParentData parentData; | |
| 47 void setParentData(RenderNode child) { | |
| 48 // override this to setup .parentData correctly for your class | |
| 49 if (child.parentData is! ParentData) | |
| 50 child.parentData = new ParentData(); | |
| 51 } | |
| 52 | |
| 53 void adoptChild(RenderNode child) { // only for use by subclasses | |
| 54 // call this whenever you decide a node is a child | |
| 55 assert(child != null); | |
| 56 setParentData(child); | |
| 57 super.adoptChild(child); | |
| 58 } | |
| 59 void dropChild(RenderNode child) { // only for use by subclasses | |
| 60 assert(child != null); | |
| 61 assert(child.parentData != null); | |
| 62 child.parentData.detach(); | |
| 63 super.dropChild(child); | |
| 64 } | |
| 65 | |
| 66 static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>(); | |
| 67 static bool _debugDoingLayout = false; | |
| 68 bool _needsLayout = true; | |
| 69 bool get needsLayout => _needsLayout; | |
| 70 RenderNode _relayoutSubtreeRoot; | |
| 71 dynamic _constraints; | |
| 72 dynamic get constraints => _constraints; | |
| 73 bool debugAncestorsAlreadyMarkedNeedsLayout() { | |
| 74 if (_relayoutSubtreeRoot == null) | |
| 75 return true; // we haven't yet done layout even once, so there's nothing f
or us to do | |
| 76 RenderNode node = this; | |
| 77 while (node != _relayoutSubtreeRoot) { | |
| 78 assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot); | |
| 79 assert(node.parent != null); | |
| 80 node = node.parent as RenderNode; | |
| 81 if (!node._needsLayout) | |
| 82 return false; | |
| 83 } | |
| 84 assert(node._relayoutSubtreeRoot == node); | |
| 85 return true; | |
| 86 } | |
| 87 void markNeedsLayout() { | |
| 88 assert(!_debugDoingLayout); | |
| 89 assert(!debugDoingPaint); | |
| 90 if (_needsLayout) { | |
| 91 assert(debugAncestorsAlreadyMarkedNeedsLayout()); | |
| 92 return; | |
| 93 } | |
| 94 _needsLayout = true; | |
| 95 assert(_relayoutSubtreeRoot != null); | |
| 96 if (_relayoutSubtreeRoot != this) { | |
| 97 assert(parent is RenderNode); | |
| 98 parent.markNeedsLayout(); | |
| 99 } else { | |
| 100 _nodesNeedingLayout.add(this); | |
| 101 } | |
| 102 } | |
| 103 static void flushLayout() { | |
| 104 _debugDoingLayout = true; | |
| 105 List<RenderNode> dirtyNodes = _nodesNeedingLayout; | |
| 106 _nodesNeedingLayout = new List<RenderNode>(); | |
| 107 dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) { | |
| 108 if (node._needsLayout && node.attached) | |
| 109 node._doLayout(); | |
| 110 }); | |
| 111 _debugDoingLayout = false; | |
| 112 } | |
| 113 void _doLayout() { | |
| 114 try { | |
| 115 assert(_relayoutSubtreeRoot == this); | |
| 116 performLayout(); | |
| 117 } catch (e, stack) { | |
| 118 print('Exception raised during layout of ${this}: ${e}'); | |
| 119 print(stack); | |
| 120 return; | |
| 121 } | |
| 122 _needsLayout = false; | |
| 123 } | |
| 124 void layout(dynamic constraints, { bool parentUsesSize: false }) { | |
| 125 RenderNode relayoutSubtreeRoot; | |
| 126 if (!parentUsesSize || sizedByParent || parent is! RenderNode) | |
| 127 relayoutSubtreeRoot = this; | |
| 128 else | |
| 129 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; | |
| 130 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) | |
| 131 return; | |
| 132 _constraints = constraints; | |
| 133 _relayoutSubtreeRoot = relayoutSubtreeRoot; | |
| 134 if (sizedByParent) | |
| 135 performResize(); | |
| 136 performLayout(); | |
| 137 _needsLayout = false; | |
| 138 markNeedsPaint(); | |
| 139 } | |
| 140 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) | |
| 141 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) | |
| 142 void performLayout(); | |
| 143 // Override this to perform relayout without your parent's | |
| 144 // involvement. | |
| 145 // | |
| 146 // This is called during layout. If sizedByParent is true, then | |
| 147 // performLayout() should not change your dimensions, only do that | |
| 148 // in performResize(). If sizedByParent is false, then set both | |
| 149 // your dimensions and do your children's layout here. | |
| 150 // | |
| 151 // When calling layout() on your children, pass in | |
| 152 // "parentUsesSize: true" if your size or layout is dependent on | |
| 153 // your child's size. | |
| 154 | |
| 155 // when the parent has rotated (e.g. when the screen has been turned | |
| 156 // 90 degrees), immediately prior to layout() being called for the | |
| 157 // new dimensions, rotate() is called with the old and new angles. | |
| 158 // The next time paint() is called, the coordinate space will have | |
| 159 // been rotated N quarter-turns clockwise, where: | |
| 160 // N = newAngle-oldAngle | |
| 161 // ...but the rendering is expected to remain the same, pixel for | |
| 162 // pixel, on the output device. Then, the layout() method or | |
| 163 // equivalent will be invoked. | |
| 164 | |
| 165 void rotate({ | |
| 166 int oldAngle, // 0..3 | |
| 167 int newAngle, // 0..3 | |
| 168 Duration time | |
| 169 }) { } | |
| 170 | |
| 171 | |
| 172 // PAINTING | |
| 173 | |
| 174 static bool debugDoingPaint = false; | |
| 175 void markNeedsPaint() { | |
| 176 assert(!debugDoingPaint); | |
| 177 scheduler.ensureVisualUpdate(); | |
| 178 } | |
| 179 void paint(RenderNodeDisplayList canvas) { } | |
| 180 | |
| 181 | |
| 182 // HIT TESTING | |
| 183 | |
| 184 void handlePointer(sky.PointerEvent event) { | |
| 185 // override this if you have a client, to hand it to the client | |
| 186 // override this if you want to do anything with the pointer event | |
| 187 } | |
| 188 | |
| 189 // RenderNode subclasses are expected to have a method like the | |
| 190 // following (with the signature being whatever passes for coordinates | |
| 191 // for this particular class): | |
| 192 // bool hitTest(HitTestResult result, { sky.Point position }) { | |
| 193 // // If (x,y) is not inside this node, then return false. (You | |
| 194 // // can assume that the given coordinate is inside your | |
| 195 // // dimensions. You only need to check this if you're an | |
| 196 // // irregular shape, e.g. if you have a hole.) | |
| 197 // // Otherwise: | |
| 198 // // For each child that intersects x,y, in z-order starting from the top, | |
| 199 // // call hitTest() for that child, passing it /result/, and the coordinate
s | |
| 200 // // converted to the child's coordinate origin, and stop at the first chil
d | |
| 201 // // that returns true. | |
| 202 // // Then, add yourself to /result/, and return true. | |
| 203 // } | |
| 204 // You must not add yourself to /result/ if you return false. | |
| 205 | |
| 206 } | |
| 207 | |
| 208 class HitTestResult { | |
| 209 final List<RenderNode> path = new List<RenderNode>(); | |
| 210 | |
| 211 RenderNode get result => path.first; | |
| 212 | |
| 213 void add(RenderNode node) { | |
| 214 path.add(node); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 | |
| 219 // GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD | |
| 220 | |
| 221 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { | |
| 222 ChildType _child; | |
| 223 ChildType get child => _child; | |
| 224 void set child (ChildType value) { | |
| 225 if (_child != null) | |
| 226 dropChild(_child); | |
| 227 _child = value; | |
| 228 if (_child != null) | |
| 229 adoptChild(_child); | |
| 230 markNeedsLayout(); | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 | |
| 235 // GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN | |
| 236 | |
| 237 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
| 238 ChildType previousSibling; | |
| 239 ChildType nextSibling; | |
| 240 void detachSiblings() { | |
| 241 if (previousSibling != null) { | |
| 242 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 243 assert(previousSibling != this); | |
| 244 assert(previousSibling.parentData.nextSibling == this); | |
| 245 previousSibling.parentData.nextSibling = nextSibling; | |
| 246 } | |
| 247 if (nextSibling != null) { | |
| 248 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 249 assert(nextSibling != this); | |
| 250 assert(nextSibling.parentData.previousSibling == this); | |
| 251 nextSibling.parentData.previousSibling = previousSibling; | |
| 252 } | |
| 253 previousSibling = null; | |
| 254 nextSibling = null; | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
| 259 // abstract class that has only InlineNode children | |
| 260 | |
| 261 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
| 262 assert(child.parentData is ParentDataType); | |
| 263 while (child.parentData.previousSibling != null) { | |
| 264 assert(child.parentData.previousSibling != child); | |
| 265 child = child.parentData.previousSibling; | |
| 266 assert(child.parentData is ParentDataType); | |
| 267 } | |
| 268 return child == equals; | |
| 269 } | |
| 270 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
| 271 assert(child.parentData is ParentDataType); | |
| 272 while (child.parentData.nextSibling != null) { | |
| 273 assert(child.parentData.nextSibling != child); | |
| 274 child = child.parentData.nextSibling; | |
| 275 assert(child.parentData is ParentDataType); | |
| 276 } | |
| 277 return child == equals; | |
| 278 } | |
| 279 | |
| 280 ChildType _firstChild; | |
| 281 ChildType _lastChild; | |
| 282 void add(ChildType child, { ChildType before }) { | |
| 283 assert(child != this); | |
| 284 assert(before != this); | |
| 285 assert(child != before); | |
| 286 assert(child != _firstChild); | |
| 287 assert(child != _lastChild); | |
| 288 adoptChild(child); | |
| 289 assert(child.parentData is ParentDataType); | |
| 290 assert(child.parentData.nextSibling == null); | |
| 291 assert(child.parentData.previousSibling == null); | |
| 292 if (before == null) { | |
| 293 // append at the end (_lastChild) | |
| 294 child.parentData.previousSibling = _lastChild; | |
| 295 if (_lastChild != null) { | |
| 296 assert(_lastChild.parentData is ParentDataType); | |
| 297 _lastChild.parentData.nextSibling = child; | |
| 298 } | |
| 299 _lastChild = child; | |
| 300 if (_firstChild == null) | |
| 301 _firstChild = child; | |
| 302 } else { | |
| 303 assert(_firstChild != null); | |
| 304 assert(_lastChild != null); | |
| 305 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
| 306 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
| 307 assert(before.parentData is ParentDataType); | |
| 308 if (before.parentData.previousSibling == null) { | |
| 309 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
| 310 assert(before == _firstChild); | |
| 311 child.parentData.nextSibling = before; | |
| 312 before.parentData.previousSibling = child; | |
| 313 _firstChild = child; | |
| 314 } else { | |
| 315 // insert in the middle; we'll end up with three or more children | |
| 316 // set up links from child to siblings | |
| 317 child.parentData.previousSibling = before.parentData.previousSibling; | |
| 318 child.parentData.nextSibling = before; | |
| 319 // set up links from siblings to child | |
| 320 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 321 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 322 child.parentData.previousSibling.parentData.nextSibling = child; | |
| 323 child.parentData.nextSibling.parentData.previousSibling = child; | |
| 324 assert(before.parentData.previousSibling == child); | |
| 325 } | |
| 326 } | |
| 327 markNeedsLayout(); | |
| 328 } | |
| 329 void remove(ChildType child) { | |
| 330 assert(child.parentData is ParentDataType); | |
| 331 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
| 332 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
| 333 if (child.parentData.previousSibling == null) { | |
| 334 assert(_firstChild == child); | |
| 335 _firstChild = child.parentData.nextSibling; | |
| 336 } else { | |
| 337 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 338 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
| 339 } | |
| 340 if (child.parentData.nextSibling == null) { | |
| 341 assert(_lastChild == child); | |
| 342 _lastChild = child.parentData.previousSibling; | |
| 343 } else { | |
| 344 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 345 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
| 346 } | |
| 347 child.parentData.previousSibling = null; | |
| 348 child.parentData.nextSibling = null; | |
| 349 dropChild(child); | |
| 350 markNeedsLayout(); | |
| 351 } | |
| 352 void redepthChildren() { | |
| 353 ChildType child = _firstChild; | |
| 354 while (child != null) { | |
| 355 redepthChild(child); | |
| 356 assert(child.parentData is ParentDataType); | |
| 357 child = child.parentData.nextSibling; | |
| 358 } | |
| 359 } | |
| 360 void attachChildren() { | |
| 361 ChildType child = _firstChild; | |
| 362 while (child != null) { | |
| 363 child.attach(); | |
| 364 assert(child.parentData is ParentDataType); | |
| 365 child = child.parentData.nextSibling; | |
| 366 } | |
| 367 } | |
| 368 void detachChildren() { | |
| 369 ChildType child = _firstChild; | |
| 370 while (child != null) { | |
| 371 child.detach(); | |
| 372 assert(child.parentData is ParentDataType); | |
| 373 child = child.parentData.nextSibling; | |
| 374 } | |
| 375 } | |
| 376 | |
| 377 ChildType get firstChild => _firstChild; | |
| 378 ChildType get lastChild => _lastChild; | |
| 379 ChildType childAfter(ChildType child) { | |
| 380 assert(child.parentData is ParentDataType); | |
| 381 return child.parentData.nextSibling; | |
| 382 } | |
| 383 } | |
| OLD | NEW |