| Index: sky/examples/style/toolbar-layout.sky
|
| diff --git a/sky/examples/style/toolbar-layout.sky b/sky/examples/style/toolbar-layout.sky
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a43ce3fc5d773ed8e9ce859b62b7eb215e348674
|
| --- /dev/null
|
| +++ b/sky/examples/style/toolbar-layout.sky
|
| @@ -0,0 +1,208 @@
|
| +SKY MODULE
|
| +<import src="sky:core" as="sky"/>
|
| +<script>
|
| + // display: toolbar;
|
| + // toolbar-spacing: <length>
|
| + // display: spring; // remaining space is split equally amongst the springs
|
| + // children are vertically centered, layout out left-to-right with toolbar-spacing space between them
|
| + // last child is hidden by default unless there's not enough room for the others, then it's shown last, right-aligned
|
| + module.exports.SpringLayoutManager = class SpringLayoutManager extends sky.LayoutManager { }
|
| + sky.registerLayoutManager('spring', module.exports.SpringLayoutManager);
|
| + sky.registerProperty({
|
| + name: 'toolbar-spacing',
|
| + type: sky.LengthStyleValueType,
|
| + inherits: true,
|
| + initialValue: { value: 8, unit: 'px' },
|
| + needsLayout: true,
|
| + });
|
| + module.exports.ToolbarLayoutManager = class ToolbarLayoutManager extends sky.LayoutManager {
|
| + constructor (styleNode) {
|
| + super(styleNode);
|
| + this.showingOverflow = false;
|
| + this.firstSkippedChild = null;
|
| + this.overflowChild = null;
|
| + }
|
| + function layout(width, height) {
|
| + let children = null;
|
| + let loop = null;
|
| + if (height == null)
|
| + height = this.getIntrinsicHeight().value;
|
| + if (width == null)
|
| + this.assumeDimensions(0, height);
|
| + else
|
| + this.assumeDimensions(width, height);
|
| + let spacing = this.node.getProperty('toolbar-spacing');
|
| + if (typeof spacing != 'number')
|
| + spacing = 0;
|
| + this.overflowChild = null;
|
| + this.firstSkippedChild = null;
|
| +
|
| + // layout children and figure out whether we need to truncate the child list and show the overflow child
|
| + let springCount = 0;
|
| + let minX = 0;
|
| + let overflowChildWidth = 0;
|
| + let pendingSpacing = 0;
|
| + children = this.walkChildren();
|
| + loop = children.next();
|
| + while (!loop.done) {
|
| + let child = loop.value;
|
| + let dims = null;
|
| + if (child.layoutManager instanceof module.exports.SpringLayoutManager) {
|
| + springCount += 1;
|
| + pendingSpacing = spacing; // not +=, because we only have one extra spacing per batch of springs
|
| + } else {
|
| + if (child.needsLayout) {
|
| + childHeight = child.layoutManager.getIntrinsicHeight();
|
| + if (childHeight.value < height)
|
| + childHeight = childHeight.value;
|
| + else
|
| + childHeight = height;
|
| + dims = child.layoutManager.layout(width, height);
|
| + this.setChildSize(child, dims.width, dims.height);
|
| + } else {
|
| + dims = {
|
| + width: child.width,
|
| + height: child.height,
|
| + };
|
| + }
|
| + loop = children.next();
|
| + if (!loop.done) {
|
| + if (minX > 0)
|
| + minX += spacing + pendingSpacing;
|
| + minX += dims.width;
|
| + pendingSpacing = 0;
|
| + } else {
|
| + overflowChildWidth = spacing + dims.width;
|
| + this.overflowChild = child;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // figure out the spacing
|
| + this.showingOverflow = false;
|
| + let springSize = 0;
|
| + if (width != null) {
|
| + if (minX <= width) {
|
| + if (springCount > 0)
|
| + springSize = (width - minX) / sprintCount;
|
| + } else {
|
| + this.showingOverflow = true;
|
| + }
|
| + } else {
|
| + width = minX;
|
| + }
|
| +
|
| + // position the children
|
| + // TODO(ianh): support rtl toolbars
|
| + let x = 0;
|
| + let lastWasNonSpring = false;
|
| + children = this.walkChildren();
|
| + loop = children.next();
|
| + while (!loop.done) {
|
| + let child = loop.value;
|
| + if (child.layoutManager instanceof module.exports.SpringLayoutManager) {
|
| + x += springSize;
|
| + if (lastWasNonSpring)
|
| + x += spacing;
|
| + lastWasNonSpring = false;
|
| + } else {
|
| + if (!loop.done) {
|
| + if (x + child.width + overflowChildWidth > width) {
|
| + this.firstSkippedChild = child;
|
| + break; // don't display any more children
|
| + }
|
| + this.setChildPosition(child, x, (height - child.height)/2);
|
| + x += child.width + spacing;
|
| + lastWasNonSpring = true;
|
| + } else {
|
| + // assert: this.showingOverflow == false
|
| + }
|
| + }
|
| + }
|
| + if (this.showingOverflow)
|
| + this.setChildPosition(this.overflowChild, width-this.overflowChild.width, (height - this.overflowChild.height)/2);
|
| + else
|
| + this.firstSkippedChild = this.overflowChild;
|
| +
|
| + return {
|
| + width: width,
|
| + height: height,
|
| + }
|
| + }
|
| + function getIntrinsicWidth() {
|
| + let width = this.node.getProperty('width');
|
| + if (typeof height != 'number') {
|
| + let spacing = this.node.getProperty('toolbar-spacing');
|
| + if (typeof spacing != 'number')
|
| + spacing = 0;
|
| + width = 0;
|
| + let children = this.walkChildren();
|
| + let loop = children.next();
|
| + // we exclude the last child because at our ideal width we wouldn't need it
|
| + let last1 = null; // last one
|
| + let last2 = null; // one before the last one
|
| + while (!loop.done) {
|
| + if (last1)
|
| + width += last1.layoutManager.getIntrinsicWidth().value;
|
| + if (last2)
|
| + width += spacing;
|
| + last2 = last1;
|
| + last1 = loop.value;
|
| + loop = children.next();
|
| + }
|
| + }
|
| + return super(width); // applies and provides our own min-width/max-width rules
|
| + }
|
| + function getIntrinsicHeight() {
|
| + // we grow our minimum height to be no smaller than the children's
|
| + let result = super();
|
| + let determineHeight = false;
|
| + let heightProperty = this.node.getProperty('height');
|
| + if (typeof heightProperty != 'number')
|
| + determineHeight = true;
|
| + let children = this.walkChildren();
|
| + let loop = children.next();
|
| + // here we include the last child so that if it pops in our height doesn't change
|
| + while (!loop.done) {
|
| + let child = loop.value;
|
| + let childHeight = child.layoutManager.getIntrinsicHeight();
|
| + if (determineHeight) {
|
| + if (result.value < childHeight.value)
|
| + result.value = childHeight.value;
|
| + }
|
| + if (result.minimum < childHeight.minimum)
|
| + result.minimum = childHeight.minimum;
|
| + loop = children.next();
|
| + }
|
| + if (result.minimum > result.maximum)
|
| + result.maximum = result.minimum;
|
| + if (result.value > result.maximum)
|
| + result.value = result.maximum;
|
| + if (result.value < result.minimum)
|
| + result.value = result.minimum;
|
| + return result;
|
| + }
|
| + function paintChildren(canvas) {
|
| + let width = this.node.width;
|
| + let loop = children.next();
|
| + while ((!loop.done) && (loop.value != this.firstSkippedChild))
|
| + this.paintChild(loop.value, canvas);
|
| + if (this.showingOverflow)
|
| + this.paintChild(this.overflowChild, canvas);
|
| + }
|
| + function paintChild(child, canvas) {
|
| + if (child.needsPaint) {
|
| + canvas.save();
|
| + try {
|
| + canvas.beginPath();
|
| + canvas.rect(child.x, child.y, child.width, child.height);
|
| + canvas.clip();
|
| + child.paint(canvas);
|
| + } finally {
|
| + canvas.restore();
|
| + }
|
| + }
|
| + }
|
| + }
|
| + sky.registerLayoutManager('toolbar', module.exports.ToolbarLayoutManager);
|
| +</script>
|
|
|