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