flutter/examples/style/toolbar-layout.sky
Hixie 7598f1fd3c Examples: move markAsLaidOut() to just before the return, so the asserts work
Specs: introduce layoutDescendants() to avoid work when a layout
manager is unaffected by its childrens' intrinsic dimensions
Examples: update for layoutDescendants() change
Specs: add "lifetime" to resolver settings so that a transition can
avoid having to dirty every consumer of the property every frame when
it only needs to update the objects that are changing that frame
Specs: expose the parents on AbstractStyleDeclarationList subclasses
Specs: fix documentation around autoreap
Specs: fix definition of setProperty()
Specs: clean up the dimension-related logic of layout managers

Review URL: https://codereview.chromium.org/850593003
2015-01-13 11:25:24 -08:00

215 lines
7.3 KiB
Plaintext

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.PositiveLengthStyleGrammar,
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) {
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(null, 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;
this.markAsLaidOut();
return {
width: width,
height: height,
}
}
function layoutDescendants() {
this.layout(node.width, node.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>