OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 import 'node.dart'; | 5 import 'node.dart'; |
6 import 'dart:sky' as sky; | 6 import 'dart:sky' as sky; |
7 | 7 |
8 // ABSTRACT LAYOUT | 8 // ABSTRACT LAYOUT |
9 | 9 |
10 class ParentData { | 10 class ParentData { |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
233 | 233 |
234 void handlePointer(sky.PointerEvent event) { | 234 void handlePointer(sky.PointerEvent event) { |
235 // override this if you have a client, to hand it to the client | 235 // override this if you have a client, to hand it to the client |
236 // override this if you want to do anything with the pointer event | 236 // override this if you want to do anything with the pointer event |
237 } | 237 } |
238 | 238 |
239 // RenderNode subclasses are expected to have a method like the | 239 // RenderNode subclasses are expected to have a method like the |
240 // following (with the signature being whatever passes for coordinates | 240 // following (with the signature being whatever passes for coordinates |
241 // for this particular class): | 241 // for this particular class): |
242 // bool hitTest(HitTestResult result, { double x, double y }) { | 242 // bool hitTest(HitTestResult result, { double x, double y }) { |
243 // // If (x,y) is not inside this node, then return false. | 243 // // If (x,y) is not inside this node, then return false. (You |
| 244 // // can assume that the given coordinate is inside your |
| 245 // // dimensions. You only need to check this if you're an |
| 246 // // irregular shape, e.g. if you have a hole.) |
244 // // Otherwise: | 247 // // Otherwise: |
245 // // For each child that intersects x,y, in z-order starting from the top, | 248 // // For each child that intersects x,y, in z-order starting from the top, |
246 // // call hitTest() for that child, passing it /result/, and the coordinate
s | 249 // // call hitTest() for that child, passing it /result/, and the coordinate
s |
247 // // converted to the child's coordinate origin, and stop at the first chil
d | 250 // // converted to the child's coordinate origin, and stop at the first chil
d |
248 // // that returns true. | 251 // // that returns true. |
249 // // Then, add yourself to /result/, and return true. | 252 // // Then, add yourself to /result/, and return true. |
250 // } | 253 // } |
251 // You must not add yourself to /result/ if you return false. | 254 // You must not add yourself to /result/ if you return false. |
252 | 255 |
253 } | 256 } |
254 | 257 |
255 class HitTestResult { | 258 class HitTestResult { |
256 final List<RenderNode> path = new List<RenderNode>(); | 259 final List<RenderNode> path = new List<RenderNode>(); |
257 | 260 |
258 RenderNode get result => path.first; | 261 RenderNode get result => path.first; |
259 | 262 |
260 void add(RenderNode node) { | 263 void add(RenderNode node) { |
261 path.add(node); | 264 path.add(node); |
262 } | 265 } |
263 } | 266 } |
264 | 267 |
| 268 |
| 269 // GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD |
| 270 |
265 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { | 271 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { |
266 ChildType _child; | 272 ChildType _child; |
267 ChildType get child => _child; | 273 ChildType get child => _child; |
268 void set child (ChildType value) { | 274 void set child (ChildType value) { |
269 if (_child != null) | 275 if (_child != null) |
270 dropChild(_child); | 276 dropChild(_child); |
271 _child = value; | 277 _child = value; |
272 if (_child != null) | 278 if (_child != null) |
273 adoptChild(_child); | 279 adoptChild(_child); |
274 markNeedsLayout(); | 280 markNeedsLayout(); |
275 } | 281 } |
276 } | 282 } |
277 | 283 |
278 // GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN | 284 |
| 285 // GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN |
279 | 286 |
280 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | 287 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { |
281 ChildType previousSibling; | 288 ChildType previousSibling; |
282 ChildType nextSibling; | 289 ChildType nextSibling; |
283 void detachSiblings() { | 290 void detachSiblings() { |
284 if (previousSibling != null) { | 291 if (previousSibling != null) { |
285 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | 292 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); |
286 assert(previousSibling != this); | 293 assert(previousSibling != this); |
287 assert(previousSibling.parentData.nextSibling == this); | 294 assert(previousSibling.parentData.nextSibling == this); |
288 previousSibling.parentData.nextSibling = nextSibling; | 295 previousSibling.parentData.nextSibling = nextSibling; |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
423 assert(child.parentData is ParentDataType); | 430 assert(child.parentData is ParentDataType); |
424 return child.parentData.nextSibling; | 431 return child.parentData.nextSibling; |
425 } | 432 } |
426 | 433 |
427 } | 434 } |
428 | 435 |
429 | 436 |
430 // GENERIC BOX RENDERING | 437 // GENERIC BOX RENDERING |
431 // Anything that has a concept of x, y, width, height is going to derive from th
is | 438 // Anything that has a concept of x, y, width, height is going to derive from th
is |
432 | 439 |
| 440 class EdgeDims { |
| 441 // used for e.g. padding |
| 442 const EdgeDims(this.top, this.right, this.bottom, this.left); |
| 443 final double top; |
| 444 final double right; |
| 445 final double bottom; |
| 446 final double left; |
| 447 operator ==(EdgeDims other) => (top == other.top) || |
| 448 (right == other.right) || |
| 449 (bottom == other.bottom) || |
| 450 (left == other.left); |
| 451 } |
| 452 |
433 class BoxConstraints { | 453 class BoxConstraints { |
434 const BoxConstraints({ | 454 const BoxConstraints({ |
435 this.minWidth: 0.0, | 455 this.minWidth: 0.0, |
436 this.maxWidth: double.INFINITY, | 456 this.maxWidth: double.INFINITY, |
437 this.minHeight: 0.0, | 457 this.minHeight: 0.0, |
438 this.maxHeight: double.INFINITY}); | 458 this.maxHeight: double.INFINITY}); |
439 | 459 |
440 const BoxConstraints.tight({ double width: 0.0, double height: 0.0 }) | 460 const BoxConstraints.tight({ double width: 0.0, double height: 0.0 }) |
441 : minWidth = width, | 461 : minWidth = width, |
442 maxWidth = width, | 462 maxWidth = width, |
443 minHeight = height, | 463 minHeight = height, |
444 maxHeight = height; | 464 maxHeight = height; |
445 | 465 |
| 466 BoxConstraints deflate(EdgeDims edges) { |
| 467 assert(edges != null); |
| 468 return new BoxConstraints( |
| 469 minWidth: minWidth, |
| 470 maxWidth: maxWidth - (edges.left + edges.right), |
| 471 minHeight: minHeight, |
| 472 maxHeight: maxHeight - (edges.top + edges.bottom) |
| 473 ); |
| 474 } |
| 475 |
446 final double minWidth; | 476 final double minWidth; |
447 final double maxWidth; | 477 final double maxWidth; |
448 final double minHeight; | 478 final double minHeight; |
449 final double maxHeight; | 479 final double maxHeight; |
450 | 480 |
451 double constrainWidth(double width) { | 481 double constrainWidth(double width) { |
452 return clamp(min: minWidth, max: maxWidth, value: width); | 482 return clamp(min: minWidth, max: maxWidth, value: width); |
453 } | 483 } |
454 | 484 |
455 double constrainHeight(double height) { | 485 double constrainHeight(double height) { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
493 return new BoxDimensions.withConstraints(constraints); | 523 return new BoxDimensions.withConstraints(constraints); |
494 } | 524 } |
495 | 525 |
496 void layout(BoxConstraints constraints, { RenderNode relayoutSubtreeRoot }) { | 526 void layout(BoxConstraints constraints, { RenderNode relayoutSubtreeRoot }) { |
497 width = constraints.constrainWidth(0.0); | 527 width = constraints.constrainWidth(0.0); |
498 height = constraints.constrainHeight(0.0); | 528 height = constraints.constrainHeight(0.0); |
499 layoutDone(); | 529 layoutDone(); |
500 } | 530 } |
501 | 531 |
502 bool hitTest(HitTestResult result, { double x, double y }) { | 532 bool hitTest(HitTestResult result, { double x, double y }) { |
503 if (x < 0.0 || x >= width || y < 0.0 || y >= height) | |
504 return false; | |
505 hitTestChildren(result, x: x, y: y); | 533 hitTestChildren(result, x: x, y: y); |
506 result.add(this); | 534 result.add(this); |
507 return true; | 535 return true; |
508 } | 536 } |
509 void hitTestChildren(HitTestResult result, { double x, double y }) { } | 537 void hitTestChildren(HitTestResult result, { double x, double y }) { } |
510 | 538 |
511 double width; | 539 double width; |
512 double height; | 540 double height; |
513 } | 541 } |
514 | 542 |
| 543 class RenderPadding extends RenderBox with RenderNodeWithChildMixin<RenderBox> { |
| 544 |
| 545 RenderPadding(EdgeDims padding, RenderBox child) { |
| 546 assert(padding != null); |
| 547 this.padding = padding; |
| 548 this.child = child; |
| 549 } |
| 550 |
| 551 EdgeDims _padding; |
| 552 EdgeDims get padding => _padding; |
| 553 void set padding (EdgeDims value) { |
| 554 assert(value != null); |
| 555 if (_padding != value) { |
| 556 _padding = value; |
| 557 markNeedsLayout(); |
| 558 } |
| 559 } |
| 560 |
| 561 BoxDimensions getIntrinsicDimensions(BoxConstraints constraints) { |
| 562 assert(padding != null); |
| 563 constraints = constraints.deflate(padding); |
| 564 if (child == null) |
| 565 return super.getIntrinsicDimensions(constraints); |
| 566 return child.getIntrinsicDimensions(constraints); |
| 567 } |
| 568 |
| 569 void layout(BoxConstraints constraints, { RenderNode relayoutSubtreeRoot }) { |
| 570 assert(padding != null); |
| 571 constraints = constraints.deflate(padding); |
| 572 if (child == null) { |
| 573 width = constraints.constrainWidth(padding.left + padding.right); |
| 574 height = constraints.constrainHeight(padding.top + padding.bottom); |
| 575 return; |
| 576 } |
| 577 if (relayoutSubtreeRoot != null) |
| 578 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); |
| 579 else |
| 580 relayoutSubtreeRoot = this; |
| 581 child.layout(constraints, relayoutSubtreeRoot: relayoutSubtreeRoot); |
| 582 assert(child.parentData is BoxParentData); |
| 583 child.parentData.x = padding.left; |
| 584 child.parentData.y = padding.top; |
| 585 width = constraints.constrainWidth(padding.left + child.width + padding.righ
t); |
| 586 height = constraints.constrainHeight(padding.top + child.height + padding.bo
ttom); |
| 587 } |
| 588 |
| 589 void paint(RenderNodeDisplayList canvas) { |
| 590 if (child != null) |
| 591 canvas.paintChild(child, child.parentData.x, child.parentData.y); |
| 592 } |
| 593 |
| 594 void hitTestChildren(HitTestResult result, { double x, double y }) { |
| 595 if (child != null) { |
| 596 assert(child.parentData is BoxParentData); |
| 597 if ((x >= child.parentData.x) && (x < child.parentData.x + child.width) && |
| 598 (y >= child.parentData.y) && (y < child.parentData.y + child.height)) |
| 599 child.hitTest(result, x: x+child.parentData.x, y: y+child.parentData.y); |
| 600 } |
| 601 } |
| 602 |
| 603 } |
| 604 |
515 // This must be immutable, because we won't notice when it changes | 605 // This must be immutable, because we won't notice when it changes |
516 class BoxDecoration { | 606 class BoxDecoration { |
517 const BoxDecoration({ | 607 const BoxDecoration({ |
518 this.backgroundColor | 608 this.backgroundColor |
519 }); | 609 }); |
520 | 610 |
521 final int backgroundColor; | 611 final int backgroundColor; |
522 } | 612 } |
523 | 613 |
524 class RenderDecoratedBox extends RenderBox { | 614 class RenderDecoratedBox extends RenderBox { |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
606 _width = newWidth; | 696 _width = newWidth; |
607 _height = newHeight; | 697 _height = newHeight; |
608 relayout(); | 698 relayout(); |
609 } else { | 699 } else { |
610 layoutDone(); | 700 layoutDone(); |
611 } | 701 } |
612 } | 702 } |
613 | 703 |
614 void relayout() { | 704 void relayout() { |
615 if (child != null) { | 705 if (child != null) { |
616 child.layout(new BoxConstraints( | 706 child.layout(new BoxConstraints.tight(width: width, height: height)); |
617 minWidth: width, | |
618 maxWidth: width, | |
619 minHeight: height, | |
620 maxHeight: height | |
621 )); | |
622 assert(child.width == width); | 707 assert(child.width == width); |
623 assert(child.height == height); | 708 assert(child.height == height); |
624 } | 709 } |
625 layoutDone(); | 710 layoutDone(); |
626 } | 711 } |
627 | 712 |
628 void rotate({ int oldAngle, int newAngle, Duration time }) { | 713 void rotate({ int oldAngle, int newAngle, Duration time }) { |
629 assert(false); // nobody tells the screen to rotate, the whole rotate() danc
e is started from our layout() | 714 assert(false); // nobody tells the screen to rotate, the whole rotate() danc
e is started from our layout() |
630 } | 715 } |
631 | 716 |
632 bool hitTest(HitTestResult result, { double x, double y }) { | 717 bool hitTest(HitTestResult result, { double x, double y }) { |
633 if (x < 0.0 || x >= width || y < 0.0 || y >= height) | 718 if (child != null && x >= 0.0 && x < child.width && y >= 0.0 && y < child.he
ight) |
634 return false; | 719 child.hitTest(result, x: x, y: y); |
635 if (child != null) { | |
636 if (x >= 0.0 && x < child.width && y >= 0.0 && y < child.height) | |
637 child.hitTest(result, x: x, y: y); | |
638 } | |
639 result.add(this); | 720 result.add(this); |
640 return true; | 721 return true; |
641 } | 722 } |
642 | 723 |
643 void paint(RenderNodeDisplayList canvas) { | 724 void paint(RenderNodeDisplayList canvas) { |
644 if (child != null) | 725 if (child != null) |
645 canvas.paintChild(child, 0.0, 0.0); | 726 canvas.paintChild(child, 0.0, 0.0); |
646 } | 727 } |
647 | 728 |
648 void paintFrame() { | 729 void paintFrame() { |
(...skipping 28 matching lines...) Expand all Loading... |
677 while (child != null) { | 758 while (child != null) { |
678 assert(child.parentData is BoxParentData); | 759 assert(child.parentData is BoxParentData); |
679 canvas.paintChild(child, child.parentData.x, child.parentData.y); | 760 canvas.paintChild(child, child.parentData.x, child.parentData.y); |
680 child = child.parentData.nextSibling; | 761 child = child.parentData.nextSibling; |
681 } | 762 } |
682 } | 763 } |
683 } | 764 } |
684 | 765 |
685 // BLOCK LAYOUT MANAGER | 766 // BLOCK LAYOUT MANAGER |
686 | 767 |
687 class EdgeDims { | |
688 // used for e.g. padding | |
689 const EdgeDims(this.top, this.right, this.bottom, this.left); | |
690 final double top; | |
691 final double right; | |
692 final double bottom; | |
693 final double left; | |
694 operator ==(EdgeDims other) => (top == other.top) || | |
695 (right == other.right) || | |
696 (bottom == other.bottom) || | |
697 (left == other.left); | |
698 } | |
699 | |
700 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render
Box> { } | 768 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render
Box> { } |
701 | 769 |
702 class RenderBlock extends RenderDecoratedBox with ContainerRenderNodeMixin<Rende
rBox, BlockParentData>, | 770 class RenderBlock extends RenderDecoratedBox with ContainerRenderNodeMixin<Rende
rBox, BlockParentData>, |
703 RenderBoxContainerDefaultsMixi
n<RenderBox, BlockParentData> { | 771 RenderBoxContainerDefaultsMixi
n<RenderBox, BlockParentData> { |
704 // lays out RenderBox children in a vertical stack | 772 // lays out RenderBox children in a vertical stack |
705 // uses the maximum width provided by the parent | 773 // uses the maximum width provided by the parent |
706 // sizes itself to the height of its child stack | 774 // sizes itself to the height of its child stack |
707 | 775 |
708 RenderBlock({ | 776 RenderBlock({ |
709 BoxDecoration decoration, | 777 BoxDecoration decoration |
710 EdgeDims padding: const EdgeDims(0.0, 0.0, 0.0, 0.0) | 778 }) : super(decoration); |
711 }) : super(decoration), _padding = padding; | |
712 | |
713 EdgeDims _padding; | |
714 EdgeDims get padding => _padding; | |
715 void set padding (EdgeDims value) { | |
716 assert(value != null); | |
717 if (_padding != value) { | |
718 _padding = value; | |
719 markNeedsLayout(); | |
720 } | |
721 } | |
722 | 779 |
723 void setParentData(RenderBox child) { | 780 void setParentData(RenderBox child) { |
724 if (child.parentData is! BlockParentData) | 781 if (child.parentData is! BlockParentData) |
725 child.parentData = new BlockParentData(); | 782 child.parentData = new BlockParentData(); |
726 } | 783 } |
727 | 784 |
728 // override this to report what dimensions you would have if you | 785 // override this to report what dimensions you would have if you |
729 // were laid out with the given constraints this can walk the tree | 786 // were laid out with the given constraints this can walk the tree |
730 // if it must, but it should be as cheap as possible; just get the | 787 // if it must, but it should be as cheap as possible; just get the |
731 // dimensions and nothing else (e.g. don't calculate hypothetical | 788 // dimensions and nothing else (e.g. don't calculate hypothetical |
732 // child positions if they're not needed to determine dimensions) | 789 // child positions if they're not needed to determine dimensions) |
733 BoxDimensions getIntrinsicDimensions(BoxConstraints constraints) { | 790 BoxDimensions getIntrinsicDimensions(BoxConstraints constraints) { |
734 double outerHeight = _padding.top + _padding.bottom; | 791 double outerHeight = 0.0; |
735 double outerWidth = constraints.constrainWidth(constraints.maxWidth); | 792 double outerWidth = constraints.constrainWidth(constraints.maxWidth); |
736 assert(outerWidth < double.INFINITY); | 793 assert(outerWidth < double.INFINITY); |
737 double innerWidth = outerWidth - (_padding.left + _padding.right); | 794 double innerWidth = outerWidth; |
738 RenderBox child = firstChild; | 795 RenderBox child = firstChild; |
739 BoxConstraints innerConstraints = new BoxConstraints(minWidth: innerWidth, | 796 BoxConstraints innerConstraints = new BoxConstraints(minWidth: innerWidth, |
740 maxWidth: innerWidth); | 797 maxWidth: innerWidth); |
741 while (child != null) { | 798 while (child != null) { |
742 outerHeight += child.getIntrinsicDimensions(innerConstraints).height; | 799 outerHeight += child.getIntrinsicDimensions(innerConstraints).height; |
743 assert(child.parentData is BlockParentData); | 800 assert(child.parentData is BlockParentData); |
744 child = child.parentData.nextSibling; | 801 child = child.parentData.nextSibling; |
745 } | 802 } |
746 | 803 |
747 return new BoxDimensions(width: outerWidth, | 804 return new BoxDimensions(width: outerWidth, |
(...skipping 11 matching lines...) Expand all Loading... |
759 _constraints = constraints; | 816 _constraints = constraints; |
760 internalLayout(relayoutSubtreeRoot); | 817 internalLayout(relayoutSubtreeRoot); |
761 } | 818 } |
762 | 819 |
763 void relayout() { | 820 void relayout() { |
764 internalLayout(this); | 821 internalLayout(this); |
765 } | 822 } |
766 | 823 |
767 void internalLayout(RenderNode relayoutSubtreeRoot) { | 824 void internalLayout(RenderNode relayoutSubtreeRoot) { |
768 assert(_constraints != null); | 825 assert(_constraints != null); |
769 double y = _padding.top; | 826 double y = 0.0; |
770 double innerWidth = width - (_padding.left + _padding.right); | 827 double innerWidth = width; |
771 RenderBox child = firstChild; | 828 RenderBox child = firstChild; |
772 while (child != null) { | 829 while (child != null) { |
773 child.layout(new BoxConstraints(minWidth: innerWidth, maxWidth: innerWidth
), | 830 child.layout(new BoxConstraints(minWidth: innerWidth, maxWidth: innerWidth
), |
774 relayoutSubtreeRoot: relayoutSubtreeRoot); | 831 relayoutSubtreeRoot: relayoutSubtreeRoot); |
775 assert(child.parentData is BlockParentData); | 832 assert(child.parentData is BlockParentData); |
776 child.parentData.x = _padding.left; | 833 child.parentData.x = 0.0; |
777 child.parentData.y = y; | 834 child.parentData.y = y; |
778 y += child.height; | 835 y += child.height; |
779 child = child.parentData.nextSibling; | 836 child = child.parentData.nextSibling; |
780 } | 837 } |
781 height = _constraints.constrainHeight(y + _padding.bottom); | 838 height = _constraints.constrainHeight(y); |
782 layoutDone(); | 839 layoutDone(); |
783 } | 840 } |
784 | 841 |
785 void hitTestChildren(HitTestResult result, { double x, double y }) { | 842 void hitTestChildren(HitTestResult result, { double x, double y }) { |
786 defaultHitTestChildren(result, x: x, y: y); | 843 defaultHitTestChildren(result, x: x, y: y); |
787 } | 844 } |
788 | 845 |
789 void paint(RenderNodeDisplayList canvas) { | 846 void paint(RenderNodeDisplayList canvas) { |
790 super.paint(canvas); | 847 super.paint(canvas); |
791 defaultPaint(canvas); | 848 defaultPaint(canvas); |
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
917 | 974 |
918 void hitTestChildren(HitTestResult result, { double x, double y }) { | 975 void hitTestChildren(HitTestResult result, { double x, double y }) { |
919 defaultHitTestChildren(result, x: x, y: y); | 976 defaultHitTestChildren(result, x: x, y: y); |
920 } | 977 } |
921 | 978 |
922 void paint(RenderNodeDisplayList canvas) { | 979 void paint(RenderNodeDisplayList canvas) { |
923 super.paint(canvas); | 980 super.paint(canvas); |
924 defaultPaint(canvas); | 981 defaultPaint(canvas); |
925 } | 982 } |
926 } | 983 } |
OLD | NEW |