UNPKG

@thi.ng/layout

Version:

Configurable nested 2D grid layout generators

97 lines (96 loc) 3.19 kB
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 };