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> |