mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

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
215 lines
7.3 KiB
Plaintext
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>
|