OLD | NEW |
---|---|
(Empty) | |
1 library layout; | |
2 | |
3 import 'node.dart'; | |
4 | |
5 // VALUE TYPES | |
6 | |
7 class EdgeDims { | |
8 // used for e.g. padding | |
9 const EdgeDims(this.top, this.right, this.bottom, this.left); | |
10 final double top; | |
11 final double right; | |
12 final double bottom; | |
13 final double left; | |
14 operator ==(EdgeDims other) => (top == other.top) || | |
15 (right == other.right) || | |
16 (bottom == other.bottom) || | |
17 (left == other.left); | |
18 } | |
19 | |
20 class Matrix { | |
21 external static Matrix get IDENTITY; | |
22 external void rotate(double cx, double cy, double theta); // rotate theta radi ans clockwise around cx,cy | |
23 } | |
24 | |
25 | |
26 // PAINTING | |
27 | |
28 class DisplayList { | |
29 List<RenderNode> _children; | |
30 void paintChild(RenderNode child, double x, double y) { | |
31 _addChildToDisplayList(child, x, y); | |
32 if (_children == null) | |
33 _children = new List<RenderNode>(); | |
34 assert(!_children.contains(child)); | |
35 _children.add(child); | |
36 } | |
37 external void _addChildToDisplayList(RenderNode child, double x, double y); | |
38 } | |
39 | |
40 | |
41 // ABSTRACT LAYOUT | |
42 | |
43 class ParentData { | |
44 void detach() { | |
45 detachSiblings(); | |
46 } | |
47 void detachSiblings() { } // workaround for lack of inter-class mixins in Dart | |
48 } | |
49 | |
50 class PaintOptions { | |
51 Matrix transform = Matrix.IDENTITY; | |
52 } | |
53 | |
54 const kLayoutDirections = 4; | |
55 | |
56 double clamp({double min: 0.0, double value: 0.0, double max: double.INFINITY}) { | |
57 if (value > max) | |
58 value = max; | |
59 if (value < min) | |
60 value = min; | |
61 return value; | |
62 } | |
63 | |
64 abstract class RenderNode extends Node { | |
65 | |
66 // LAYOUT | |
67 | |
68 // pos is only for use by the RenderNode that actually lays this | |
69 // node out, and any other nodes who happen to know exactly what | |
70 // kind of node that is. | |
71 ParentData pos; | |
72 void setupPos(RenderNode child) { | |
73 // override this to setup .pos correctly for your class | |
74 if (child.pos is! ParentData) | |
75 child.pos = new ParentData(); | |
76 } | |
77 | |
78 void setAsChild(RenderNode child) { // only for use by subclasses | |
79 // call this whenever you decide a node is a child | |
80 assert(child != null); | |
81 setupPos(child); | |
82 super.setAsChild(child); | |
83 } | |
84 void dropChild(RenderNode child) { // only for use by subclasses | |
85 assert(child != null); | |
86 assert(child.pos != null); | |
87 child.pos.detach(); | |
88 super.dropChild(child); | |
89 } | |
90 | |
91 static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>(); | |
92 static bool _debugDoingLayout = false; | |
93 bool _needsLayout = true; | |
94 bool get needsLayout => _needsLayout; | |
95 RenderNode _relayoutSubtreeRoot; | |
96 void saveRelayoutSubtreeRoot(RenderNode relayoutSubtreeRoot) { | |
97 assert(_relayoutSubtreeRoot._relayoutSubtreeRoot == null); | |
98 _relayoutSubtreeRoot = relayoutSubtreeRoot; | |
99 } | |
100 void markNeedsLayout() { | |
101 assert(!_debugDoingLayout); | |
102 assert(!_debugDoingPaint); | |
103 if (_needsLayout) return; | |
104 _needsLayout = true; | |
105 _nodesNeedingLayout.add(this); | |
106 } | |
107 static void flushLayout() { | |
108 _debugDoingLayout = true; | |
109 List<RenderNode> dirtyNodes = _nodesNeedingLayout; | |
110 _nodesNeedingLayout = new List<RenderNode>(); | |
111 dirtyNodes..sort((a, b) => a.order - b.order)..forEach((node) { | |
112 if (node._needsLayout && node.attached) | |
113 node._doLayout(); | |
114 }); | |
115 _debugDoingLayout = false; | |
116 } | |
117 void _doLayout() { | |
118 try { | |
119 if (_relayoutSubtreeRoot != null) { | |
120 assert(_relayoutSubtreeRoot._relayoutSubtreeRoot == null); | |
121 _relayoutSubtreeRoot._doLayout(); | |
122 } else { | |
123 relayout(); | |
ojan
2015/05/09 01:23:34
I was talking Elliott through this design and we r
Hixie
2015/05/13 20:29:21
Hm, yeah.
I don't see a way to work around this.
| |
124 } | |
125 } catch (e, stack) { | |
126 print('Exception raised during layout of ${this}: ${e}'); | |
127 print(stack); | |
128 return; | |
129 } | |
130 assert(!_needsLayout); // check that the relayout() method marked us "not di rty" | |
131 } | |
132 /* // this method's signature is subclass-specific, but will exist in | |
133 // some form in all subclasses: | |
134 void layout({arguments..., RenderNode relayoutSubtreeRoot}) { | |
135 if (this node has an opinion about its size, e.g. because it autosizes ba sed on kids, or has an intrinsic dimension) { | |
136 if (relayoutSubtreeRoot != null) { | |
137 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); | |
138 // for each child, if we are going to size ourselves around them: | |
139 child.layout(... relayoutSubtreeRoot: relayoutSubtreeRoot); | |
140 width = ...; | |
141 height = ...; | |
142 } else { | |
143 saveRelayoutSubtreeRoot(null); // you can skip this if there's no way you would ever have called saveRelayoutSubtreeRoot() before | |
144 // we're the root of the relayout subtree | |
145 // for each child, if we are going to size ourselves around them: | |
146 child.layout(... relayoutSubtreeRoot: this); | |
147 width = ...; | |
148 height = ...; | |
149 } | |
150 } else { | |
151 // we're sizing ourselves exclusively on input from the parent (argumen ts to this function) | |
152 // ignore relayoutSubtreeRoot | |
153 saveRelayoutSubtreeRoot(null); // you can skip this if there's no way y ou would ever have called saveRelayoutSubtreeRoot() before | |
154 width = ...; // based on input from arguments only | |
155 height = ...; // based on input from arguments only | |
156 } | |
157 // for each child whose size we'll ignore when deciding ours: | |
158 child.layout(... relayoutSubtreeRoot: null); // or just omit relayoutSubt reeRoot | |
159 layoutDone(); | |
160 return result; | |
161 } | |
162 */ | |
163 void relayout() { | |
164 // Override this to perform relayout without your parent's | |
165 // involvement. | |
166 // | |
167 // This is what is called after the first layout(), if you mark | |
168 // yourself dirty and don't have a _relayoutSubtreeRoot set; in | |
169 // other words, either if your parent doesn't care what size you | |
170 // are (and thus didn't pass a relayoutSubtreeRoot to your | |
171 // layout() method) or if you sized yourself entirely based on | |
172 // what your parents told you, and not based on your children (and | |
173 // thus you never called saveRelayoutSubtreeRoot()). | |
174 // | |
175 // In the former case, you can resize yourself here at will. In | |
176 // the latter case, just leave your dimensions unchanged. | |
177 // | |
178 // If _relayoutSubtreeRoot is set (i.e. you called saveRelayout- | |
179 // SubtreeRoot() in your layout(), with a relayoutSubtreeRoot | |
180 // argument that was non-null), then if you mark yourself as dirty | |
181 // then we'll tell that subtree root instead, and the layout will | |
182 // occur via the layout() tree rather than starting from this | |
183 // relayout() method. | |
184 assert(_relayoutSubtreeRoot == null); | |
185 layoutDone(); | |
186 } | |
187 void layoutDone({bool needsPaint: true}) { | |
188 // make sure to call this at the end of your layout() or relayout() | |
189 _needsLayout = false; | |
190 if (needsPaint) | |
191 markNeedsPaint(); | |
192 } | |
193 | |
194 // when the parent has rotated (e.g. when the screen has been turned | |
195 // 90 degrees), immediately prior to layout() being called for the | |
196 // new dimensions, rotate() is called with the old and new angles. | |
197 // The next time paint() is called, the coordinate space will have | |
198 // been rotated N quarter-turns clockwise, where: | |
199 // N = newAngle-oldAngle | |
200 // ...but the rendering is expected to remain the same, pixel for | |
201 // pixel, on the output device. Then, the layout() method or | |
202 // equivalent will be invoked. | |
203 | |
204 void rotate({ | |
205 int oldAngle, // 0..3 | |
206 int newAngle, // 0..3 | |
207 Duration time | |
208 }) { } | |
209 | |
210 | |
211 // HIT TESTING | |
212 | |
213 void hitTest(double x, double y, List<RenderNode> targets) { | |
214 // override this if you have children | |
215 // if any of your children cover x,y, call the top-most such | |
216 // child's hitTest(). | |
217 // then, call this superclass hitTest(). | |
218 targets.add(this); | |
219 } | |
220 | |
221 | |
222 // PAINTING | |
223 | |
224 PaintOptions paintOptions; | |
225 DisplayList _cachedPaint; | |
226 // only set this to a subclass of PaintOptions that this class of RenderNode k nows how to deal with | |
227 | |
228 static List<RenderNode> _nodesNeedingPaint = new List<RenderNode>(); | |
229 static bool _debugDoingPaint = false; | |
230 bool _needsPaint = true; | |
231 bool get needsPaint => _needsPaint; | |
232 void markNeedsPaint() { | |
233 assert(!_debugDoingPaint); | |
234 if (_needsPaint) return; | |
235 _needsPaint = true; | |
236 _nodesNeedingPaint.add(this); | |
237 } | |
238 static void flushPaint() { | |
239 List<RenderNode> dirtyNodes = _nodesNeedingPaint; | |
240 _nodesNeedingPaint = new List<RenderNode>(); | |
241 dirtyNodes..sort((a, b) => a.order - b.order)..forEach((node) { | |
242 if (node._needsPaint && node.attached) | |
243 node._doPaint(); | |
244 }); | |
245 } | |
246 void _doPaint() { | |
247 assert(!_needsLayout); | |
248 DisplayList newPaint = new DisplayList(); | |
249 _needsPaint = false; | |
250 try { | |
251 paint(newPaint); | |
252 } catch (e, stack) { | |
253 print('Exception raised during paint of ${this}: ${e}'); | |
254 print(stack); | |
255 return; | |
256 } | |
257 assert(!_needsLayout); // check that the paint() method didn't mark us dirty again | |
258 assert(!_needsPaint); // check that the paint() method didn't mark us dirty again | |
259 if (newPaint._children != null) | |
260 newPaint._children.forEach((node) { | |
261 assert(node.attached == attached); | |
262 if (node._needsPaint) | |
263 node._doPaint(); | |
264 }); | |
265 _cachedPaint = newPaint; | |
266 } | |
267 | |
268 void paint(DisplayList canvas) { } | |
269 | |
270 } | |
271 | |
272 | |
273 // GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN | |
274 | |
275 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
276 ChildType previousSibling; | |
277 ChildType nextSibling; | |
278 void detachSiblings() { | |
279 if (previousSibling != null) { | |
280 assert(previousSibling.pos is ContainerParentDataMixin<ChildType>); | |
281 previousSibling.pos.nextSibling = nextSibling; | |
282 } | |
283 if (nextSibling != null) { | |
284 assert(nextSibling.pos is ContainerParentDataMixin<ChildType>); | |
285 nextSibling.pos.previousSibling = previousSibling; | |
286 } | |
287 previousSibling = null; | |
288 nextSibling = null; | |
289 } | |
290 } | |
291 | |
292 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData Type extends ContainerParentDataMixin<ChildType>> { | |
293 // abstract class that has only InlineNode children | |
294 | |
295 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
296 assert(child.pos is ParentDataType); | |
297 while (child.pos.previousSibling != null) { | |
298 child = child.pos.previousSibling; | |
299 assert(child.pos is ParentDataType); | |
300 } | |
301 return child == equals; | |
302 } | |
303 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
304 assert(child.pos is ParentDataType); | |
305 while (child.pos.nextSibling != null) { | |
306 child = child.pos.nextSibling; | |
307 assert(child.pos is ParentDataType); | |
308 } | |
309 return child == equals; | |
310 } | |
311 | |
312 ChildType _firstChild; | |
313 ChildType _lastChild; | |
314 void add(ChildType child, { ChildType before }) { | |
315 setAsChild(child); | |
316 assert(child.pos is ParentDataType); | |
317 if (before == null) { | |
318 // append at the end (_lastChild) | |
319 child.pos.previousSibling = _lastChild; | |
320 if (_lastChild != null) { | |
321 assert(_lastChild.pos is ParentDataType); | |
322 _lastChild.pos.nextSibling = child; | |
323 } | |
324 _lastChild = child; | |
325 if (_firstChild == null) | |
326 _firstChild = child; | |
327 } else { | |
328 assert(_firstChild != null); | |
329 assert(_lastChild != null); | |
330 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
331 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
332 assert(before.pos is ParentDataType); | |
333 if (before.pos.previousSibling == null) { | |
334 // insert at the start (_firstChild); we'll end up with two or more chil dren | |
335 assert(before == _firstChild); | |
336 child.pos.nextSibling = before; | |
337 before.pos.previousSibling = child; | |
338 _firstChild = child; | |
339 } else { | |
340 // insert in the middle; we'll end up with three or more children | |
341 // set up links from child to siblings | |
342 child.pos.previousSibling = before.pos.previousSibling; | |
343 child.pos.nextSibling = before; | |
344 // set up links from siblings to child | |
345 assert(child.pos.previousSibling.pos is ParentDataType); | |
346 assert(child.pos.nextSibling.pos is ParentDataType); | |
347 child.pos.previousSibling.pos.nextSibling = child; | |
348 child.pos.nextSibling.pos.previousSibling = child; | |
349 } | |
350 } | |
351 markNeedsLayout(); | |
352 } | |
353 void remove(ChildType child) { | |
354 assert(child.pos is ParentDataType); | |
355 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
356 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
357 if (child.pos.previousSibling == null) { | |
358 _firstChild = child.pos.nextSibling; | |
359 } else { | |
360 assert(child.pos.previousSibling.pos is ParentDataType); | |
361 child.pos.previousSibling.pos.nextSibling = child.pos.nextSibling; | |
362 } | |
363 if (child.pos.nextSibling == null) { | |
364 _lastChild = child.pos.previousSibling; | |
365 } else { | |
366 assert(child.pos.nextSibling.pos is ParentDataType); | |
367 child.pos.nextSibling.pos.previousSibling = child.pos.previousSibling; | |
368 } | |
369 dropChild(child); | |
370 markNeedsLayout(); | |
371 } | |
372 void reorderChildren() { | |
373 ChildType child = _firstChild; | |
374 while (child != null) { | |
375 reorderChild(child); | |
376 assert(child.pos is ParentDataType); | |
377 child = child.pos.nextSibling; | |
378 } | |
379 } | |
380 void attachChildren() { | |
381 ChildType child = _firstChild; | |
382 while (child != null) { | |
383 child.attach(); | |
384 assert(child.pos is ParentDataType); | |
385 child = child.pos.nextSibling; | |
386 } | |
387 } | |
388 void detachChildren() { | |
389 ChildType child = _firstChild; | |
390 while (child != null) { | |
391 child.detach(); | |
392 assert(child.pos is ParentDataType); | |
393 child = child.pos.nextSibling; | |
394 } | |
395 } | |
396 | |
397 } | |
398 | |
399 | |
400 // GENERIC BOX RENDERING | |
401 // Anything that has a concept of x, y, width, height is going to derive from th is | |
402 | |
403 class BoxDimensions { | |
404 const BoxDimensions({this.width, this.height}); | |
405 final double width; | |
406 final double height; | |
407 } | |
408 | |
409 class BoxParentData extends ParentData { | |
410 double x = 0.0; | |
411 double y = 0.0; | |
412 } | |
413 | |
414 abstract class RenderBox extends RenderNode { | |
415 | |
416 void setupPos(RenderNode child) { | |
417 if (child.pos is! BoxParentData) | |
418 child.pos = new BoxParentData(); | |
419 } | |
420 | |
421 // override this to report what dimensions you would have if you | |
422 // were laid out with the given constraints this can walk the tree | |
423 // if it must, but it should be as cheap as possible; just get the | |
424 // dimensions and nothing else (e.g. don't calculate hypothetical | |
425 // child positions if they're not needed to determine dimensions) | |
426 BoxDimensions getIntrinsicDimensions({ | |
427 double minWidth: 0.0, | |
428 double maxWidth: double.INFINITY, | |
429 double minHeight: 0.0, | |
430 double maxHeight: double.INFINITY | |
431 }) { | |
432 return new BoxDimensions( | |
433 width: clamp(min: minWidth, max: maxWidth), | |
434 height: clamp(min: minHeight, max: maxHeight) | |
435 ); | |
436 } | |
437 | |
438 void layout({ | |
439 double minWidth: 0.0, | |
440 double maxWidth: double.INFINITY, | |
441 double minHeight: 0.0, | |
442 double maxHeight: double.INFINITY, | |
443 RenderNode relayoutSubtreeRoot | |
444 }) { | |
445 width = clamp(min: minWidth, max: maxWidth); | |
446 height = clamp(min: minHeight, max: maxHeight); | |
447 layoutDone(); | |
448 } | |
449 | |
450 double width; | |
451 double height; | |
452 | |
453 void rotate({ | |
454 int oldAngle, // 0..3 | |
455 int newAngle, // 0..3 | |
456 Duration time | |
457 }) { } | |
458 | |
459 } | |
460 | |
461 | |
462 // SCREEN LAYOUT MANAGER | |
463 | |
464 class Screen extends RenderNode { | |
465 | |
466 Screen({ | |
467 RenderBox root, | |
468 this.timeForRotation: const Duration(microseconds: 83333) | |
469 }) { | |
470 assert(root != null); | |
471 this.root = root; | |
472 } | |
473 | |
474 double _width; | |
475 double get width => _width; | |
476 double _height; | |
477 double get height => _height; | |
478 | |
479 int _orientation; // 0..3 | |
480 int get orientation => _orientation; | |
481 Duration timeForRotation; | |
482 | |
483 RenderBox _root; | |
484 RenderBox get root => _root; | |
485 void set root (RenderBox value) { | |
486 assert(root != null); | |
487 _root = value; | |
488 setAsChild(_root); | |
489 markNeedsLayout(); | |
490 } | |
491 | |
492 void layout({ | |
493 double newWidth, | |
494 double newHeight, | |
495 int newOrientation | |
496 }) { | |
497 assert(root != null); | |
498 if (newOrientation != orientation) { | |
499 if (orientation != null) | |
500 root.rotate(oldAngle: orientation, newAngle: newOrientation, time: timeF orRotation); | |
501 _orientation = newOrientation; | |
502 } | |
503 if ((newWidth != width) || (newHeight != height)) { | |
504 _width = newWidth; | |
505 _height = newHeight; | |
506 relayout(); | |
507 } | |
508 } | |
509 | |
510 void relayout() { | |
511 assert(root != null); | |
512 root.layout( | |
513 minWidth: width, | |
514 maxWidth: width, | |
515 minHeight: height, | |
516 maxHeight: height | |
517 ); | |
518 assert(root.width == width); | |
519 assert(root.height == height); | |
520 } | |
521 | |
522 void rotate({ int oldAngle, int newAngle, Duration time }) { | |
523 assert(false); // nobody tells the screen to rotate, the whole rotate() danc e is started from our layout() | |
524 } | |
525 | |
526 void paint(DisplayList canvas) { | |
527 canvas.paintChild(root, 0.0, 0.0); | |
528 } | |
529 | |
530 } | |
531 | |
532 | |
533 // BLOCK LAYOUT MANAGER | |
534 | |
535 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render Box> { } | |
536 | |
537 class BlockBox extends RenderBox with ContainerRenderNodeMixin<RenderBox, BlockP arentData> { | |
538 // lays out RenderBox children in a vertical stack | |
539 // uses the maximum width provided by the parent | |
540 // sizes itself to the height of its child stack | |
541 | |
542 BlockBox({ | |
543 EdgeDims padding: const EdgeDims(0.0, 0.0, 0.0, 0.0) | |
544 }) { | |
545 _padding = padding; | |
546 } | |
547 | |
548 EdgeDims _padding; | |
549 EdgeDims get padding => _padding; | |
550 void set padding(EdgeDims value) { | |
551 assert(value != null); | |
552 if (_padding != value) { | |
553 _padding = value; | |
554 markNeedsLayout(); | |
555 } | |
556 } | |
557 | |
558 void setupPos(RenderBox child) { | |
559 if (child.pos is! BlockParentData) | |
560 child.pos = new BlockParentData(); | |
561 } | |
562 | |
563 // override this to report what dimensions you would have if you | |
564 // were laid out with the given constraints this can walk the tree | |
565 // if it must, but it should be as cheap as possible; just get the | |
566 // dimensions and nothing else (e.g. don't calculate hypothetical | |
567 // child positions if they're not needed to determine dimensions) | |
568 BoxDimensions getIntrinsicDimensions({ | |
569 double minWidth: 0.0, | |
570 double maxWidth: double.INFINITY, | |
571 double minHeight: 0.0, | |
572 double maxHeight: double.INFINITY | |
573 }) { | |
574 double outerHeight = _padding.top + _padding.bottom; | |
575 double outerWidth = clamp(min: minWidth, max: maxWidth); | |
576 double innerWidth = outerWidth - (_padding.left + _padding.right); | |
577 RenderBox child = _firstChild; | |
578 while (child != null) { | |
579 outerHeight += child.getIntrinsicDimensions(minWidth: innerWidth, maxWidth : innerWidth).height; | |
580 assert(child.pos is BlockParentData); | |
581 child = child.pos.nextSibling; | |
582 } | |
583 return new BoxDimensions( | |
584 width: outerWidth, | |
585 height: clamp(min: minHeight, max: maxHeight, value: outerHeight) | |
586 ); | |
587 } | |
588 | |
589 double _minHeight; // value cached from parent for relayout call | |
590 double _maxHeight; // value cached from parent for relayout call | |
591 void layout({ | |
592 double minWidth: 0.0, | |
593 double maxWidth: double.INFINITY, | |
594 double minHeight: 0.0, | |
595 double maxHeight: double.INFINITY, | |
596 RenderNode relayoutSubtreeRoot | |
597 }) { | |
598 if (relayoutSubtreeRoot != null) | |
599 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); | |
600 relayoutSubtreeRoot = relayoutSubtreeRoot == null ? this : relayoutSubtreeRo ot; | |
601 width = clamp(min: minWidth, max: maxWidth); | |
602 _minHeight = minHeight; | |
603 _maxHeight = maxHeight; | |
604 internalLayout(relayoutSubtreeRoot); | |
605 } | |
606 | |
607 void relayout() { | |
608 internalLayout(this); | |
609 } | |
610 | |
611 void internalLayout(RenderNode relayoutSubtreeRoot) { | |
612 assert(_minHeight != null); | |
613 assert(_maxHeight != null); | |
614 double y = _padding.top; | |
615 double innerWidth = width - (_padding.left + _padding.right); | |
616 RenderBox child = _firstChild; | |
617 while (child != null) { | |
618 child.layout(minWidth: innerWidth, maxWidth: innerWidth, relayoutSubtreeRo ot: relayoutSubtreeRoot); | |
619 assert(child.pos is BlockParentData); | |
620 child.pos.x = 0.0; | |
621 child.pos.y = y; | |
622 y += child.height; | |
623 child = child.pos.nextSibling; | |
624 } | |
625 height = clamp(min: _minHeight, value: y + _padding.bottom, max: _maxHeight) ; | |
626 layoutDone(); | |
627 } | |
628 | |
629 void hitTest(double x, double y, List<RenderNode> targets) { | |
630 RenderBox child = _lastChild; | |
631 while (child != null) { | |
632 assert(child.pos is BlockParentData); | |
633 if ((x >= child.pos.x) && (x < child.pos.x + child.width) && | |
634 (y >= child.pos.y) && (y < child.pos.y + child.height)) { | |
635 child.hitTest(x, y, targets); | |
636 break; | |
637 } | |
638 child = child.pos.previousSibling; | |
639 } | |
640 super.hitTest(x, y, targets); | |
641 } | |
642 | |
643 void paint(DisplayList canvas) { | |
644 RenderBox child = _firstChild; | |
645 while (child != null) { | |
646 assert(child.pos is BlockParentData); | |
647 canvas.paintChild(child, child.pos.x, child.pos.y); | |
648 child = child.pos.nextSibling; | |
649 } | |
650 } | |
651 | |
652 } | |
653 | |
654 | |
655 // PARAGRAPH LAYOUT MANAGER | |
656 | |
657 class InlineParentData extends BoxParentData with ContainerParentDataMixin<Inlin eNode> { } | |
658 | |
659 class ParagraphBox extends RenderBox with ContainerRenderNodeMixin<InlineNode, I nlineParentData> { | |
660 | |
661 void setupPos(InlineNode child) { | |
662 if (child.pos is! InlineParentData) | |
663 child.pos = new InlineParentData(); | |
664 } | |
665 | |
666 BoxDimensions getIntrinsicDimensions({ | |
667 double minWidth: 0.0, | |
668 double maxWidth: double.INFINITY, | |
669 double minHeight: 0.0, | |
670 double maxHeight: double.INFINITY | |
671 }) { | |
672 // ...compute intrinsic dimension given these constraints... | |
673 } | |
674 | |
675 double _minWidth; // value cached from parent for relayout call | |
676 double _maxWidth; // value cached from parent for relayout call | |
677 double _minHeight; // value cached from parent for relayout call | |
678 double _maxHeight; // value cached from parent for relayout call | |
679 void layout({ | |
680 double minWidth: 0.0, | |
681 double maxWidth: double.INFINITY, | |
682 double minHeight: 0.0, | |
683 double maxHeight: double.INFINITY, | |
684 RenderNode relayoutSubtreeRoot | |
685 }) { | |
686 if (relayoutSubtreeRoot != null) | |
687 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); | |
688 relayoutSubtreeRoot = relayoutSubtreeRoot == null ? this : relayoutSubtreeRo ot; | |
689 _minWidth = minWidth; | |
690 _maxWidth = maxWidth; | |
691 _minHeight = minHeight; | |
692 _maxHeight = maxHeight; | |
693 internalLayout(relayoutSubtreeRoot); | |
694 layoutDone(); | |
695 } | |
696 | |
697 void relayout() { | |
698 internalLayout(this); | |
699 layoutDone(); | |
700 } | |
701 | |
702 external void internalLayout(RenderNode relayoutSubtreeRoot); | |
703 external void hitTest(double x, double y, List<RenderNode> targets); | |
704 external void paint(DisplayList canvas); | |
705 | |
706 } | |
707 | |
708 class InlineNode extends RenderNode with ContainerRenderNodeMixin<InlineNode, In lineParentData> { | |
709 // ... | |
710 } | |
711 | |
712 | |
713 // InlineBox wraps a RenderBox, i.e. it's a box that fits into inline | |
714 // layout without breaking into multiple lines. This allows you to put | |
715 // images, form controls, inline blocks, etc, into paragraphs | |
716 class InlineBox extends InlineNode { | |
717 InlineBox(this.child); | |
718 final RenderBox child; | |
719 | |
720 // ... | |
721 } | |
722 | |
723 | |
724 // SCAFFOLD LAYOUT MANAGER | |
725 | |
726 // a sample special-purpose layout manager | |
727 | |
728 class ScaffoldBox extends RenderBox { | |
729 | |
730 ScaffoldBox(this.toolbar, this.body, this.statusbar, this.drawer) { | |
731 assert(body != null); | |
732 } | |
733 | |
734 final RenderBox toolbar; | |
735 final RenderBox body; | |
736 final RenderBox statusbar; | |
737 final RenderBox drawer; | |
738 | |
739 void layout({ | |
740 double minWidth: 0.0, | |
741 double maxWidth: double.INFINITY, | |
742 double minHeight: 0.0, | |
743 double maxHeight: double.INFINITY, | |
744 RenderNode relayoutSubtreeRoot | |
745 }) { | |
746 width = clamp(min: minWidth, max: maxWidth); | |
747 height = clamp(min: minHeight, max: maxHeight); | |
748 relayout(); | |
749 } | |
750 | |
751 static const kToolbarHeight = 100.0; | |
752 static const kStatusbarHeight = 50.0; | |
753 | |
754 void relayout() { | |
755 double bodyHeight = height; | |
756 if (toolbar != null) { | |
757 toolbar.layout(minWidth: width, maxWidth: width, minHeight: kToolbarHeight , maxHeight: kToolbarHeight); | |
758 assert(toolbar.pos is BoxParentData); | |
759 toolbar.pos.x = 0.0; | |
760 toolbar.pos.y = 0.0; | |
761 bodyHeight -= kToolbarHeight; | |
762 } | |
763 if (statusbar != null) { | |
764 statusbar.layout(minWidth: width, maxWidth: width, minHeight: kStatusbarHe ight, maxHeight: kStatusbarHeight); | |
765 assert(statusbar.pos is BoxParentData); | |
766 statusbar.pos.x = 0.0; | |
767 statusbar.pos.y = height - kStatusbarHeight; | |
768 bodyHeight -= kStatusbarHeight; | |
769 } | |
770 body.layout(minWidth: width, maxWidth: width, minHeight: bodyHeight, maxHeig ht: bodyHeight); | |
771 if (drawer != null) | |
772 drawer.layout(minWidth: 0.0, maxWidth: width, minHeight: height, maxHeight : height); | |
773 layoutDone(); | |
774 } | |
775 | |
776 void hitTest(double x, double y, List<RenderNode> targets) { | |
777 if ((drawer != null) && (x < drawer.width)) { | |
778 drawer.hitTest(x, y, targets); | |
779 } else if ((toolbar != null) && (y < toolbar.height)) { | |
780 toolbar.hitTest(x, y, targets); | |
781 } else if ((statusbar != null) && (y > (statusbar.pos as BoxParentData).y)) { | |
782 statusbar.hitTest(x, y, targets); | |
783 } else { | |
784 body.hitTest(x, y, targets); | |
785 } | |
786 super.hitTest(x, y, targets); | |
787 } | |
788 | |
789 void paint(DisplayList canvas) { | |
790 canvas.paintChild(body, (body.pos as BoxParentData).x, (body.pos as BoxParen tData).y); | |
791 if (statusbar != null) | |
792 canvas.paintChild(statusbar, (statusbar.pos as BoxParentData).x, (statusba r.pos as BoxParentData).y); | |
793 if (toolbar != null) | |
794 canvas.paintChild(toolbar, (toolbar.pos as BoxParentData).x, (toolbar.pos as BoxParentData).y); | |
795 if (drawer != null) | |
796 canvas.paintChild(drawer, (drawer.pos as BoxParentData).x, (drawer.pos as BoxParentData).y); | |
797 } | |
798 | |
799 } | |
OLD | NEW |