| 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 scheduler.ensureVisualUpdate(); | |
| 102 } | |
| 103 } | |
| 104 static void flushLayout() { | |
| 105 _debugDoingLayout = true; | |
| 106 List<RenderNode> dirtyNodes = _nodesNeedingLayout; | |
| 107 _nodesNeedingLayout = new List<RenderNode>(); | |
| 108 dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) { | |
| 109 if (node._needsLayout && node.attached) | |
| 110 node._doLayout(); | |
| 111 }); | |
| 112 _debugDoingLayout = false; | |
| 113 } | |
| 114 void _doLayout() { | |
| 115 try { | |
| 116 assert(_relayoutSubtreeRoot == this); | |
| 117 performLayout(); | |
| 118 } catch (e, stack) { | |
| 119 print('Exception raised during layout of ${this}: ${e}'); | |
| 120 print(stack); | |
| 121 return; | |
| 122 } | |
| 123 _needsLayout = false; | |
| 124 } | |
| 125 void layout(dynamic constraints, { bool parentUsesSize: false }) { | |
| 126 RenderNode relayoutSubtreeRoot; | |
| 127 if (!parentUsesSize || sizedByParent || parent is! RenderNode) | |
| 128 relayoutSubtreeRoot = this; | |
| 129 else | |
| 130 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; | |
| 131 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) | |
| 132 return; | |
| 133 _constraints = constraints; | |
| 134 _relayoutSubtreeRoot = relayoutSubtreeRoot; | |
| 135 if (sizedByParent) | |
| 136 performResize(); | |
| 137 performLayout(); | |
| 138 _needsLayout = false; | |
| 139 markNeedsPaint(); | |
| 140 } | |
| 141 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) | |
| 142 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) | |
| 143 void performLayout(); | |
| 144 // Override this to perform relayout without your parent's | |
| 145 // involvement. | |
| 146 // | |
| 147 // This is called during layout. If sizedByParent is true, then | |
| 148 // performLayout() should not change your dimensions, only do that | |
| 149 // in performResize(). If sizedByParent is false, then set both | |
| 150 // your dimensions and do your children's layout here. | |
| 151 // | |
| 152 // When calling layout() on your children, pass in | |
| 153 // "parentUsesSize: true" if your size or layout is dependent on | |
| 154 // your child's size. | |
| 155 | |
| 156 // when the parent has rotated (e.g. when the screen has been turned | |
| 157 // 90 degrees), immediately prior to layout() being called for the | |
| 158 // new dimensions, rotate() is called with the old and new angles. | |
| 159 // The next time paint() is called, the coordinate space will have | |
| 160 // been rotated N quarter-turns clockwise, where: | |
| 161 // N = newAngle-oldAngle | |
| 162 // ...but the rendering is expected to remain the same, pixel for | |
| 163 // pixel, on the output device. Then, the layout() method or | |
| 164 // equivalent will be invoked. | |
| 165 | |
| 166 void rotate({ | |
| 167 int oldAngle, // 0..3 | |
| 168 int newAngle, // 0..3 | |
| 169 Duration time | |
| 170 }) { } | |
| 171 | |
| 172 | |
| 173 // PAINTING | |
| 174 | |
| 175 static bool debugDoingPaint = false; | |
| 176 void markNeedsPaint() { | |
| 177 assert(!debugDoingPaint); | |
| 178 scheduler.ensureVisualUpdate(); | |
| 179 } | |
| 180 void paint(RenderNodeDisplayList canvas) { } | |
| 181 | |
| 182 | |
| 183 // EVENTS | |
| 184 | |
| 185 void handleEvent(sky.Event event) { | |
| 186 // override this if you have a client, to hand it to the client | |
| 187 // override this if you want to do anything with the event | |
| 188 } | |
| 189 | |
| 190 | |
| 191 // HIT TESTING | |
| 192 | |
| 193 // RenderNode subclasses are expected to have a method like the | |
| 194 // following (with the signature being whatever passes for coordinates | |
| 195 // for this particular class): | |
| 196 // bool hitTest(HitTestResult result, { sky.Point position }) { | |
| 197 // // If (x,y) is not inside this node, then return false. (You | |
| 198 // // can assume that the given coordinate is inside your | |
| 199 // // dimensions. You only need to check this if you're an | |
| 200 // // irregular shape, e.g. if you have a hole.) | |
| 201 // // Otherwise: | |
| 202 // // For each child that intersects x,y, in z-order starting from the top, | |
| 203 // // call hitTest() for that child, passing it /result/, and the coordinate
s | |
| 204 // // converted to the child's coordinate origin, and stop at the first chil
d | |
| 205 // // that returns true. | |
| 206 // // Then, add yourself to /result/, and return true. | |
| 207 // } | |
| 208 // You must not add yourself to /result/ if you return false. | |
| 209 | |
| 210 } | |
| 211 | |
| 212 class HitTestResult { | |
| 213 final List<RenderNode> path = new List<RenderNode>(); | |
| 214 | |
| 215 RenderNode get result => path.first; | |
| 216 | |
| 217 void add(RenderNode node) { | |
| 218 path.add(node); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 | |
| 223 // GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD | |
| 224 | |
| 225 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { | |
| 226 ChildType _child; | |
| 227 ChildType get child => _child; | |
| 228 void set child (ChildType value) { | |
| 229 if (_child != null) | |
| 230 dropChild(_child); | |
| 231 _child = value; | |
| 232 if (_child != null) | |
| 233 adoptChild(_child); | |
| 234 markNeedsLayout(); | |
| 235 } | |
| 236 void attachChildren() { | |
| 237 if (_child != null) | |
| 238 _child.attach(); | |
| 239 } | |
| 240 void detachChildren() { | |
| 241 if (_child != null) | |
| 242 _child.detach(); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 | |
| 247 // GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN | |
| 248 | |
| 249 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
| 250 ChildType previousSibling; | |
| 251 ChildType nextSibling; | |
| 252 void detachSiblings() { | |
| 253 if (previousSibling != null) { | |
| 254 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 255 assert(previousSibling != this); | |
| 256 assert(previousSibling.parentData.nextSibling == this); | |
| 257 previousSibling.parentData.nextSibling = nextSibling; | |
| 258 } | |
| 259 if (nextSibling != null) { | |
| 260 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 261 assert(nextSibling != this); | |
| 262 assert(nextSibling.parentData.previousSibling == this); | |
| 263 nextSibling.parentData.previousSibling = previousSibling; | |
| 264 } | |
| 265 previousSibling = null; | |
| 266 nextSibling = null; | |
| 267 } | |
| 268 } | |
| 269 | |
| 270 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
| 271 // abstract class that has only InlineNode children | |
| 272 | |
| 273 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
| 274 assert(child.parentData is ParentDataType); | |
| 275 while (child.parentData.previousSibling != null) { | |
| 276 assert(child.parentData.previousSibling != child); | |
| 277 child = child.parentData.previousSibling; | |
| 278 assert(child.parentData is ParentDataType); | |
| 279 } | |
| 280 return child == equals; | |
| 281 } | |
| 282 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
| 283 assert(child.parentData is ParentDataType); | |
| 284 while (child.parentData.nextSibling != null) { | |
| 285 assert(child.parentData.nextSibling != child); | |
| 286 child = child.parentData.nextSibling; | |
| 287 assert(child.parentData is ParentDataType); | |
| 288 } | |
| 289 return child == equals; | |
| 290 } | |
| 291 | |
| 292 ChildType _firstChild; | |
| 293 ChildType _lastChild; | |
| 294 void add(ChildType child, { ChildType before }) { | |
| 295 assert(child != this); | |
| 296 assert(before != this); | |
| 297 assert(child != before); | |
| 298 assert(child != _firstChild); | |
| 299 assert(child != _lastChild); | |
| 300 adoptChild(child); | |
| 301 assert(child.parentData is ParentDataType); | |
| 302 assert(child.parentData.nextSibling == null); | |
| 303 assert(child.parentData.previousSibling == null); | |
| 304 if (before == null) { | |
| 305 // append at the end (_lastChild) | |
| 306 child.parentData.previousSibling = _lastChild; | |
| 307 if (_lastChild != null) { | |
| 308 assert(_lastChild.parentData is ParentDataType); | |
| 309 _lastChild.parentData.nextSibling = child; | |
| 310 } | |
| 311 _lastChild = child; | |
| 312 if (_firstChild == null) | |
| 313 _firstChild = child; | |
| 314 } else { | |
| 315 assert(_firstChild != null); | |
| 316 assert(_lastChild != null); | |
| 317 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
| 318 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
| 319 assert(before.parentData is ParentDataType); | |
| 320 if (before.parentData.previousSibling == null) { | |
| 321 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
| 322 assert(before == _firstChild); | |
| 323 child.parentData.nextSibling = before; | |
| 324 before.parentData.previousSibling = child; | |
| 325 _firstChild = child; | |
| 326 } else { | |
| 327 // insert in the middle; we'll end up with three or more children | |
| 328 // set up links from child to siblings | |
| 329 child.parentData.previousSibling = before.parentData.previousSibling; | |
| 330 child.parentData.nextSibling = before; | |
| 331 // set up links from siblings to child | |
| 332 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 333 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 334 child.parentData.previousSibling.parentData.nextSibling = child; | |
| 335 child.parentData.nextSibling.parentData.previousSibling = child; | |
| 336 assert(before.parentData.previousSibling == child); | |
| 337 } | |
| 338 } | |
| 339 markNeedsLayout(); | |
| 340 } | |
| 341 void remove(ChildType child) { | |
| 342 assert(child.parentData is ParentDataType); | |
| 343 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
| 344 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
| 345 if (child.parentData.previousSibling == null) { | |
| 346 assert(_firstChild == child); | |
| 347 _firstChild = child.parentData.nextSibling; | |
| 348 } else { | |
| 349 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 350 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
| 351 } | |
| 352 if (child.parentData.nextSibling == null) { | |
| 353 assert(_lastChild == child); | |
| 354 _lastChild = child.parentData.previousSibling; | |
| 355 } else { | |
| 356 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 357 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
| 358 } | |
| 359 child.parentData.previousSibling = null; | |
| 360 child.parentData.nextSibling = null; | |
| 361 dropChild(child); | |
| 362 markNeedsLayout(); | |
| 363 } | |
| 364 void redepthChildren() { | |
| 365 ChildType child = _firstChild; | |
| 366 while (child != null) { | |
| 367 redepthChild(child); | |
| 368 assert(child.parentData is ParentDataType); | |
| 369 child = child.parentData.nextSibling; | |
| 370 } | |
| 371 } | |
| 372 void attachChildren() { | |
| 373 ChildType child = _firstChild; | |
| 374 while (child != null) { | |
| 375 child.attach(); | |
| 376 assert(child.parentData is ParentDataType); | |
| 377 child = child.parentData.nextSibling; | |
| 378 } | |
| 379 } | |
| 380 void detachChildren() { | |
| 381 ChildType child = _firstChild; | |
| 382 while (child != null) { | |
| 383 child.detach(); | |
| 384 assert(child.parentData is ParentDataType); | |
| 385 child = child.parentData.nextSibling; | |
| 386 } | |
| 387 } | |
| 388 | |
| 389 ChildType get firstChild => _firstChild; | |
| 390 ChildType get lastChild => _lastChild; | |
| 391 ChildType childAfter(ChildType child) { | |
| 392 assert(child.parentData is ParentDataType); | |
| 393 return child.parentData.nextSibling; | |
| 394 } | |
| 395 } | |
| OLD | NEW |