| 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.PositiveLengthStyleValueType, |
| inherits: true, |
| initialValue: 8, |
| 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) { |
| this.markAsLaidOut(); |
| 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 || child.descendantNeedsLayout) { |
| 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 width != '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 children = this.walkChildren(); |
| let loop = children.next(); |
| while ((!loop.done) && (loop.value != this.firstSkippedChild)) |
| canvas.paintChild(loop.value); |
| if (this.showingOverflow) |
| canvas.paintChild(this.overflowChild); |
| } |
| function inChild(child, x, y) { |
| return (x >= child.x) && (y >= child.y) && (x < child.x+child.width) && (y < child.y+child.height); |
| } |
| function hitTest(x, y) { |
| let children = this.walkChildrenBackwards(); |
| let loop = children.next(); |
| while ((!loop.done) && (loop.value != this.firstSkippedChild)) |
| if (this.inChild(loop.value, x, y)) |
| return loop.value; |
| if (this.showingOverflow) |
| if (this.inChild(this.overflowChild, x, y)) |
| return this.overflowChild; |
| return this.node; |
| } |
| } |
| sky.registerLayoutManager('toolbar', module.exports.ToolbarLayoutManager); |
| </script> |