UNPKG

verstak

Version:
335 lines (334 loc) 15.8 kB
import { Direction } from "./El.js"; import { clamp } from "./ElUtils.js"; import { Drivers, isSplitViewPartition } from "./Elements.js"; const DEBUG = false; const eps = 0.0001; export function equal(a, b) { return Math.abs(a - b) <= eps; } export function less(a, b) { return b - a > eps; } export function greater(a, b) { return a - b > eps; } export function relayoutUsingSplitter(splitViewNode, deltaPx, index, initialSizesPx, priorities) { var _a, _b, _c, _d; if (priorities === undefined) { priorities = getPrioritiesForSplitter(index + 1, initialSizesPx.length); } const containerSizePx = splitViewNode.element.splitView === Direction.horizontal ? (_b = (_a = splitViewNode.element.layoutInfo) === null || _a === void 0 ? void 0 : _a.contentSizeXpx) !== null && _b !== void 0 ? _b : 0 : (_d = (_c = splitViewNode.element.layoutInfo) === null || _c === void 0 ? void 0 : _c.contentSizeYpx) !== null && _d !== void 0 ? _d : 0; if (containerSizePx > 0) { DEBUG && console.group(`(splitter) delta = ${deltaPx}, container = ${containerSizePx}, size = ${initialSizesPx.reduce((p, c) => p + c.sizePx, 0)}, index = ${index}`); resizeUsingDelta(splitViewNode, deltaPx, index + 1, priorities, initialSizesPx, true); DEBUG && console.groupEnd(); layout(splitViewNode); } } export function relayout(splitViewNode, priorities, manuallyResizablePriorities, sizesPx) { var _a, _b, _c, _d; const containerSizePx = splitViewNode.element.splitView === Direction.horizontal ? (_b = (_a = splitViewNode.element.layoutInfo) === null || _a === void 0 ? void 0 : _a.contentSizeXpx) !== null && _b !== void 0 ? _b : 0 : (_d = (_c = splitViewNode.element.layoutInfo) === null || _c === void 0 ? void 0 : _c.contentSizeYpx) !== null && _d !== void 0 ? _d : 0; if (containerSizePx > 0) { const totalSizePx = sizesPx.reduce((p, c) => p + c.sizePx, 0); let deltaPx = containerSizePx - totalSizePx; DEBUG && console.log(printPriorities(priorities, manuallyResizablePriorities), "color: grey", "color:", "color: grey", "color:"); DEBUG && console.group(`(relayout) ∆ = ${n(deltaPx)}px, container = ${n(containerSizePx)}px, total = ${totalSizePx}`); deltaPx = resizeUsingDelta(splitViewNode, deltaPx, sizesPx.length, priorities, sizesPx); DEBUG && console.groupEnd(); DEBUG && console.group(`(relayout) ~∆ = ${n(deltaPx)}, container = ${n(containerSizePx, 3)}px, total = ${n(sizesPx.reduce((p, c) => p + c.sizePx, 0), 3)}px`); if (deltaPx < -(1 / devicePixelRatio)) { DEBUG && console.log(`%c${deltaPx}px`, "color: lime"); resizeUsingDelta(splitViewNode, deltaPx, sizesPx.length, manuallyResizablePriorities, sizesPx, true); } DEBUG && console.groupEnd(); layout(splitViewNode); } } export function resizeUsingDelta(splitViewNode, deltaPx, index, priorities, sizesPx, force = false) { const isHorizontal = splitViewNode.element.splitView === Direction.horizontal; let beforeDeltaPx = 0; if (sizesPx.length > 0 && deltaPx !== 0) { let minBeforeDeltaPx = 0; let maxBeforeDeltaPx = 0; for (let i = 0; i < index; i++) { const size = isHorizontal ? sizesPx[i].node.element.widthPx : sizesPx[i].node.element.heightPx; minBeforeDeltaPx += size.minPx - sizesPx[i].sizePx; maxBeforeDeltaPx += size.maxPx - sizesPx[i].sizePx; } const hasAfter = index < sizesPx.length; let minAfterDeltaPx = hasAfter ? 0 : Number.NEGATIVE_INFINITY; let maxAfterDeltaPx = hasAfter ? 0 : Number.POSITIVE_INFINITY; for (let i = index; i < sizesPx.length; i++) { const size = isHorizontal ? sizesPx[i].node.element.widthPx : sizesPx[i].node.element.heightPx; minAfterDeltaPx += sizesPx[i].sizePx - size.maxPx; maxAfterDeltaPx += sizesPx[i].sizePx - size.minPx; } const minDeltaPx = Math.max(minBeforeDeltaPx, minAfterDeltaPx); const maxDeltaPx = Math.min(maxBeforeDeltaPx, maxAfterDeltaPx); const clampedDeltaPx = clamp(deltaPx, minDeltaPx, maxDeltaPx); DEBUG && console.log("Initial sizes:"); DEBUG && sizesPx.map((x, i) => { const size = isHorizontal ? x.node.element.widthPx : x.node.element.heightPx; return console.log(`%c ${i}: ${size.minPx}..${x.sizePx}..${size.maxPx} (px)`, "color: skyblue"); }); DEBUG && console.log(`[%c${Array.from({ length: index }).map((x, i) => i).join(",")}%c | %c${Array.from({ length: Math.max(0, sizesPx.length - index) }).map((x, i) => index + i).join(",")}%c] ∆ = ${n(minDeltaPx)}px..${n(deltaPx)}px -> %c${n(clampedDeltaPx)}px%c..${n(maxDeltaPx)}px`, "color: #00BB00", "color:", "color: #00BB00", "color:", "color: yellow", "color:"); if (clampedDeltaPx !== 0) { DEBUG && console.log("distribution: start"); if (index > 0) beforeDeltaPx = distribute(1, clampedDeltaPx, index, priorities, sizesPx, isHorizontal, force); if (hasAfter) distribute(-1, clampedDeltaPx, index, priorities, sizesPx, isHorizontal, force); DEBUG && console.log("distribution: end"); } } DEBUG && console.log("Set new sizes:"); for (let i = 0; i < sizesPx.length; i++) { const el = sizesPx[i].node.element; DEBUG && console.log(`%c ${i}: ${n(el.layoutInfo.effectiveSizePx)} -> ${n(sizesPx[i].sizePx)} (px)`, "color: skyblue"); el.layoutInfo.effectiveSizePx = sizesPx[i].sizePx; } return beforeDeltaPx; } export function layout(splitViewNode) { var _a, _b, _c, _d, _e, _f, _g; const isHorizontal = splitViewNode.element.splitView === Direction.horizontal; let posPx = 0; let shrinkBefore = false; let growBefore = false; let isSplitterEnabled = false; const sizesPx = []; const layoutInfo = splitViewNode.element.layoutInfo; for (const item of splitViewNode.children.items()) { const child = item.instance; if (isSplitViewPartition(child.driver)) { const el = child.element; if (el.native !== undefined) { const current = item; const sizePx = isHorizontal ? el.widthPx : el.heightPx; const effectiveSizePx = (_b = (_a = el.layoutInfo) === null || _a === void 0 ? void 0 : _a.effectiveSizePx) !== null && _b !== void 0 ? _b : 0; posPx += effectiveSizePx; sizesPx.push(effectiveSizePx); el.native.setAttribute("rx-max", equal(effectiveSizePx, sizePx.maxPx) ? "true" : "false"); el.native.setAttribute("rx-min", equal(effectiveSizePx, sizePx.minPx) ? "true" : "false"); shrinkBefore || (shrinkBefore = greater(effectiveSizePx - sizePx.minPx, 0)); growBefore || (growBefore = greater(sizePx.maxPx - effectiveSizePx, 0)); let shrinkAfter = false; let growAfter = false; for (const item of splitViewNode.children.items(current)) { const child = item.instance; if (isSplitViewPartition(child.driver)) { const el = child.element; if (el.native !== undefined) { const sizePx = isHorizontal ? el.widthPx : el.heightPx; const effectiveSizePx = (_d = (_c = el.layoutInfo) === null || _c === void 0 ? void 0 : _c.effectiveSizePx) !== null && _d !== void 0 ? _d : 0; shrinkAfter || (shrinkAfter = greater(effectiveSizePx - sizePx.minPx, 0)); growAfter || (growAfter = greater(sizePx.maxPx - effectiveSizePx, 0)); isSplitterEnabled = growBefore && shrinkAfter || growAfter && shrinkBefore; if (isSplitterEnabled) break; } } } } } else if (child.driver === Drivers.splitter) { const el = child.element; if (el.native !== undefined) { el.style.display = isSplitterEnabled ? "block" : "none"; if (isHorizontal) el.style.left = `${posPx}px`; else el.style.top = `${posPx}px`; } } } const containerSizePx = (_e = (isHorizontal ? layoutInfo === null || layoutInfo === void 0 ? void 0 : layoutInfo.contentSizeXpx : layoutInfo === null || layoutInfo === void 0 ? void 0 : layoutInfo.contentSizeYpx)) !== null && _e !== void 0 ? _e : 0; const isOverflowing = greater(posPx, containerSizePx); const wrapper = (_g = (_f = splitViewNode.children.firstMergedItem()) === null || _f === void 0 ? void 0 : _f.instance.children.firstMergedItem()) === null || _g === void 0 ? void 0 : _g.instance; if (wrapper !== undefined) { if (isHorizontal) wrapper.element.style.gridTemplateColumns = sizesPx.map(x => `${x}px`).join(" "); else wrapper.element.style.gridTemplateRows = sizesPx.map(x => `${x}px`).join(" "); if (isOverflowing) { if (isHorizontal) wrapper.element.style.overflow = "scroll visible"; else wrapper.element.style.overflow = "visible scroll"; } else { wrapper.element.style.overflow = "visible"; } } } export function getPrioritiesForSplitter(index, size) { const result = []; let i = index - 1; let j = index; while (i >= 0 || j < size) { if (i >= 0 && j < size) { result.push((1 << i--) | (1 << j++)); } else if (i >= 0) { result.push(1 << i--); } else { result.push(1 << j++); } } return result; } export function getPrioritiesForSizeChanging(isHorizontal, children, indexes) { var _a, _b; const resizable = []; const manuallyResizable = []; const items = Array.from(children.items()).filter(x => isSplitViewPartition(x.instance.driver)); for (let i = items.length - 1; i >= 0; i--) { const el = items[i].instance.element; const strength = (_a = (isHorizontal ? el.stretchingStrengthHorizontally : el.stretchingStrengthVertically)) !== null && _a !== void 0 ? _a : 1; if (!indexes.includes(i)) { if (strength > 0) resizable.push(1 << i); else manuallyResizable.push(1 << i); } } let r = 0; let mr = 0; for (const i of indexes) { const el = items[i].instance.element; const strength = (_b = (isHorizontal ? el.stretchingStrengthHorizontally : el.stretchingStrengthVertically)) !== null && _b !== void 0 ? _b : 1; if (strength > 0) r |= 1 << i; else mr |= 1 << i; } if (r > 0) resizable.push(r); if (mr > 0) manuallyResizable.push(mr); return { resizable, manuallyResizable }; } export function getPrioritiesForEmptySpaceDistribution(isHorizontal, children) { var _a; let r = 0; let mr = 0; let i = 0; for (const child of children.items()) { if (isSplitViewPartition(child.instance.driver)) { const el = child.instance.element; const strength = (_a = (isHorizontal ? el.stretchingStrengthHorizontally : el.stretchingStrengthVertically)) !== null && _a !== void 0 ? _a : 1; if (strength > 0) r |= 1 << i; else mr |= 1 << i; i++; } } return { resizable: [r], manuallyResizable: [mr] }; } function getFractionCount(isHorizontal, children, vector, index, force = false) { var _a; let result = 0; for (const i of indexes(vector, index)) { const growth = (_a = (isHorizontal ? children[i].element.stretchingStrengthHorizontally : children[i].element.stretchingStrengthVertically)) !== null && _a !== void 0 ? _a : 1; result += growth > 0 ? growth : (force ? 1 : 0); } return result; } function getFractionSizePx(spacePx, fractionCount) { return fractionCount > 0 ? spacePx / fractionCount : 0; } function* indexes(vector, index) { let i = 0; if (index < 0) { i = -index; vector >>>= i; while (vector > 0) { if (vector & 1) { yield i; } vector >>>= 1; i++; } } else { while (i < index) { if (vector & 1) { yield i; } vector >>>= 1; i++; } } } function n(value, fractionDigits = 2) { return value === 0 ? "0" : value.toFixed(fractionDigits); } function distribute(sign, deltaPx, index, priorities, sizesPx, isHorizontal, force) { var _a, _b; for (let priority = 0; priority < priorities.length; priority++) { const vector = priorities[priority]; let fractionCount = getFractionCount(isHorizontal, sizesPx.map(x => x.node), vector, sign * index, force); do { const fractionSizePx = getFractionSizePx(deltaPx, fractionCount); fractionCount = 0; for (const i of indexes(vector, sign * index)) { const child = sizesPx[i].node; const initialSizePx = sizesPx[i].sizePx; const strength = isHorizontal ? ((_a = child.element.stretchingStrengthHorizontally) !== null && _a !== void 0 ? _a : 1) : ((_b = child.element.stretchingStrengthVertically) !== null && _b !== void 0 ? _b : 1); const growth = strength > 0 ? strength : (force ? 1 : 0); const newSizePx = initialSizePx + sign * (growth * fractionSizePx); const size = isHorizontal ? sizesPx[i].node.element.widthPx : sizesPx[i].node.element.heightPx; const sizePx = clamp(newSizePx, size.minPx, size.maxPx); deltaPx = deltaPx - sign * (sizePx - initialSizePx); sizesPx[i].sizePx = sizePx; if (sizesPx[i].sizePx > size.minPx && sizesPx[i].sizePx < size.maxPx) { fractionCount += growth; } } } while (!equal(deltaPx, 0) && fractionCount > 0); if (equal(deltaPx, 0)) { break; } } return deltaPx; } function printPriorities(priorities, manuallyResizablePriorities) { let text = ""; if (priorities.length > 0) { text += `Automatically Resizable:\n%c(${priorities.map(x => `0b${x.toString(2)}`).join(", ")})%c\n`; for (let i = 0; i < priorities.length; i++) { let vector = priorities[i]; const parts = []; let j = 0; while (vector) { if (vector & 1) parts.push(j); j++; vector >>= 1; } text += `${i}: ${parts.join(", ")}\n`; } } if (manuallyResizablePriorities.length > 0) { text += `Manually Resizable:\n%c(${manuallyResizablePriorities.map(x => `0b${x.toString(2)}`).join(", ")})%c\n`; for (let i = 0; i < manuallyResizablePriorities.length; i++) { let vector = manuallyResizablePriorities[i]; const parts = []; let j = 0; while (vector) { if (vector & 1) parts.push(j); j++; vector >>= 1; } text += `${i}: ${parts.join(", ")}\n`; } } return text; }