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 |