 Chromium Code Reviews
 Chromium Code Reviews Issue 1151293002:
  WIP Flexbox Layout Manager for Sky framework.  (Closed) 
  Base URL: git@github.com:domokit/mojo.git@master
    
  
    Issue 1151293002:
  WIP Flexbox Layout Manager for Sky framework.  (Closed) 
  Base URL: git@github.com:domokit/mojo.git@master| OLD | NEW | 
|---|---|
| 1 library layout; | 1 library layout; | 
| 2 | 2 | 
| 3 // This version of layout.dart is an update to the other one, this one using new APIs. | 3 // This version of layout.dart is an update to the other one, this one using new APIs. | 
| 4 // It will not work in a stock Sky setup currently. | 4 // It will not work in a stock Sky setup currently. | 
| 5 | 5 | 
| 6 import 'node.dart'; | 6 import 'node.dart'; | 
| 7 | 7 | 
| 8 import 'dart:sky' as sky; | 8 import 'dart:sky' as sky; | 
| 9 | 9 | 
| 10 // ABSTRACT LAYOUT | 10 // ABSTRACT LAYOUT | 
| (...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 214 int newAngle, // 0..3 | 214 int newAngle, // 0..3 | 
| 215 Duration time | 215 Duration time | 
| 216 }) { } | 216 }) { } | 
| 217 | 217 | 
| 218 | 218 | 
| 219 // HIT TESTING | 219 // HIT TESTING | 
| 220 | 220 | 
| 221 bool handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) { | 221 bool handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) { | 
| 222 // override this if you have children, to hand it to the appropriate child | 222 // override this if you have children, to hand it to the appropriate child | 
| 223 // override this if you want to do anything with the pointer event | 223 // override this if you want to do anything with the pointer event | 
| 224 return false; | |
| 224 } | 225 } | 
| 225 | 226 | 
| 226 | 227 | 
| 227 // PAINTING | 228 // PAINTING | 
| 228 | 229 | 
| 229 static bool _debugDoingPaint = false; | 230 static bool _debugDoingPaint = false; | 
| 230 void markNeedsPaint() { | 231 void markNeedsPaint() { | 
| 231 assert(!_debugDoingPaint); | 232 assert(!_debugDoingPaint); | 
| 232 // TODO(abarth): It's very redunant to call this for every node in the | 233 // TODO(abarth): It's very redunant to call this for every node in the | 
| 233 // render tree during layout. We should instead compute a summary bit and | 234 // render tree during layout. We should instead compute a summary bit and | 
| (...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 418 | 419 | 
| 419 double constrainHeight(double height) { | 420 double constrainHeight(double height) { | 
| 420 return clamp(min: minHeight, max: maxHeight, value: height); | 421 return clamp(min: minHeight, max: maxHeight, value: height); | 
| 421 } | 422 } | 
| 422 } | 423 } | 
| 423 | 424 | 
| 424 class BoxDimensions { | 425 class BoxDimensions { | 
| 425 const BoxDimensions({this.width, this.height}); | 426 const BoxDimensions({this.width, this.height}); | 
| 426 | 427 | 
| 427 BoxDimensions.withConstraints( | 428 BoxDimensions.withConstraints( | 
| 428 BoxConstraints constraints, {double width: 0.0, double height: 0.0}) { | 429 BoxConstraints constraints, {double width: 0.0, double height: 0.0}) | 
| 429 this.width = constraints.constrainWidth(width); | 430 : width = constraints.constrainWidth(width), | 
| 430 this.height = constraints.constrainHeight(height); | 431 height = constraints.constrainHeight(height); | 
| 431 } | |
| 432 | 432 | 
| 433 final double width; | 433 final double width; | 
| 434 final double height; | 434 final double height; | 
| 435 } | 435 } | 
| 436 | 436 | 
| 437 class BoxParentData extends ParentData { | 437 class BoxParentData extends ParentData { | 
| 438 double x = 0.0; | 438 double x = 0.0; | 
| 439 double y = 0.0; | 439 double y = 0.0; | 
| 440 } | 440 } | 
| 441 | 441 | 
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 573 void paintFrame() { | 573 void paintFrame() { | 
| 574 RenderNode._debugDoingPaint = true; | 574 RenderNode._debugDoingPaint = true; | 
| 575 var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height); | 575 var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height); | 
| 576 paint(canvas); | 576 paint(canvas); | 
| 577 sky.view.picture = canvas.endRecording(); | 577 sky.view.picture = canvas.endRecording(); | 
| 578 RenderNode._debugDoingPaint = false; | 578 RenderNode._debugDoingPaint = false; | 
| 579 } | 579 } | 
| 580 | 580 | 
| 581 } | 581 } | 
| 582 | 582 | 
| 583 // DEFAULT BEHAVIORS FOR RENDERBOX CONTAINERS | |
| 584 abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare ntDataType extends ContainerParentDataMixin<ChildType>> implements ContainerRend erNodeMixin<ChildType, ParentDataType> { | |
| 585 bool defaultHandlePointer(sky.PointerEvent event, double x, double y) { | |
| 586 // the x, y parameters have the top left of the node's box as the origin | |
| 587 ChildType child = _lastChild; | |
| 
Hixie
2015/05/23 00:34:20
_lastChild => lastChild
 | |
| 588 while (child != null) { | |
| 589 assert(child.parentData is BoxParentData); | |
| 590 if ((x >= child.parentData.x) && (x < child.parentData.x + child.width) && | |
| 591 (y >= child.parentData.y) && (y < child.parentData.y + child.height)) { | |
| 592 if (child.handlePointer(event, x: x-child.parentData.x, y: y-child.paren tData.y)) | |
| 593 return true; | |
| 594 break; | |
| 595 } | |
| 596 child = child.parentData.previousSibling; | |
| 597 } | |
| 598 return false; | |
| 599 } | |
| 600 | |
| 601 void defaultPaint(RenderNodeDisplayList canvas) { | |
| 602 RenderBox child = firstChild; | |
| 603 while (child != null) { | |
| 604 assert(child.parentData is BoxParentData); | |
| 605 canvas.paintChild(child, child.parentData.x, child.parentData.y); | |
| 606 child = child.parentData.nextSibling; | |
| 607 } | |
| 608 } | |
| 609 } | |
| 583 | 610 | 
| 584 // BLOCK LAYOUT MANAGER | 611 // BLOCK LAYOUT MANAGER | 
| 585 | 612 | 
| 586 class EdgeDims { | 613 class EdgeDims { | 
| 587 // used for e.g. padding | 614 // used for e.g. padding | 
| 588 const EdgeDims(this.top, this.right, this.bottom, this.left); | 615 const EdgeDims(this.top, this.right, this.bottom, this.left); | 
| 589 final double top; | 616 final double top; | 
| 590 final double right; | 617 final double right; | 
| 591 final double bottom; | 618 final double bottom; | 
| 592 final double left; | 619 final double left; | 
| 593 operator ==(EdgeDims other) => (top == other.top) || | 620 operator ==(EdgeDims other) => (top == other.top) || | 
| 594 (right == other.right) || | 621 (right == other.right) || | 
| 595 (bottom == other.bottom) || | 622 (bottom == other.bottom) || | 
| 596 (left == other.left); | 623 (left == other.left); | 
| 597 } | 624 } | 
| 598 | 625 | 
| 599 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render Box> { } | 626 class BlockParentData extends BoxParentData with ContainerParentDataMixin<Render Box> { } | 
| 600 | 627 | 
| 601 class RenderBlock extends RenderDecoratedBox with ContainerRenderNodeMixin<Rende rBox, BlockParentData> { | 628 class RenderBlock extends RenderDecoratedBox with ContainerRenderNodeMixin<Rende rBox, BlockParentData>, | 
| 629 RenderBoxContainerDefaultsMixi n<RenderBox, BlockParentData> { | |
| 602 // lays out RenderBox children in a vertical stack | 630 // lays out RenderBox children in a vertical stack | 
| 603 // uses the maximum width provided by the parent | 631 // uses the maximum width provided by the parent | 
| 604 // sizes itself to the height of its child stack | 632 // sizes itself to the height of its child stack | 
| 605 | 633 | 
| 606 RenderBlock({ | 634 RenderBlock({ | 
| 607 BoxDecoration decoration, | 635 BoxDecoration decoration, | 
| 608 EdgeDims padding: const EdgeDims(0.0, 0.0, 0.0, 0.0) | 636 EdgeDims padding: const EdgeDims(0.0, 0.0, 0.0, 0.0) | 
| 609 }) : super(decoration), _padding = padding; | 637 }) : super(decoration), _padding = padding; | 
| 610 | 638 | 
| 611 EdgeDims _padding; | 639 EdgeDims _padding; | 
| (...skipping 15 matching lines...) Expand all Loading... | |
| 627 // were laid out with the given constraints this can walk the tree | 655 // were laid out with the given constraints this can walk the tree | 
| 628 // if it must, but it should be as cheap as possible; just get the | 656 // if it must, but it should be as cheap as possible; just get the | 
| 629 // dimensions and nothing else (e.g. don't calculate hypothetical | 657 // dimensions and nothing else (e.g. don't calculate hypothetical | 
| 630 // child positions if they're not needed to determine dimensions) | 658 // child positions if they're not needed to determine dimensions) | 
| 631 BoxDimensions getIntrinsicDimensions(BoxConstraints constraints) { | 659 BoxDimensions getIntrinsicDimensions(BoxConstraints constraints) { | 
| 632 double outerHeight = _padding.top + _padding.bottom; | 660 double outerHeight = _padding.top + _padding.bottom; | 
| 633 double outerWidth = constraints.constrainWidth(constraints.maxWidth); | 661 double outerWidth = constraints.constrainWidth(constraints.maxWidth); | 
| 634 assert(outerWidth < double.INFINITY); | 662 assert(outerWidth < double.INFINITY); | 
| 635 double innerWidth = outerWidth - (_padding.left + _padding.right); | 663 double innerWidth = outerWidth - (_padding.left + _padding.right); | 
| 636 RenderBox child = _firstChild; | 664 RenderBox child = _firstChild; | 
| 637 BoxConstraints constraints = new BoxConstraints(minWidth: innerWidth, | 665 BoxConstraints innerConstraints = new BoxConstraints(minWidth: innerWidth, | 
| 638 maxWidth: innerWidth); | 666 maxWidth: innerWidth); | 
| 639 while (child != null) { | 667 while (child != null) { | 
| 640 outerHeight += child.getIntrinsicDimensions(constraints).height; | 668 outerHeight += child.getIntrinsicDimensions(innerConstraints).height; | 
| 641 assert(child.parentData is BlockParentData); | 669 assert(child.parentData is BlockParentData); | 
| 642 child = child.parentData.nextSibling; | 670 child = child.parentData.nextSibling; | 
| 643 } | 671 } | 
| 644 | 672 | 
| 645 return new BoxDimensions(width: outerWidth, | 673 return new BoxDimensions(width: outerWidth, | 
| 646 height: constraints.constrainHeight(outerHeight)); | 674 height: constraints.constrainHeight(outerHeight)); | 
| 647 } | 675 } | 
| 648 | 676 | 
| 649 double _minHeight; // value cached from parent for relayout call | 677 double _minHeight; // value cached from parent for relayout call | 
| 650 double _maxHeight; // value cached from parent for relayout call | 678 double _maxHeight; // value cached from parent for relayout call | 
| (...skipping 25 matching lines...) Expand all Loading... | |
| 676 child.parentData.x = _padding.left; | 704 child.parentData.x = _padding.left; | 
| 677 child.parentData.y = y; | 705 child.parentData.y = y; | 
| 678 y += child.height; | 706 y += child.height; | 
| 679 child = child.parentData.nextSibling; | 707 child = child.parentData.nextSibling; | 
| 680 } | 708 } | 
| 681 height = clamp(min: _minHeight, value: y + _padding.bottom, max: _maxHeight) ; | 709 height = clamp(min: _minHeight, value: y + _padding.bottom, max: _maxHeight) ; | 
| 682 layoutDone(); | 710 layoutDone(); | 
| 683 } | 711 } | 
| 684 | 712 | 
| 685 bool handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) { | 713 bool handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) { | 
| 686 // the x, y parameters have the top left of the node's box as the origin | 714 return defaultHandlePointer(event, x, y) || super.handlePointer(event, x: x, y: y); | 
| 687 RenderBox child = _lastChild; | |
| 688 while (child != null) { | |
| 689 assert(child.parentData is BlockParentData); | |
| 690 if ((x >= child.parentData.x) && (x < child.parentData.x + child.width) && | |
| 691 (y >= child.parentData.y) && (y < child.parentData.y + child.height)) { | |
| 692 if (child.handlePointer(event, x: x-child.parentData.x, y: y-child.paren tData.y)) | |
| 693 return true; | |
| 694 break; | |
| 695 } | |
| 696 child = child.parentData.previousSibling; | |
| 697 } | |
| 698 return super.handlePointer(event, x: x, y: y); | |
| 699 } | 715 } | 
| 700 | 716 | 
| 701 void paint(RenderNodeDisplayList canvas) { | 717 void paint(RenderNodeDisplayList canvas) { | 
| 702 super.paint(canvas); | 718 super.paint(canvas); | 
| 703 RenderBox child = _firstChild; | 719 defaultPaint(canvas); | 
| 704 while (child != null) { | |
| 705 assert(child.parentData is BlockParentData); | |
| 706 canvas.paintChild(child, child.parentData.x, child.parentData.y); | |
| 707 child = child.parentData.nextSibling; | |
| 708 } | |
| 709 } | 720 } | 
| 710 | 721 | 
| 711 } | 722 } | 
| 712 | 723 | 
| 713 class FlexBoxParentData extends BoxParentData { | 724 // FLEXBOX LAYOUT MANAGER | 
| 725 | |
| 726 class FlexBoxParentData extends BoxParentData with ContainerParentDataMixin<Rend erBox> { | |
| 714 int flex; | 727 int flex; | 
| 715 void merge(FlexBoxParentData other) { | 728 void merge(FlexBoxParentData other) { | 
| 716 if (other.flex != null) | 729 if (other.flex != null) | 
| 717 flex = other.flex; | 730 flex = other.flex; | 
| 718 super.merge(other); | 731 super.merge(other); | 
| 719 } | 732 } | 
| 720 } | 733 } | 
| 721 | 734 | 
| 722 enum FlexDirection { Row, Column } | 735 enum FlexDirection { Horizontal, Vertical } | 
| 723 | 736 | 
| 724 // TODO(ianh): FlexBox | 737 class RenderFlex extends RenderDecoratedBox with ContainerRenderNodeMixin<Render Box, FlexBoxParentData>, | 
| 738 RenderBoxContainerDefaultsMixin <RenderBox, BlockParentData> { | |
| 739 // lays out RenderBox children using flexible layout | |
| 725 | 740 | 
| 741 RenderFlex({ | |
| 742 BoxDecoration decoration, | |
| 743 FlexDirection direction: FlexDirection.Horizontal | |
| 744 }) : super(decoration), _direction = direction; | |
| 745 | |
| 746 FlexDirection _direction; | |
| 747 FlexDirection get direction => _direction; | |
| 748 void set direction (FlexDirection value) { | |
| 749 if (_direction != value) { | |
| 750 _direction = value; | |
| 751 markNeedsLayout(); | |
| 752 } | |
| 753 } | |
| 754 | |
| 755 void setParentData(RenderBox child) { | |
| 756 if (child.parentData is! FlexBoxParentData) | |
| 757 child.parentData = new FlexBoxParentData(); | |
| 758 } | |
| 759 | |
| 760 BoxConstraints _constraints; // value cached from parent for relayout call | |
| 761 void layout(BoxConstraints constraints, { RenderNode relayoutSubtreeRoot }) { | |
| 762 if (relayoutSubtreeRoot != null) | |
| 763 saveRelayoutSubtreeRoot(relayoutSubtreeRoot); | |
| 764 relayoutSubtreeRoot = relayoutSubtreeRoot == null ? this : relayoutSubtreeRo ot; | |
| 765 _constraints = constraints; | |
| 766 width = _constraints.constrainWidth(_constraints.maxWidth); | |
| 767 height = _constraints.constrainHeight(_constraints.maxHeight); | |
| 768 assert(height < double.INFINITY); | |
| 769 assert(width < double.INFINITY); | |
| 770 internalLayout(relayoutSubtreeRoot); | |
| 771 } | |
| 772 | |
| 773 void relayout() { | |
| 774 internalLayout(this); | |
| 775 } | |
| 776 | |
| 777 int _getFlex(RenderBox child) { | |
| 778 assert(child.parentData is FlexBoxParentData); | |
| 779 return (child.parentData.flex != null ? child.parentData.flex : 0); | |
| 780 } | |
| 781 | |
| 782 void internalLayout(RenderNode relayoutSubtreeRoot) { | |
| 783 // Based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexib le Lengths | |
| 784 // Steps 1-3. Determine used flex factor, size inflexible items, calculate f ree space | |
| 785 int numFlexibleChildren = 0; | |
| 786 int totalFlex = 0; | |
| 787 assert(_constraints != null); | |
| 788 double freeSpace = (_direction == FlexDirection.Horizontal) ? _constraints.m axWidth : _constraints.maxHeight; | |
| 789 RenderBox child = _firstChild; | |
| 790 while (child != null) { | |
| 791 int flex = _getFlex(child); | |
| 792 if (flex > 0) { | |
| 793 numFlexibleChildren++; | |
| 794 totalFlex += child.parentData.flex; | |
| 795 } else { | |
| 796 BoxConstraints constraints = new BoxConstraints(maxHeight: _constraints. maxHeight, | |
| 797 maxWidth: _constraints.m axWidth); | |
| 798 child.layout(constraints, | |
| 799 relayoutSubtreeRoot: relayoutSubtreeRoot); | |
| 800 freeSpace -= (_direction == FlexDirection.Horizontal) ? child.width : ch ild.height; | |
| 801 } | |
| 802 child = child.parentData.nextSibling; | |
| 803 } | |
| 804 | |
| 805 // Steps 4-5. Distribute remaining space to flexible children. | |
| 806 double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0; | |
| 807 double usedSpace = 0.0; | |
| 808 child = _firstChild; | |
| 809 while (child != null) { | |
| 810 int flex = _getFlex(child); | |
| 811 if (flex > 0) { | |
| 812 double spaceForChild = spacePerFlex * flex; | |
| 813 BoxConstraints constraints; | |
| 814 switch (_direction) { | |
| 815 case FlexDirection.Horizontal: | |
| 816 constraints = new BoxConstraints(maxHeight: _constraints.maxHeight, | |
| 817 minWidth: spaceForChild, | |
| 818 maxWidth: spaceForChild); | |
| 819 break; | |
| 820 case FlexDirection.Vertical: | |
| 821 constraints = new BoxConstraints(minHeight: spaceForChild, | |
| 822 maxHeight: spaceForChild, | |
| 823 maxWidth: _constraints.maxWidth); | |
| 824 break; | |
| 825 } | |
| 826 child.layout(constraints, relayoutSubtreeRoot: relayoutSubtreeRoot); | |
| 827 } | |
| 828 | |
| 829 // For now, center the flex items in the cross direction | |
| 830 switch (_direction) { | |
| 831 case FlexDirection.Horizontal: | |
| 832 child.parentData.x = usedSpace; | |
| 833 usedSpace += child.width; | |
| 834 child.parentData.y = height / 2 - child.height / 2; | |
| 835 break; | |
| 836 case FlexDirection.Vertical: | |
| 837 child.parentData.y = usedSpace; | |
| 838 usedSpace += child.height; | |
| 839 child.parentData.x = width / 2 - child.width / 2; | |
| 840 break; | |
| 841 } | |
| 842 child = child.parentData.nextSibling; | |
| 843 } | |
| 844 layoutDone(); | |
| 845 } | |
| 846 | |
| 847 bool handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) { | |
| 848 return defaultHandlePointer(event, x, y) || super.handlePointer(event, x: x, y: y); | |
| 849 } | |
| 850 | |
| 851 void paint(RenderNodeDisplayList canvas) { | |
| 852 super.paint(canvas); | |
| 853 defaultPaint(canvas); | |
| 854 } | |
| 855 } | |
| 726 | 856 | 
| 727 // SCAFFOLD LAYOUT MANAGER | 857 // SCAFFOLD LAYOUT MANAGER | 
| 728 | 858 | 
| 729 // a sample special-purpose layout manager | 859 // a sample special-purpose layout manager | 
| 730 | 860 | 
| 731 class ScaffoldBox extends RenderBox { | 861 class ScaffoldBox extends RenderBox { | 
| 732 | 862 | 
| 733 ScaffoldBox(this.toolbar, this.body, this.statusbar, this.drawer) { | 863 ScaffoldBox(this.toolbar, this.body, this.statusbar, this.drawer) { | 
| 734 assert(body != null); | 864 assert(body != null); | 
| 735 } | 865 } | 
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 791 canvas.paintChild(body, (body.parentData as BoxParentData).x, (body.parentDa ta as BoxParentData).y); | 921 canvas.paintChild(body, (body.parentData as BoxParentData).x, (body.parentDa ta as BoxParentData).y); | 
| 792 if (statusbar != null) | 922 if (statusbar != null) | 
| 793 canvas.paintChild(statusbar, (statusbar.parentData as BoxParentData).x, (s tatusbar.parentData as BoxParentData).y); | 923 canvas.paintChild(statusbar, (statusbar.parentData as BoxParentData).x, (s tatusbar.parentData as BoxParentData).y); | 
| 794 if (toolbar != null) | 924 if (toolbar != null) | 
| 795 canvas.paintChild(toolbar, (toolbar.parentData as BoxParentData).x, (toolb ar.parentData as BoxParentData).y); | 925 canvas.paintChild(toolbar, (toolbar.parentData as BoxParentData).x, (toolb ar.parentData as BoxParentData).y); | 
| 796 if (drawer != null) | 926 if (drawer != null) | 
| 797 canvas.paintChild(drawer, (drawer.parentData as BoxParentData).x, (drawer. parentData as BoxParentData).y); | 927 canvas.paintChild(drawer, (drawer.parentData as BoxParentData).x, (drawer. parentData as BoxParentData).y); | 
| 798 } | 928 } | 
| 799 | 929 | 
| 800 } | 930 } | 
| OLD | NEW |