| 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 // ABSTRACT LAYOUT | |
| 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 | |
| 28 if (value > max) | |
| 29 value = max; | |
| 30 if (value < min) | |
| 31 value = min; | |
| 32 return value; | |
| 33 } | |
| 34 | |
| 35 class RenderNodeDisplayList extends sky.PictureRecorder { | |
| 36 RenderNodeDisplayList(double width, double height) : super(width, height); | |
| 37 void paintChild(RenderNode child, sky.Point position) { | |
| 38 save(); | |
| 39 translate(position.x, position.y); | |
| 40 child.paint(this); | |
| 41 restore(); | |
| 42 } | |
| 43 } | |
| 44 | |
| 45 abstract class RenderNode extends AbstractNode { | |
| 46 | |
| 47 // LAYOUT | |
| 48 | |
| 49 // parentData is only for use by the RenderNode that actually lays this | |
| 50 // node out, and any other nodes who happen to know exactly what | |
| 51 // kind of node that is. | |
| 52 ParentData parentData; | |
| 53 void setParentData(RenderNode child) { | |
| 54 // override this to setup .parentData correctly for your class | |
| 55 if (child.parentData is! ParentData) | |
| 56 child.parentData = new ParentData(); | |
| 57 } | |
| 58 | |
| 59 void adoptChild(RenderNode child) { // only for use by subclasses | |
| 60 // call this whenever you decide a node is a child | |
| 61 assert(child != null); | |
| 62 setParentData(child); | |
| 63 super.adoptChild(child); | |
| 64 } | |
| 65 void dropChild(RenderNode child) { // only for use by subclasses | |
| 66 assert(child != null); | |
| 67 assert(child.parentData != null); | |
| 68 child.parentData.detach(); | |
| 69 super.dropChild(child); | |
| 70 } | |
| 71 | |
| 72 static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>(); | |
| 73 static bool _debugDoingLayout = false; | |
| 74 bool _needsLayout = true; | |
| 75 bool get needsLayout => _needsLayout; | |
| 76 RenderNode _relayoutSubtreeRoot; | |
| 77 dynamic _constraints; | |
| 78 dynamic get constraints => _constraints; | |
| 79 bool debugAncestorsAlreadyMarkedNeedsLayout() { | |
| 80 if (_relayoutSubtreeRoot == null) | |
| 81 return true; // we haven't yet done layout even once, so there's nothing f
or us to do | |
| 82 RenderNode node = this; | |
| 83 while (node != _relayoutSubtreeRoot) { | |
| 84 assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot); | |
| 85 assert(node.parent != null); | |
| 86 node = node.parent as RenderNode; | |
| 87 if (!node._needsLayout) | |
| 88 return false; | |
| 89 } | |
| 90 assert(node._relayoutSubtreeRoot == node); | |
| 91 return true; | |
| 92 } | |
| 93 void markNeedsLayout() { | |
| 94 assert(!_debugDoingLayout); | |
| 95 assert(!_debugDoingPaint); | |
| 96 if (_needsLayout) { | |
| 97 assert(debugAncestorsAlreadyMarkedNeedsLayout()); | |
| 98 return; | |
| 99 } | |
| 100 _needsLayout = true; | |
| 101 assert(_relayoutSubtreeRoot != null); | |
| 102 if (_relayoutSubtreeRoot != this) { | |
| 103 assert(parent is RenderNode); | |
| 104 parent.markNeedsLayout(); | |
| 105 } else { | |
| 106 _nodesNeedingLayout.add(this); | |
| 107 } | |
| 108 } | |
| 109 static void flushLayout() { | |
| 110 _debugDoingLayout = true; | |
| 111 List<RenderNode> dirtyNodes = _nodesNeedingLayout; | |
| 112 _nodesNeedingLayout = new List<RenderNode>(); | |
| 113 dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) { | |
| 114 if (node._needsLayout && node.attached) | |
| 115 node._doLayout(); | |
| 116 }); | |
| 117 _debugDoingLayout = false; | |
| 118 } | |
| 119 void _doLayout() { | |
| 120 try { | |
| 121 assert(_relayoutSubtreeRoot == this); | |
| 122 performLayout(); | |
| 123 } catch (e, stack) { | |
| 124 print('Exception raised during layout of ${this}: ${e}'); | |
| 125 print(stack); | |
| 126 return; | |
| 127 } | |
| 128 _needsLayout = false; | |
| 129 } | |
| 130 void layout(dynamic constraints, { bool parentUsesSize: false }) { | |
| 131 RenderNode relayoutSubtreeRoot; | |
| 132 if (!parentUsesSize || sizedByParent || parent is! RenderNode) | |
| 133 relayoutSubtreeRoot = this; | |
| 134 else | |
| 135 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; | |
| 136 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) | |
| 137 return; | |
| 138 _constraints = constraints; | |
| 139 _relayoutSubtreeRoot = relayoutSubtreeRoot; | |
| 140 if (sizedByParent) | |
| 141 performResize(); | |
| 142 performLayout(); | |
| 143 _needsLayout = false; | |
| 144 markNeedsPaint(); | |
| 145 } | |
| 146 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) | |
| 147 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) | |
| 148 void performLayout(); | |
| 149 // Override this to perform relayout without your parent's | |
| 150 // involvement. | |
| 151 // | |
| 152 // This is called during layout. If sizedByParent is true, then | |
| 153 // performLayout() should not change your dimensions, only do that | |
| 154 // in performResize(). If sizedByParent is false, then set both | |
| 155 // your dimensions and do your children's layout here. | |
| 156 // | |
| 157 // When calling layout() on your children, pass in | |
| 158 // "parentUsesSize: true" if your size or layout is dependent on | |
| 159 // your child's size. | |
| 160 | |
| 161 // when the parent has rotated (e.g. when the screen has been turned | |
| 162 // 90 degrees), immediately prior to layout() being called for the | |
| 163 // new dimensions, rotate() is called with the old and new angles. | |
| 164 // The next time paint() is called, the coordinate space will have | |
| 165 // been rotated N quarter-turns clockwise, where: | |
| 166 // N = newAngle-oldAngle | |
| 167 // ...but the rendering is expected to remain the same, pixel for | |
| 168 // pixel, on the output device. Then, the layout() method or | |
| 169 // equivalent will be invoked. | |
| 170 | |
| 171 void rotate({ | |
| 172 int oldAngle, // 0..3 | |
| 173 int newAngle, // 0..3 | |
| 174 Duration time | |
| 175 }) { } | |
| 176 | |
| 177 | |
| 178 // PAINTING | |
| 179 | |
| 180 static bool _debugDoingPaint = false; | |
| 181 void markNeedsPaint() { | |
| 182 assert(!_debugDoingPaint); | |
| 183 // TODO(abarth): It's very redundant to call this for every node in the | |
| 184 // render tree during layout. We should instead compute a summary bit and | |
| 185 // call it once at the end of layout. | |
| 186 sky.view.scheduleFrame(); | |
| 187 } | |
| 188 void paint(RenderNodeDisplayList canvas) { } | |
| 189 | |
| 190 | |
| 191 // HIT TESTING | |
| 192 | |
| 193 void handlePointer(sky.PointerEvent event) { | |
| 194 // override this if you have a client, to hand it to the client | |
| 195 // override this if you want to do anything with the pointer event | |
| 196 } | |
| 197 | |
| 198 // RenderNode subclasses are expected to have a method like the | |
| 199 // following (with the signature being whatever passes for coordinates | |
| 200 // for this particular class): | |
| 201 // bool hitTest(HitTestResult result, { sky.Point position }) { | |
| 202 // // If (x,y) is not inside this node, then return false. (You | |
| 203 // // can assume that the given coordinate is inside your | |
| 204 // // dimensions. You only need to check this if you're an | |
| 205 // // irregular shape, e.g. if you have a hole.) | |
| 206 // // Otherwise: | |
| 207 // // For each child that intersects x,y, in z-order starting from the top, | |
| 208 // // call hitTest() for that child, passing it /result/, and the coordinate
s | |
| 209 // // converted to the child's coordinate origin, and stop at the first chil
d | |
| 210 // // that returns true. | |
| 211 // // Then, add yourself to /result/, and return true. | |
| 212 // } | |
| 213 // You must not add yourself to /result/ if you return false. | |
| 214 | |
| 215 } | |
| 216 | |
| 217 class HitTestResult { | |
| 218 final List<RenderNode> path = new List<RenderNode>(); | |
| 219 | |
| 220 RenderNode get result => path.first; | |
| 221 | |
| 222 void add(RenderNode node) { | |
| 223 path.add(node); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 | |
| 228 // GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD | |
| 229 | |
| 230 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { | |
| 231 ChildType _child; | |
| 232 ChildType get child => _child; | |
| 233 void set child (ChildType value) { | |
| 234 if (_child != null) | |
| 235 dropChild(_child); | |
| 236 _child = value; | |
| 237 if (_child != null) | |
| 238 adoptChild(_child); | |
| 239 markNeedsLayout(); | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 | |
| 244 // GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN | |
| 245 | |
| 246 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
| 247 ChildType previousSibling; | |
| 248 ChildType nextSibling; | |
| 249 void detachSiblings() { | |
| 250 if (previousSibling != null) { | |
| 251 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 252 assert(previousSibling != this); | |
| 253 assert(previousSibling.parentData.nextSibling == this); | |
| 254 previousSibling.parentData.nextSibling = nextSibling; | |
| 255 } | |
| 256 if (nextSibling != null) { | |
| 257 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 258 assert(nextSibling != this); | |
| 259 assert(nextSibling.parentData.previousSibling == this); | |
| 260 nextSibling.parentData.previousSibling = previousSibling; | |
| 261 } | |
| 262 previousSibling = null; | |
| 263 nextSibling = null; | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
| 268 // abstract class that has only InlineNode children | |
| 269 | |
| 270 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
| 271 assert(child.parentData is ParentDataType); | |
| 272 while (child.parentData.previousSibling != null) { | |
| 273 assert(child.parentData.previousSibling != child); | |
| 274 child = child.parentData.previousSibling; | |
| 275 assert(child.parentData is ParentDataType); | |
| 276 } | |
| 277 return child == equals; | |
| 278 } | |
| 279 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
| 280 assert(child.parentData is ParentDataType); | |
| 281 while (child.parentData.nextSibling != null) { | |
| 282 assert(child.parentData.nextSibling != child); | |
| 283 child = child.parentData.nextSibling; | |
| 284 assert(child.parentData is ParentDataType); | |
| 285 } | |
| 286 return child == equals; | |
| 287 } | |
| 288 | |
| 289 ChildType _firstChild; | |
| 290 ChildType _lastChild; | |
| 291 void add(ChildType child, { ChildType before }) { | |
| 292 assert(child != this); | |
| 293 assert(before != this); | |
| 294 assert(child != before); | |
| 295 assert(child != _firstChild); | |
| 296 assert(child != _lastChild); | |
| 297 adoptChild(child); | |
| 298 assert(child.parentData is ParentDataType); | |
| 299 assert(child.parentData.nextSibling == null); | |
| 300 assert(child.parentData.previousSibling == null); | |
| 301 if (before == null) { | |
| 302 // append at the end (_lastChild) | |
| 303 child.parentData.previousSibling = _lastChild; | |
| 304 if (_lastChild != null) { | |
| 305 assert(_lastChild.parentData is ParentDataType); | |
| 306 _lastChild.parentData.nextSibling = child; | |
| 307 } | |
| 308 _lastChild = child; | |
| 309 if (_firstChild == null) | |
| 310 _firstChild = child; | |
| 311 } else { | |
| 312 assert(_firstChild != null); | |
| 313 assert(_lastChild != null); | |
| 314 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
| 315 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
| 316 assert(before.parentData is ParentDataType); | |
| 317 if (before.parentData.previousSibling == null) { | |
| 318 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
| 319 assert(before == _firstChild); | |
| 320 child.parentData.nextSibling = before; | |
| 321 before.parentData.previousSibling = child; | |
| 322 _firstChild = child; | |
| 323 } else { | |
| 324 // insert in the middle; we'll end up with three or more children | |
| 325 // set up links from child to siblings | |
| 326 child.parentData.previousSibling = before.parentData.previousSibling; | |
| 327 child.parentData.nextSibling = before; | |
| 328 // set up links from siblings to child | |
| 329 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 330 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 331 child.parentData.previousSibling.parentData.nextSibling = child; | |
| 332 child.parentData.nextSibling.parentData.previousSibling = child; | |
| 333 assert(before.parentData.previousSibling == child); | |
| 334 } | |
| 335 } | |
| 336 markNeedsLayout(); | |
| 337 } | |
| 338 void remove(ChildType child) { | |
| 339 assert(child.parentData is ParentDataType); | |
| 340 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
| 341 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
| 342 if (child.parentData.previousSibling == null) { | |
| 343 assert(_firstChild == child); | |
| 344 _firstChild = child.parentData.nextSibling; | |
| 345 } else { | |
| 346 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 347 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
| 348 } | |
| 349 if (child.parentData.nextSibling == null) { | |
| 350 assert(_lastChild == child); | |
| 351 _lastChild = child.parentData.previousSibling; | |
| 352 } else { | |
| 353 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 354 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
| 355 } | |
| 356 child.parentData.previousSibling = null; | |
| 357 child.parentData.nextSibling = null; | |
| 358 dropChild(child); | |
| 359 markNeedsLayout(); | |
| 360 } | |
| 361 void redepthChildren() { | |
| 362 ChildType child = _firstChild; | |
| 363 while (child != null) { | |
| 364 redepthChild(child); | |
| 365 assert(child.parentData is ParentDataType); | |
| 366 child = child.parentData.nextSibling; | |
| 367 } | |
| 368 } | |
| 369 void attachChildren() { | |
| 370 ChildType child = _firstChild; | |
| 371 while (child != null) { | |
| 372 child.attach(); | |
| 373 assert(child.parentData is ParentDataType); | |
| 374 child = child.parentData.nextSibling; | |
| 375 } | |
| 376 } | |
| 377 void detachChildren() { | |
| 378 ChildType child = _firstChild; | |
| 379 while (child != null) { | |
| 380 child.detach(); | |
| 381 assert(child.parentData is ParentDataType); | |
| 382 child = child.parentData.nextSibling; | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 ChildType get firstChild => _firstChild; | |
| 387 ChildType get lastChild => _lastChild; | |
| 388 ChildType childAfter(ChildType child) { | |
| 389 assert(child.parentData is ParentDataType); | |
| 390 return child.parentData.nextSibling; | |
| 391 } | |
| 392 | |
| 393 } | |
| 394 | |
| 395 | |
| 396 // GENERIC BOX RENDERING | |
| 397 // Anything that has a concept of x, y, width, height is going to derive from th
is | |
| 398 | |
| 399 class EdgeDims { | |
| 400 // used for e.g. padding | |
| 401 const EdgeDims(this.top, this.right, this.bottom, this.left); | |
| 402 final double top; | |
| 403 final double right; | |
| 404 final double bottom; | |
| 405 final double left; | |
| 406 operator ==(EdgeDims other) => (top == other.top) || | |
| 407 (right == other.right) || | |
| 408 (bottom == other.bottom) || | |
| 409 (left == other.left); | |
| 410 } | |
| 411 | |
| 412 class BoxConstraints { | |
| 413 const BoxConstraints({ | |
| 414 this.minWidth: 0.0, | |
| 415 this.maxWidth: double.INFINITY, | |
| 416 this.minHeight: 0.0, | |
| 417 this.maxHeight: double.INFINITY}); | |
| 418 | |
| 419 BoxConstraints.tight(sky.Size size) | |
| 420 : minWidth = size.width, | |
| 421 maxWidth = size.width, | |
| 422 minHeight = size.height, | |
| 423 maxHeight = size.height; | |
| 424 | |
| 425 BoxConstraints deflate(EdgeDims edges) { | |
| 426 assert(edges != null); | |
| 427 return new BoxConstraints( | |
| 428 minWidth: minWidth, | |
| 429 maxWidth: maxWidth - (edges.left + edges.right), | |
| 430 minHeight: minHeight, | |
| 431 maxHeight: maxHeight - (edges.top + edges.bottom) | |
| 432 ); | |
| 433 } | |
| 434 | |
| 435 final double minWidth; | |
| 436 final double maxWidth; | |
| 437 final double minHeight; | |
| 438 final double maxHeight; | |
| 439 | |
| 440 double constrainWidth(double width) { | |
| 441 return clamp(min: minWidth, max: maxWidth, value: width); | |
| 442 } | |
| 443 | |
| 444 double constrainHeight(double height) { | |
| 445 return clamp(min: minHeight, max: maxHeight, value: height); | |
| 446 } | |
| 447 | |
| 448 sky.Size constrain(sky.Size size) { | |
| 449 return new sky.Size(constrainWidth(size.width), constrainHeight(size.height)
); | |
| 450 } | |
| 451 | |
| 452 bool get isInfinite => maxWidth >= double.INFINITY || maxHeight >= double.INFI
NITY; | |
| 453 } | |
| 454 | |
| 455 class BoxParentData extends ParentData { | |
| 456 sky.Point position = new sky.Point(0.0, 0.0); | |
| 457 } | |
| 458 | |
| 459 abstract class RenderBox extends RenderNode { | |
| 460 | |
| 461 void setParentData(RenderNode child) { | |
| 462 if (child.parentData is! BoxParentData) | |
| 463 child.parentData = new BoxParentData(); | |
| 464 } | |
| 465 | |
| 466 // override this to report what dimensions you would have if you | |
| 467 // were laid out with the given constraints this can walk the tree | |
| 468 // if it must, but it should be as cheap as possible; just get the | |
| 469 // dimensions and nothing else (e.g. don't calculate hypothetical | |
| 470 // child positions if they're not needed to determine dimensions) | |
| 471 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
| 472 return constraints.constrain(new sky.Size(0.0, 0.0)); | |
| 473 } | |
| 474 | |
| 475 BoxConstraints get constraints => super.constraints as BoxConstraints; | |
| 476 void performResize() { | |
| 477 // default behaviour for subclasses that have sizedByParent = true | |
| 478 size = constraints.constrain(new sky.Size(0.0, 0.0)); | |
| 479 assert(size.height < double.INFINITY); | |
| 480 assert(size.width < double.INFINITY); | |
| 481 } | |
| 482 void performLayout() { | |
| 483 // descendants have to either override performLayout() to set both | |
| 484 // width and height and lay out children, or, set sizedByParent to | |
| 485 // true so that performResize()'s logic above does its thing. | |
| 486 assert(sizedByParent); | |
| 487 } | |
| 488 | |
| 489 bool hitTest(HitTestResult result, { sky.Point position }) { | |
| 490 hitTestChildren(result, position: position); | |
| 491 result.add(this); | |
| 492 return true; | |
| 493 } | |
| 494 void hitTestChildren(HitTestResult result, { sky.Point position }) { } | |
| 495 | |
| 496 sky.Size size = new sky.Size(0.0, 0.0); | |
| 497 } | |
| 498 | |
| 499 abstract class RenderProxyBox extends RenderBox with RenderNodeWithChildMixin<Re
nderBox> { | |
| 500 RenderProxyBox(RenderBox child) { | |
| 501 this.child = child; | |
| 502 } | |
| 503 | |
| 504 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
| 505 if (child != null) | |
| 506 return child.getIntrinsicDimensions(constraints); | |
| 507 return super.getIntrinsicDimensions(constraints); | |
| 508 } | |
| 509 | |
| 510 void performLayout() { | |
| 511 if (child != null) { | |
| 512 child.layout(constraints, parentUsesSize: true); | |
| 513 size = child.size; | |
| 514 } else { | |
| 515 performResize(); | |
| 516 } | |
| 517 } | |
| 518 | |
| 519 void hitTestChildren(HitTestResult result, { sky.Point position }) { | |
| 520 if (child != null) | |
| 521 child.hitTest(result, position: position); | |
| 522 else | |
| 523 super.hitTestChildren(result, position: position); | |
| 524 } | |
| 525 | |
| 526 void paint(RenderNodeDisplayList canvas) { | |
| 527 if (child != null) | |
| 528 child.paint(canvas); | |
| 529 } | |
| 530 } | |
| 531 | |
| 532 class RenderSizedBox extends RenderProxyBox { | |
| 533 final sky.Size desiredSize; | |
| 534 | |
| 535 RenderSizedBox({ | |
| 536 RenderBox child, | |
| 537 this.desiredSize: const sky.Size.infinite() | |
| 538 }) : super(child); | |
| 539 | |
| 540 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
| 541 return constraints.constrain(desiredSize); | |
| 542 } | |
| 543 | |
| 544 void performLayout() { | |
| 545 size = constraints.constrain(desiredSize); | |
| 546 child.layout(new BoxConstraints.tight(size)); | |
| 547 } | |
| 548 } | |
| 549 | |
| 550 class RenderPadding extends RenderBox with RenderNodeWithChildMixin<RenderBox> { | |
| 551 | |
| 552 RenderPadding(EdgeDims padding, RenderBox child) { | |
| 553 assert(padding != null); | |
| 554 this.padding = padding; | |
| 555 this.child = child; | |
| 556 } | |
| 557 | |
| 558 EdgeDims _padding; | |
| 559 EdgeDims get padding => _padding; | |
| 560 void set padding (EdgeDims value) { | |
| 561 assert(value != null); | |
| 562 if (_padding != value) { | |
| 563 _padding = value; | |
| 564 markNeedsLayout(); | |
| 565 } | |
| 566 } | |
| 567 | |
| 568 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
| 569 assert(padding != null); | |
| 570 constraints = constraints.deflate(padding); | |
| 571 if (child == null) | |
| 572 return super.getIntrinsicDimensions(constraints); | |
| 573 return child.getIntrinsicDimensions(constraints); | |
| 574 } | |
| 575 | |
| 576 void performLayout() { | |
| 577 assert(padding != null); | |
| 578 BoxConstraints innerConstraints = constraints.deflate(padding); | |
| 579 if (child == null) { | |
| 580 size = innerConstraints.constrain( | |
| 581 new sky.Size(padding.left + padding.right, padding.top + padding.botto
m)); | |
| 582 return; | |
| 583 } | |
| 584 child.layout(innerConstraints, parentUsesSize: true); | |
| 585 assert(child.parentData is BoxParentData); | |
| 586 child.parentData.position = new sky.Point(padding.left, padding.top); | |
| 587 size = constraints.constrain(new sky.Size(padding.left + child.size.width +
padding.right, | |
| 588 padding.top + child.size.height +
padding.bottom)); | |
| 589 } | |
| 590 | |
| 591 void paint(RenderNodeDisplayList canvas) { | |
| 592 if (child != null) | |
| 593 canvas.paintChild(child, child.parentData.position); | |
| 594 } | |
| 595 | |
| 596 void hitTestChildren(HitTestResult result, { sky.Point position }) { | |
| 597 if (child != null) { | |
| 598 assert(child.parentData is BoxParentData); | |
| 599 sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.posi
tion, child.size); | |
| 600 if (childBounds.contains(position)) { | |
| 601 child.hitTest(result, position: new sky.Point(position.x - child.parentD
ata.position.x, | |
| 602 position.y - child.parentD
ata.position.y)); | |
| 603 } | |
| 604 } | |
| 605 } | |
| 606 | |
| 607 } | |
| 608 | |
| 609 // This must be immutable, because we won't notice when it changes | |
| 610 class BoxDecoration { | |
| 611 const BoxDecoration({ | |
| 612 this.backgroundColor | |
| 613 }); | |
| 614 | |
| 615 final int backgroundColor; | |
| 616 } | |
| 617 | |
| 618 class RenderDecoratedBox extends RenderProxyBox { | |
| 619 | |
| 620 RenderDecoratedBox({ | |
| 621 BoxDecoration decoration, | |
| 622 RenderBox child | |
| 623 }) : _decoration = decoration, super(child); | |
| 624 | |
| 625 BoxDecoration _decoration; | |
| 626 BoxDecoration get decoration => _decoration; | |
| 627 void set decoration (BoxDecoration value) { | |
| 628 if (value == _decoration) | |
| 629 return; | |
| 630 _decoration = value; | |
| 631 markNeedsPaint(); | |
| 632 } | |
| 633 | |
| 634 void paint(RenderNodeDisplayList canvas) { | |
| 635 assert(size.width != null); | |
| 636 assert(size.height != null); | |
| 637 | |
| 638 if (_decoration == null) | |
| 639 return; | |
| 640 | |
| 641 if (_decoration.backgroundColor != null) { | |
| 642 sky.Paint paint = new sky.Paint()..color = _decoration.backgroundColor; | |
| 643 canvas.drawRect(new sky.Rect.fromLTRB(0.0, 0.0, size.width, size.height),
paint); | |
| 644 } | |
| 645 super.paint(canvas); | |
| 646 } | |
| 647 | |
| 648 } | |
| 649 | |
| 650 | |
| 651 // RENDER VIEW LAYOUT MANAGER | |
| 652 | |
| 653 class ViewConstraints { | |
| 654 | |
| 655 const ViewConstraints({ | |
| 656 this.width: 0.0, this.height: 0.0, this.orientation: null | |
| 657 }); | |
| 658 | |
| 659 final double width; | |
| 660 final double height; | |
| 661 final int orientation; | |
| 662 | |
| 663 } | |
| 664 | |
| 665 class RenderView extends RenderNode with RenderNodeWithChildMixin<RenderBox> { | |
| 666 | |
| 667 RenderView({ | |
| 668 RenderBox child, | |
| 669 this.timeForRotation: const Duration(microseconds: 83333) | |
| 670 }) { | |
| 671 this.child = child; | |
| 672 } | |
| 673 | |
| 674 sky.Size _size = new sky.Size(0.0, 0.0); | |
| 675 double get width => _size.width; | |
| 676 double get height => _size.height; | |
| 677 | |
| 678 int _orientation; // 0..3 | |
| 679 int get orientation => _orientation; | |
| 680 Duration timeForRotation; | |
| 681 | |
| 682 ViewConstraints get constraints => super.constraints as ViewConstraints; | |
| 683 bool get sizedByParent => true; | |
| 684 void performResize() { | |
| 685 if (constraints.orientation != _orientation) { | |
| 686 if (_orientation != null && child != null) | |
| 687 child.rotate(oldAngle: _orientation, newAngle: constraints.orientation,
time: timeForRotation); | |
| 688 _orientation = constraints.orientation; | |
| 689 } | |
| 690 _size = new sky.Size(constraints.width, constraints.height); | |
| 691 assert(_size.height < double.INFINITY); | |
| 692 assert(_size.width < double.INFINITY); | |
| 693 } | |
| 694 void performLayout() { | |
| 695 if (child != null) { | |
| 696 child.layout(new BoxConstraints.tight(_size)); | |
| 697 assert(child.size.width == width); | |
| 698 assert(child.size.height == height); | |
| 699 } | |
| 700 } | |
| 701 | |
| 702 void rotate({ int oldAngle, int newAngle, Duration time }) { | |
| 703 assert(false); // nobody tells the screen to rotate, the whole rotate() danc
e is started from our performResize() | |
| 704 } | |
| 705 | |
| 706 bool hitTest(HitTestResult result, { sky.Point position }) { | |
| 707 if (child != null) { | |
| 708 sky.Rect childBounds = new sky.Rect.fromSize(child.size); | |
| 709 if (childBounds.contains(position)) | |
| 710 child.hitTest(result, position: position); | |
| 711 } | |
| 712 result.add(this); | |
| 713 return true; | |
| 714 } | |
| 715 | |
| 716 void paint(RenderNodeDisplayList canvas) { | |
| 717 if (child != null) | |
| 718 canvas.paintChild(child, new sky.Point(0.0, 0.0)); | |
| 719 } | |
| 720 | |
| 721 void paintFrame() { | |
| 722 RenderNode._debugDoingPaint = true; | |
| 723 var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height); | |
| 724 paint(canvas); | |
| 725 sky.view.picture = canvas.endRecording(); | |
| 726 RenderNode._debugDoingPaint = false; | |
| 727 } | |
| 728 | |
| 729 } | |
| 730 | |
| 731 // DEFAULT BEHAVIORS FOR RENDERBOX CONTAINERS | |
| 732 abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
ntDataType extends ContainerParentDataMixin<ChildType>> implements ContainerRend
erNodeMixin<ChildType, ParentDataType> { | |
| 733 | |
| 734 void defaultHitTestChildren(HitTestResult result, { sky.Point position }) { | |
| 735 // the x, y parameters have the top left of the node's box as the origin | |
| 736 ChildType child = lastChild; | |
| 737 while (child != null) { | |
| 738 assert(child.parentData is BoxParentData); | |
| 739 sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.posi
tion, child.size); | |
| 740 if (childBounds.contains(position)) { | |
| 741 if (child.hitTest(result, position: new sky.Point(position.x - child.par
entData.position.x, | |
| 742 position.y - child.par
entData.position.y))) | |
| 743 break; | |
| 744 } | |
| 745 child = child.parentData.previousSibling; | |
| 746 } | |
| 747 } | |
| 748 | |
| 749 void defaultPaint(RenderNodeDisplayList canvas) { | |
| 750 RenderBox child = firstChild; | |
| 751 while (child != null) { | |
| 752 assert(child.parentData is BoxParentData); | |
| 753 canvas.paintChild(child, child.parentData.position); | |
| 754 child = child.parentData.nextSibling; | |
| 755 } | |
| 756 } | |
| 757 } | |
| 758 | |
| 759 // BLOCK LAYOUT MANAGER | |
| 760 | |
| 761 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render
Box> { } | |
| 762 | |
| 763 class RenderBlock extends RenderBox with ContainerRenderNodeMixin<RenderBox, Blo
ckParentData>, | |
| 764 RenderBoxContainerDefaultsMixin<RenderB
ox, BlockParentData> { | |
| 765 // lays out RenderBox children in a vertical stack | |
| 766 // uses the maximum width provided by the parent | |
| 767 // sizes itself to the height of its child stack | |
| 768 | |
| 769 void setParentData(RenderBox child) { | |
| 770 if (child.parentData is! BlockParentData) | |
| 771 child.parentData = new BlockParentData(); | |
| 772 } | |
| 773 | |
| 774 // override this to report what dimensions you would have if you | |
| 775 // were laid out with the given constraints this can walk the tree | |
| 776 // if it must, but it should be as cheap as possible; just get the | |
| 777 // dimensions and nothing else (e.g. don't calculate hypothetical | |
| 778 // child positions if they're not needed to determine dimensions) | |
| 779 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
| 780 double height = 0.0; | |
| 781 double width = constraints.constrainWidth(constraints.maxWidth); | |
| 782 assert(width < double.INFINITY); | |
| 783 RenderBox child = firstChild; | |
| 784 BoxConstraints innerConstraints = new BoxConstraints(minWidth: width, | |
| 785 maxWidth: width); | |
| 786 while (child != null) { | |
| 787 height += child.getIntrinsicDimensions(innerConstraints).height; | |
| 788 assert(child.parentData is BlockParentData); | |
| 789 child = child.parentData.nextSibling; | |
| 790 } | |
| 791 | |
| 792 return new sky.Size(width, constraints.constrainHeight(height)); | |
| 793 } | |
| 794 | |
| 795 void performLayout() { | |
| 796 assert(constraints is BoxConstraints); | |
| 797 double width = constraints.constrainWidth(constraints.maxWidth); | |
| 798 double y = 0.0; | |
| 799 RenderBox child = firstChild; | |
| 800 while (child != null) { | |
| 801 child.layout(new BoxConstraints(minWidth: width, maxWidth: width), parentU
sesSize: true); | |
| 802 assert(child.parentData is BlockParentData); | |
| 803 child.parentData.position = new sky.Point(0.0, y); | |
| 804 y += child.size.height; | |
| 805 child = child.parentData.nextSibling; | |
| 806 } | |
| 807 size = new sky.Size(width, constraints.constrainHeight(y)); | |
| 808 assert(size.width < double.INFINITY); | |
| 809 assert(size.height < double.INFINITY); | |
| 810 } | |
| 811 | |
| 812 void hitTestChildren(HitTestResult result, { sky.Point position }) { | |
| 813 defaultHitTestChildren(result, position: position); | |
| 814 } | |
| 815 | |
| 816 void paint(RenderNodeDisplayList canvas) { | |
| 817 defaultPaint(canvas); | |
| 818 } | |
| 819 | |
| 820 } | |
| 821 | |
| 822 // FLEXBOX LAYOUT MANAGER | |
| 823 | |
| 824 class FlexBoxParentData extends BoxParentData with ContainerParentDataMixin<Rend
erBox> { | |
| 825 int flex; | |
| 826 void merge(FlexBoxParentData other) { | |
| 827 if (other.flex != null) | |
| 828 flex = other.flex; | |
| 829 super.merge(other); | |
| 830 } | |
| 831 } | |
| 832 | |
| 833 enum FlexDirection { Horizontal, Vertical } | |
| 834 | |
| 835 class RenderFlex extends RenderBox with ContainerRenderNodeMixin<RenderBox, Flex
BoxParentData>, | |
| 836 RenderBoxContainerDefaultsMixin<RenderBo
x, BlockParentData> { | |
| 837 // lays out RenderBox children using flexible layout | |
| 838 | |
| 839 RenderFlex({ | |
| 840 FlexDirection direction: FlexDirection.Horizontal | |
| 841 }) : _direction = direction; | |
| 842 | |
| 843 FlexDirection _direction; | |
| 844 FlexDirection get direction => _direction; | |
| 845 void set direction (FlexDirection value) { | |
| 846 if (_direction != value) { | |
| 847 _direction = value; | |
| 848 markNeedsLayout(); | |
| 849 } | |
| 850 } | |
| 851 | |
| 852 void setParentData(RenderBox child) { | |
| 853 if (child.parentData is! FlexBoxParentData) | |
| 854 child.parentData = new FlexBoxParentData(); | |
| 855 } | |
| 856 | |
| 857 bool get sizedByParent => true; | |
| 858 void performResize() { | |
| 859 size = _constraints.constrain(new sky.Size(_constraints.maxWidth, _constrain
ts.maxHeight)); | |
| 860 assert(size.height < double.INFINITY); | |
| 861 assert(size.width < double.INFINITY); | |
| 862 } | |
| 863 | |
| 864 int _getFlex(RenderBox child) { | |
| 865 assert(child.parentData is FlexBoxParentData); | |
| 866 return child.parentData.flex != null ? child.parentData.flex : 0; | |
| 867 } | |
| 868 | |
| 869 void performLayout() { | |
| 870 // Based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexib
le Lengths | |
| 871 // Steps 1-3. Determine used flex factor, size inflexible items, calculate f
ree space | |
| 872 int totalFlex = 0; | |
| 873 assert(constraints != null); | |
| 874 double freeSpace = (_direction == FlexDirection.Horizontal) ? constraints.ma
xWidth : constraints.maxHeight; | |
| 875 RenderBox child = firstChild; | |
| 876 while (child != null) { | |
| 877 int flex = _getFlex(child); | |
| 878 if (flex > 0) { | |
| 879 totalFlex += child.parentData.flex; | |
| 880 } else { | |
| 881 BoxConstraints innerConstraints = new BoxConstraints(maxHeight: constrai
nts.maxHeight, | |
| 882 maxWidth: constrain
ts.maxWidth); | |
| 883 child.layout(innerConstraints, parentUsesSize: true); | |
| 884 freeSpace -= (_direction == FlexDirection.Horizontal) ? child.size.width
: child.size.height; | |
| 885 } | |
| 886 child = child.parentData.nextSibling; | |
| 887 } | |
| 888 | |
| 889 // Steps 4-5. Distribute remaining space to flexible children. | |
| 890 double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0; | |
| 891 double usedSpace = 0.0; | |
| 892 child = firstChild; | |
| 893 while (child != null) { | |
| 894 int flex = _getFlex(child); | |
| 895 if (flex > 0) { | |
| 896 double spaceForChild = spacePerFlex * flex; | |
| 897 BoxConstraints innerConstraints; | |
| 898 switch (_direction) { | |
| 899 case FlexDirection.Horizontal: | |
| 900 innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeig
ht, | |
| 901 minWidth: spaceForChild, | |
| 902 maxWidth: spaceForChild); | |
| 903 break; | |
| 904 case FlexDirection.Vertical: | |
| 905 innerConstraints = new BoxConstraints(minHeight: spaceForChild, | |
| 906 maxHeight: spaceForChild, | |
| 907 maxWidth: constraints.maxWidth
); | |
| 908 break; | |
| 909 } | |
| 910 child.layout(innerConstraints, parentUsesSize: true); | |
| 911 } | |
| 912 | |
| 913 // For now, center the flex items in the cross direction | |
| 914 switch (_direction) { | |
| 915 case FlexDirection.Horizontal: | |
| 916 child.parentData.position = new sky.Point(usedSpace, size.height / 2.0
- child.size.height / 2.0); | |
| 917 usedSpace += child.size.width; | |
| 918 break; | |
| 919 case FlexDirection.Vertical: | |
| 920 child.parentData.position = new sky.Point(size.width / 2.0 - child.siz
e.width / 2.0, usedSpace); | |
| 921 usedSpace += child.size.height; | |
| 922 break; | |
| 923 } | |
| 924 child = child.parentData.nextSibling; | |
| 925 } | |
| 926 } | |
| 927 | |
| 928 void hitTestChildren(HitTestResult result, { sky.Point position }) { | |
| 929 defaultHitTestChildren(result, position: position); | |
| 930 } | |
| 931 | |
| 932 void paint(RenderNodeDisplayList canvas) { | |
| 933 defaultPaint(canvas); | |
| 934 } | |
| 935 } | |
| 936 | |
| 937 class RenderInline extends RenderNode { | |
| 938 String data; | |
| 939 | |
| 940 RenderInline(this.data); | |
| 941 } | |
| 942 | |
| 943 class RenderParagraph extends RenderBox { | |
| 944 | |
| 945 RenderParagraph({ | |
| 946 String text, | |
| 947 int color | |
| 948 }) : _color = color { | |
| 949 _layoutRoot.rootElement = _document.createElement('p'); | |
| 950 this.text = text; | |
| 951 } | |
| 952 | |
| 953 final sky.Document _document = new sky.Document(); | |
| 954 final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot(); | |
| 955 | |
| 956 String get text => (_layoutRoot.rootElement.firstChild as sky.Text).data; | |
| 957 void set text (String value) { | |
| 958 _layoutRoot.rootElement.setChild(_document.createText(value)); | |
| 959 markNeedsLayout(); | |
| 960 } | |
| 961 | |
| 962 int _color = 0xFF000000; | |
| 963 int get color => _color; | |
| 964 void set color (int value) { | |
| 965 if (_color != value) { | |
| 966 _color = value; | |
| 967 markNeedsPaint(); | |
| 968 } | |
| 969 } | |
| 970 | |
| 971 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
| 972 assert(false); | |
| 973 return null; | |
| 974 // we don't currently support this for RenderParagraph | |
| 975 } | |
| 976 | |
| 977 void performLayout() { | |
| 978 _layoutRoot.maxWidth = constraints.maxWidth; | |
| 979 _layoutRoot.minWidth = constraints.minWidth; | |
| 980 _layoutRoot.minHeight = constraints.minHeight; | |
| 981 _layoutRoot.maxHeight = constraints.maxHeight; | |
| 982 _layoutRoot.layout(); | |
| 983 size = constraints.constrain(new sky.Size(_layoutRoot.rootElement.width, _la
youtRoot.rootElement.height)); | |
| 984 } | |
| 985 | |
| 986 void paint(RenderNodeDisplayList canvas) { | |
| 987 // _layoutRoot.rootElement.style['color'] = 'rgba(' + ...color... + ')'; | |
| 988 _layoutRoot.paint(canvas); | |
| 989 } | |
| 990 | |
| 991 // we should probably expose a way to do precise (inter-glpyh) hit testing | |
| 992 | |
| 993 } | |
| OLD | NEW |