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); | |
ojan
2015/04/23 20:28:32
Why add the children to a list instead of calling
Hixie
2015/04/23 20:45:13
If it's not dirty, you don't need to paint it. We
ojan
2015/04/23 21:12:36
Extra lists == moar slow. So we would only want to
| |
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; | |
ojan
2015/04/23 20:28:32
FYI, this is dead
Hixie
2015/04/23 20:45:13
Acknowledged.
| |
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(); | |
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 (previousSibling.pos as ContainerParentDataMixin<ChildType>).nextSibling = nextSibling; | |
281 if (nextSibling != null) | |
282 (nextSibling.pos as ContainerParentDataMixin<ChildType>).previousSibling = previousSibling; | |
283 previousSibling = null; | |
284 nextSibling = null; | |
285 } | |
286 } | |
287 | |
288 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData Type extends ContainerParentDataMixin<ChildType>> { | |
289 // abstract class that has only InlineNode children | |
290 | |
291 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
292 assert(child.pos is ParentDataType); | |
293 while ((child.pos as ParentDataType).previousSibling != null) { | |
294 child = (child.pos as ParentDataType).previousSibling; | |
295 assert(child.pos is ParentDataType); | |
296 } | |
297 return child == equals; | |
298 } | |
299 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
300 assert(child.pos is ParentDataType); | |
301 while ((child.pos as ParentDataType).nextSibling != null) { | |
302 child = (child.pos as ParentDataType).nextSibling; | |
303 assert(child.pos is ParentDataType); | |
304 } | |
305 return child == equals; | |
306 } | |
307 | |
308 ChildType _firstChild; | |
309 ChildType _lastChild; | |
310 void add(ChildType child, { ChildType before }) { | |
311 setAsChild(child); | |
312 if (before == null) { | |
313 // append at the end (_lastChild) | |
314 (child.pos as ParentDataType).previousSibling = _lastChild; | |
315 if (_lastChild != null) | |
316 (_lastChild.pos as ParentDataType).nextSibling = child; | |
317 (child.pos as ParentDataType).previousSibling = _lastChild; | |
318 _lastChild = child; | |
319 if (_firstChild == null) | |
320 _firstChild = child; | |
321 } else { | |
322 assert(_firstChild != null); | |
323 assert(_lastChild != null); | |
324 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
325 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
326 if ((before.pos as ParentDataType).previousSibling == null) { | |
327 // insert at the start (_firstChild); there's two or more children | |
328 assert(before == _firstChild); | |
329 (child.pos as ParentDataType).nextSibling = before; | |
330 (before.pos as ParentDataType).previousSibling = child; | |
331 _firstChild = child; | |
332 } else { | |
333 // insert in the middle; there's two or more children | |
334 // set up links from child to siblings | |
335 (child.pos as ParentDataType).previousSibling = (before.pos as ParentDat aType).previousSibling; | |
336 (child.pos as ParentDataType).nextSibling = before; | |
337 // set up links from siblings to child | |
338 ((child.pos as ParentDataType).previousSibling.pos as ParentDataType).ne xtSibling = child; | |
339 ((child.pos as ParentDataType).nextSibling.pos as ParentDataType).previo usSibling = child; | |
340 } | |
341 } | |
342 markNeedsLayout(); | |
343 } | |
344 void remove(ChildType child) { | |
345 assert(child.pos is ParentDataType); | |
346 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
347 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
348 if ((child.pos as ParentDataType).previousSibling == null) | |
349 _firstChild = (child.pos as ParentDataType).nextSibling; | |
350 else | |
351 ((child.pos as ParentDataType).previousSibling.pos as ParentDataType).next Sibling = (child.pos as ParentDataType).nextSibling; | |
352 if ((child.pos as ParentDataType).nextSibling == null) | |
353 _lastChild = (child.pos as ParentDataType).previousSibling; | |
354 else | |
355 ((child.pos as ParentDataType).nextSibling.pos as ParentDataType).previous Sibling = (child.pos as ParentDataType).previousSibling; | |
356 dropChild(child); | |
357 markNeedsLayout(); | |
358 } | |
359 void reorderChildren() { | |
360 ChildType child = _firstChild; | |
361 while (child != null) { | |
362 reorderChild(child); | |
363 child = (child.pos as ParentDataType).nextSibling; | |
364 } | |
365 } | |
366 void attachChildren() { | |
367 ChildType child = _firstChild; | |
368 while (child != null) { | |
369 child.attach(); | |
370 child = (child.pos as ParentDataType).nextSibling; | |
371 } | |
372 } | |
373 void detachChildren() { | |
374 ChildType child = _firstChild; | |
375 while (child != null) { | |
376 child.detach(); | |
377 child = (child.pos as ParentDataType).nextSibling; | |
378 } | |
379 } | |
380 | |
381 } | |
382 | |
383 | |
384 // GENERIC BOX RENDERING | |
385 // Anything that has a concept of x, y, width, height is going to derive from th is | |
386 | |
387 class BoxDimensions { | |
388 const BoxDimensions({this.width, this.height}); | |
389 final double width; | |
390 final double height; | |
391 } | |
392 | |
393 class BoxParentData extends ParentData { | |
394 double x = 0.0; | |
395 double y = 0.0; | |
396 } | |
397 | |
398 abstract class RenderBox extends RenderNode { | |
399 | |
400 void setupPos(RenderNode child) { | |
401 if (child.pos is! BoxParentData) | |
402 child.pos = new BoxParentData(); | |
403 } | |
404 | |
405 // override this to report what dimensions you would have if you | |
406 // were laid out with the given constraints this can walk the tree | |
407 // if it must, but it should be as cheap as possible; just get the | |
408 // dimensions and nothing else (e.g. don't calculate hypothetical | |
409 // child positions if they're not needed to determine dimensions) | |
410 BoxDimensions getIntrinsicDimensions({ | |
411 double minWidth: 0.0, | |
412 double maxWidth: double.INFINITY, | |
413 double minHeight: 0.0, | |
414 double maxHeight: double.INFINITY | |
415 }) { | |
416 return new BoxDimensions( | |
417 width: clamp(min: minWidth, max: maxWidth), | |
418 height: clamp(min: minHeight, max: maxHeight) | |
419 ); | |
420 } | |
421 | |
422 void layout({ | |
423 double minWidth: 0.0, | |
424 double maxWidth: double.INFINITY, | |
425 double minHeight: 0.0, | |
426 double maxHeight: double.INFINITY, | |
427 RenderNode relayoutSubtreeRoot | |
428 }) { | |
429 width = clamp(min: minWidth, max: maxWidth); | |
430 height = clamp(min: minHeight, max: maxHeight); | |
431 layoutDone(); | |
432 } | |
433 | |
434 double width; | |
435 double height; | |
436 | |
437 void rotate({ | |
438 int oldAngle, // 0..3 | |
439 int newAngle, // 0..3 | |
440 Duration time | |
441 }) { } | |
442 | |
443 } | |
444 | |
445 | |
446 // SCREEN LAYOUT MANAGER | |
447 | |
448 class Screen extends RenderNode { | |
449 | |
450 Screen({ | |
451 RenderBox root, | |
452 this.timeForRotation: const Duration(microseconds: 83333) | |
453 }) { | |
454 assert(root != null); | |
455 this.root = root; | |
456 } | |
457 | |
458 double _width; | |
459 double get width => _width; | |
460 double _height; | |
461 double get height => _height; | |
462 | |
463 int _orientation; // 0..3 | |
464 int get orientation => _orientation; | |
465 Duration timeForRotation; | |
466 | |
467 RenderBox _root; | |
468 RenderBox get root => _root; | |
469 void set root (RenderBox value) { | |
470 assert(root != null); | |
471 _root = value; | |
472 setAsChild(_root); | |
473 markNeedsLayout(); | |
474 } | |
475 | |
476 void layout({ | |
477 double newWidth, | |
478 double newHeight, | |
479 int newOrientation | |
480 }) { | |
481 assert(root != null); | |
482 if (newOrientation != orientation) { | |
483 if (orientation != null) | |
484 root.rotate(oldAngle: orientation, newAngle: newOrientation, time: timeF orRotation); | |
485 _orientation = newOrientation; | |
486 } | |
487 if ((newWidth != width) || (newHeight != height)) { | |
488 _width = newWidth; | |
489 _height = newHeight; | |
490 relayout(); | |
491 } | |
492 } | |
493 | |
494 void relayout() { | |
495 assert(root != null); | |
496 root.layout( | |
497 minWidth: width, | |
498 maxWidth: width, | |
499 minHeight: height, | |
500 maxHeight: height | |
501 ); | |
502 assert(root.width == width); | |
503 assert(root.height == height); | |
504 } | |
505 | |
506 void rotate({ int oldAngle, int newAngle, Duration time }) { | |
507 assert(false); // nobody tells the screen to rotate, the whole rotate() danc e is started from our layout() | |
508 } | |
509 | |
510 void paint(DisplayList canvas) { | |
511 canvas.paintChild(root, 0, 0); | |
512 } | |
513 | |
514 } | |
515 | |
516 | |
517 // BLOCK LAYOUT MANAGER | |
518 | |
519 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render Box> { } | |
520 | |
521 class BlockBox extends RenderBox with ContainerRenderNodeMixin<RenderBox, BlockP arentData> { | |
522 // lays out RenderBox children in a vertical stack | |
523 // uses the maximum width provided by the parent | |
524 // sizes itself to the height of its child stack | |
525 | |
526 BlockBox({ | |
527 EdgeDims padding: const EdgeDims(0.0, 0.0, 0.0, 0.0) | |
528 }) { | |
529 _padding = padding; | |
530 } | |
531 | |
532 EdgeDims _padding; | |
533 EdgeDims get padding => _padding; | |
534 void set padding(EdgeDims value) { | |
535 assert(value != null); | |
536 if (_padding != value) { | |
537 _padding = value; | |
538 markNeedsLayout(); | |
539 } | |
540 } | |
541 | |
542 void setupPos(RenderBox child) { | |
543 if (child.pos is! BlockParentData) | |
544 child.pos = new BlockParentData(); | |
545 } | |
546 | |
547 // override this to report what dimensions you would have if you | |
548 // were laid out with the given constraints this can walk the tree | |
549 // if it must, but it should be as cheap as possible; just get the | |
550 // dimensions and nothing else (e.g. don't calculate hypothetical | |
551 // child positions if they're not needed to determine dimensions) | |
552 BoxDimensions getIntrinsicDimensions({ | |
553 double minWidth: 0.0, | |
554 double maxWidth: double.INFINITY, | |
555 double minHeight: 0.0, | |
556 double maxHeight: double.INFINITY | |
557 }) { | |
558 double outerHeight = _padding.top + _padding.bottom; | |
559 double outerWidth = clamp(min: minWidth, max: maxWidth); | |
560 double innerWidth = outerWidth - (_padding.left + _padding.right); | |
561 RenderBox child = _firstChild; | |
562 while (child != null) { | |
563 outerHeight += child.getIntrinsicDimensions(minWidth: innerWidth, maxWidth : innerWidth).height; | |
564 child = (child.pos as BlockParentData).nextSibling; | |
565 } | |
566 return new BoxDimensions( | |
567 width: outerWidth, | |
568 height: clamp(min: minHeight, max: maxHeight, value: outerHeight) | |
569 ); | |
570 } | |
571 | |
572 double _minHeight; // value cached from parent for relayout call | |
573 double _maxHeight; // value cached from parent for relayout call | |
574 void layout({ | |
575 double minWidth: 0.0, | |
576 double maxWidth: double.INFINITY, | |
577 double minHeight: 0.0, | |
578 double maxHeight: double.INFINITY, | |
579 RenderNode relayoutSubtreeRoot | |
580 }) { | |
581 if (relayoutSubtreeRoot != null) | |
582 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); | |
583 relayoutSubtreeRoot = relayoutSubtreeRoot == null ? this : relayoutSubtreeRo ot; | |
584 width = clamp(min: minWidth, max: maxWidth); | |
585 _minHeight = minHeight; | |
586 _maxHeight = maxHeight; | |
587 internalLayout(relayoutSubtreeRoot); | |
588 } | |
589 | |
590 void relayout() { | |
591 internalLayout(this); | |
592 } | |
593 | |
594 void internalLayout(RenderNode relayoutSubtreeRoot) { | |
595 assert(_minHeight != null); | |
596 assert(_maxHeight != null); | |
597 double y = _padding.top; | |
598 double innerWidth = width - (_padding.left + _padding.right); | |
599 RenderBox child = _firstChild; | |
600 while (child != null) { | |
601 child.layout(minWidth: innerWidth, maxWidth: innerWidth, relayoutSubtreeRo ot: relayoutSubtreeRoot); | |
602 assert(child.pos is BlockParentData); | |
603 (child.pos as BlockParentData).x = 0.0; | |
604 (child.pos as BlockParentData).y = y; | |
605 y += child.height; | |
606 child = (child.pos as BlockParentData).nextSibling; | |
607 } | |
608 height = clamp(min: _minHeight, value: y + _padding.bottom, max: _maxHeight) ; | |
609 layoutDone(); | |
610 } | |
611 | |
612 void hitTest(double x, double y, List<RenderNode> targets) { | |
613 RenderBox child = _lastChild; | |
614 while (child != null) { | |
615 if ((x >= (child.pos as BlockParentData).x) && (x < (child.pos as BlockPar entData).x + child.width) && | |
616 (y >= (child.pos as BlockParentData).y) && (y < (child.pos as BlockPar entData).y + child.height)) { | |
617 child.hitTest(x, y, targets); | |
618 break; | |
619 } | |
620 child = (child.pos as BlockParentData).previousSibling; | |
621 } | |
622 super.hitTest(x, y, targets); | |
623 } | |
624 | |
625 void paint(DisplayList canvas) { | |
626 RenderBox child = _firstChild; | |
627 while (child != null) { | |
628 canvas.paintChild(child, (child.pos as BlockParentData).x, (child.pos as B lockParentData).y); | |
629 child = (child.pos as BlockParentData).nextSibling; | |
630 } | |
631 } | |
632 | |
633 } | |
634 | |
635 | |
636 // PARAGRAPH LAYOUT MANAGER | |
637 | |
638 class InlineParentData extends BoxParentData with ContainerParentDataMixin<Inlin eNode> { } | |
639 | |
640 class ParagraphBox extends RenderBox with ContainerRenderNodeMixin<InlineNode, I nlineParentData> { | |
641 | |
642 void setupPos(InlineNode child) { | |
643 if (child.pos is! InlineParentData) | |
644 child.pos = new InlineParentData(); | |
645 } | |
646 | |
647 BoxDimensions getIntrinsicDimensions({ | |
648 double minWidth: 0.0, | |
649 double maxWidth: double.INFINITY, | |
650 double minHeight: 0.0, | |
651 double maxHeight: double.INFINITY | |
652 }) { | |
653 // ...compute intrinsic dimension given these constraints... | |
654 } | |
655 | |
656 double _minWidth; // value cached from parent for relayout call | |
657 double _maxWidth; // value cached from parent for relayout call | |
658 double _minHeight; // value cached from parent for relayout call | |
659 double _maxHeight; // value cached from parent for relayout call | |
660 void layout({ | |
661 double minWidth: 0.0, | |
662 double maxWidth: double.INFINITY, | |
663 double minHeight: 0.0, | |
664 double maxHeight: double.INFINITY, | |
665 RenderNode relayoutSubtreeRoot | |
666 }) { | |
667 if (relayoutSubtreeRoot != null) | |
668 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); | |
669 relayoutSubtreeRoot = relayoutSubtreeRoot == null ? this : relayoutSubtreeRo ot; | |
670 _minWidth = minWidth; | |
671 _maxWidth = maxWidth; | |
672 _minHeight = minHeight; | |
673 _maxHeight = maxHeight; | |
674 internalLayout(relayoutSubtreeRoot); | |
675 layoutDone(); | |
676 } | |
677 | |
678 void relayout() { | |
679 internalLayout(this); | |
680 layoutDone(); | |
681 } | |
682 | |
683 external void internalLayout(RenderNode relayoutSubtreeRoot); | |
684 external void hitTest(double x, double y, List<RenderNode> targets); | |
685 external void paint(DisplayList canvas); | |
686 | |
687 } | |
688 | |
689 class InlineNode extends RenderNode with ContainerRenderNodeMixin<InlineNode, In lineParentData> { | |
690 // ... | |
691 } | |
692 | |
693 | |
694 // InlineBox wraps a RenderBox, i.e. it's a box that fits into inline | |
695 // layout without breaking into multiple lines. This allows you to put | |
696 // images, form controls, inline blocks, etc, into paragraphs | |
697 class InlineBox extends InlineNode { | |
698 InlineBox(this.child); | |
699 final RenderBox child; | |
700 | |
701 // ... | |
702 } | |
703 | |
704 | |
705 // SCAFFOLD LAYOUT MANAGER | |
706 | |
707 // a sample special-purpose layout manager | |
708 | |
709 class ScaffoldBox extends RenderBox { | |
710 | |
711 ScaffoldBox(this.toolbar, this.body, this.statusbar, this.drawer) { | |
712 assert(body != null); | |
713 } | |
714 | |
715 final RenderBox toolbar; | |
716 final RenderBox body; | |
717 final RenderBox statusbar; | |
718 final RenderBox drawer; | |
719 | |
720 void layout({ | |
721 double minWidth: 0.0, | |
722 double maxWidth: double.INFINITY, | |
723 double minHeight: 0.0, | |
724 double maxHeight: double.INFINITY, | |
725 RenderNode relayoutSubtreeRoot | |
726 }) { | |
727 width = clamp(min: minWidth, max: maxWidth); | |
728 height = clamp(min: minHeight, max: maxHeight); | |
729 relayout(); | |
730 } | |
731 | |
732 static const kToolbarHeight = 100.0; | |
733 static const kStatusbarHeight = 50.0; | |
734 | |
735 void relayout() { | |
736 double bodyHeight = height; | |
737 if (toolbar != null) { | |
738 toolbar.layout(minWidth: width, maxWidth: width, minHeight: kToolbarHeight , maxHeight: kToolbarHeight); | |
739 (toolbar.pos as BoxParentData).x = 0.0; | |
740 (toolbar.pos as BoxParentData).y = 0.0; | |
741 bodyHeight -= kToolbarHeight; | |
742 } | |
743 if (statusbar != null) { | |
744 statusbar.layout(minWidth: width, maxWidth: width, minHeight: kStatusbarHe ight, maxHeight: kStatusbarHeight); | |
745 (statusbar.pos as BoxParentData).x = 0.0; | |
746 (statusbar.pos as BoxParentData).y = height - kStatusbarHeight; | |
747 bodyHeight -= kStatusbarHeight; | |
748 } | |
749 body.layout(minWidth: width, maxWidth: width, minHeight: bodyHeight, maxHeig ht: bodyHeight); | |
750 if (drawer != null) | |
751 drawer.layout(minWidth: 0.0, maxWidth: width, minHeight: height, maxHeight : height); | |
752 layoutDone(); | |
753 } | |
754 | |
755 void hitTest(double x, double y, List<RenderNode> targets) { | |
756 if ((drawer != null) && (x < drawer.width)) { | |
757 drawer.hitTest(x, y, targets); | |
758 } else if ((toolbar != null) && (y < toolbar.height)) { | |
759 toolbar.hitTest(x, y, targets); | |
760 } else if ((statusbar != null) && (y > (statusbar.pos as BoxParentData).y)) { | |
761 statusbar.hitTest(x, y, targets); | |
762 } else { | |
763 body.hitTest(x, y, targets); | |
764 } | |
765 super.hitTest(x, y, targets); | |
766 } | |
767 | |
768 void paint(DisplayList canvas) { | |
769 canvas.paintChild(body, (body.pos as BoxParentData).x, (body.pos as BoxParen tData).y); | |
770 if (statusbar != null) | |
771 canvas.paintChild(statusbar, (statusbar.pos as BoxParentData).x, (statusba r.pos as BoxParentData).y); | |
772 if (toolbar != null) | |
773 canvas.paintChild(toolbar, (toolbar.pos as BoxParentData).x, (toolbar.pos as BoxParentData).y); | |
774 if (drawer != null) | |
775 canvas.paintChild(drawer, (drawer.pos as BoxParentData).x, (drawer.pos as BoxParentData).y); | |
776 } | |
777 | |
778 } | |
OLD | NEW |