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