@thi.ng/layout
Version:
Configurable nested 2D grid layout generators
97 lines (96 loc) • 3.19 kB
JavaScript
import { argMax, argMin } from "@thi.ng/arrays/argmin";
import { GridLayout, __DEFAULT_SPANS } from "./grid-layout.js";
class StackedLayout extends GridLayout {
offsets;
currSpan = 1;
constructor(parent, x, y, width, cols, rowH, gap) {
super(parent, x, y, width, cols, rowH, gap);
this.offsets = new Uint32Array(cols);
}
nest(cols, spans, gap = this.gap) {
const { x, y, w } = this.next(spans);
return new StackedLayout(this, x, y, w, cols, this.cellH, gap);
}
next(spans = __DEFAULT_SPANS) {
const { cellWG, cellHG, gap, cols, offsets } = this;
const cspan = Math.min(spans[0], cols);
const rspan = spans[1];
let minY = Infinity;
let maxY = 0;
let column = 0;
for (let i = 0; i <= cols - cspan; i++) {
const chunk = offsets.subarray(i, i + cspan);
const maxID = argMax(chunk);
const y = chunk[maxID];
if (y < minY) {
minY = y;
maxY = chunk[maxID];
column = i;
}
}
const h = rspan * cellHG - gap;
const cell = {
x: this.x + column * cellWG,
y: this.y + maxY * cellHG,
w: cspan * cellWG - gap,
h,
cw: this.cellW,
ch: this.cellH,
gap,
span: [cspan, rspan]
};
this.currRow = maxY;
this.currCol = column;
offsets.fill(maxY + rspan, column, column + cspan);
this.currSpan = cspan;
this.parent?.propagateSize(Math.max(...this.offsets));
return cell;
}
/**
* Finds the largest available span of free area, such that if it'll be
* allocated via {@link StackedLayout.next} or {@link StackedLayout.nest},
* the impacted columns will all have the same height, and that height will
* match that of the next column after (if any). Repeated use of this method
* can be used to fill up (aka equalize) any bottom gaps of a layout
* container until all columns are equal. If the function returns a vertical
* span of 0, all columns are equalized already.
*
* @remarks
* An optional `maxSpan` can be provided to constrain the returned span (by
* default unconstrained).
*
* @param maxSpan
*/
availableSpan(maxSpan = [Infinity, Infinity]) {
const { offsets, cols } = this;
const minID = argMin(offsets);
const y = offsets[minID];
let result;
for (let i = minID + 1; i < cols; i++) {
if (offsets[i] > y) {
result = [i - minID, offsets[i] - y];
break;
}
}
if (!result) result = [cols - minID, offsets[argMax(offsets)] - y];
result[0] = Math.min(result[0], maxSpan[0]);
result[1] = Math.min(result[1], maxSpan[1]);
return result;
}
propagateSize(rspan) {
const newY = Math.max(this.currRow + rspan, this.offsets[this.currCol]);
this.offsets.fill(newY, this.currCol, this.currCol + this.currSpan);
this.parent?.propagateSize(newY);
}
/**
* Returns true if all column offsets/heights in the layout are equal.
*/
isEqualized() {
return Math.min(...this.offsets) === Math.max(...this.offsets);
}
}
const stackedLayout = (x, y, width, cols = 4, rowH = 16, gap = 4) => new StackedLayout(null, x, y, width, cols, rowH, gap);
export {
StackedLayout,
stackedLayout
};