verstak
Version:
Verstak - Front-End Library
333 lines (332 loc) • 15.6 kB
JavaScript
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;
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 child of splitViewNode.children.items()) {
if (isSplitViewPartition(child.driver)) {
const el = child.element;
if (el.native !== undefined) {
const current = child;
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 child of splitViewNode.children.items(current)) {
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 = (_f = splitViewNode.children.firstItem) === null || _f === void 0 ? void 0 : _f.children.firstItem;
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.driver));
for (let i = items.length - 1; i >= 0; i--) {
const el = items[i].element;
const strength = (_a = (isHorizontal ? el.selfStretchingStrengthHorizontal : el.selfStretchingStrengthVertical)) !== 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].element;
const strength = (_b = (isHorizontal ? el.selfStretchingStrengthHorizontal : el.selfStretchingStrengthVertical)) !== 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.driver)) {
const el = child.element;
const strength = (_a = (isHorizontal ? el.selfStretchingStrengthHorizontal : el.selfStretchingStrengthVertical)) !== 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.selfStretchingStrengthHorizontal : children[i].element.selfStretchingStrengthVertical)) !== 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.selfStretchingStrengthHorizontal) !== null && _a !== void 0 ? _a : 1) : ((_b = child.element.selfStretchingStrengthVertical) !== 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;
}