309 lines
9.9 KiB
TypeScript
309 lines
9.9 KiB
TypeScript
import { PanelConstraints } from "../Panel";
|
||
import { assert } from "./assert";
|
||
import { fuzzyCompareNumbers } from "./numbers/fuzzyCompareNumbers";
|
||
import { fuzzyLayoutsEqual } from "./numbers/fuzzyLayoutsEqual";
|
||
import { fuzzyNumbersEqual } from "./numbers/fuzzyNumbersEqual";
|
||
import { resizePanel } from "./resizePanel";
|
||
|
||
// All units must be in percentages; pixel values should be pre-converted
|
||
export function adjustLayoutByDelta({
|
||
delta,
|
||
initialLayout,
|
||
panelConstraints: panelConstraintsArray,
|
||
pivotIndices,
|
||
prevLayout,
|
||
trigger,
|
||
}: {
|
||
delta: number;
|
||
initialLayout: number[];
|
||
panelConstraints: PanelConstraints[];
|
||
pivotIndices: number[];
|
||
prevLayout: number[];
|
||
trigger: "imperative-api" | "keyboard" | "mouse-or-touch";
|
||
}): number[] {
|
||
if (fuzzyNumbersEqual(delta, 0)) {
|
||
return initialLayout;
|
||
}
|
||
|
||
const nextLayout = [...initialLayout];
|
||
|
||
const [firstPivotIndex, secondPivotIndex] = pivotIndices;
|
||
assert(firstPivotIndex != null, "Invalid first pivot index");
|
||
assert(secondPivotIndex != null, "Invalid second pivot index");
|
||
|
||
let deltaApplied = 0;
|
||
|
||
// const DEBUG = [];
|
||
// DEBUG.push(`adjustLayoutByDelta()`);
|
||
// DEBUG.push(` initialLayout: ${initialLayout.join(", ")}`);
|
||
// DEBUG.push(` prevLayout: ${prevLayout.join(", ")}`);
|
||
// DEBUG.push(` delta: ${delta}`);
|
||
// DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
|
||
// DEBUG.push(` trigger: ${trigger}`);
|
||
// DEBUG.push("");
|
||
|
||
// A resizing panel affects the panels before or after it.
|
||
//
|
||
// A negative delta means the panel(s) immediately after the resize handle should grow/expand by decreasing its offset.
|
||
// Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
|
||
//
|
||
// A positive delta means the panel(s) immediately before the resize handle should "expand".
|
||
// This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resize handle.
|
||
|
||
{
|
||
// If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
|
||
// We no longer check the halfway threshold because this may prevent the panel from expanding at all.
|
||
if (trigger === "keyboard") {
|
||
{
|
||
// Check if we should expand a collapsed panel
|
||
const index = delta < 0 ? secondPivotIndex : firstPivotIndex;
|
||
const panelConstraints = panelConstraintsArray[index];
|
||
assert(
|
||
panelConstraints,
|
||
`Panel constraints not found for index ${index}`
|
||
);
|
||
|
||
const {
|
||
collapsedSize = 0,
|
||
collapsible,
|
||
minSize = 0,
|
||
} = panelConstraints;
|
||
|
||
// DEBUG.push(`edge case check 1: ${index}`);
|
||
// DEBUG.push(` -> collapsible? ${collapsible}`);
|
||
if (collapsible) {
|
||
const prevSize = initialLayout[index];
|
||
assert(
|
||
prevSize != null,
|
||
`Previous layout not found for panel index ${index}`
|
||
);
|
||
|
||
if (fuzzyNumbersEqual(prevSize, collapsedSize)) {
|
||
const localDelta = minSize - prevSize;
|
||
// DEBUG.push(` -> expand delta: ${localDelta}`);
|
||
|
||
if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
|
||
delta = delta < 0 ? 0 - localDelta : localDelta;
|
||
// DEBUG.push(` -> delta: ${delta}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
{
|
||
// Check if we should collapse a panel at its minimum size
|
||
const index = delta < 0 ? firstPivotIndex : secondPivotIndex;
|
||
const panelConstraints = panelConstraintsArray[index];
|
||
assert(
|
||
panelConstraints,
|
||
`No panel constraints found for index ${index}`
|
||
);
|
||
|
||
const {
|
||
collapsedSize = 0,
|
||
collapsible,
|
||
minSize = 0,
|
||
} = panelConstraints;
|
||
|
||
// DEBUG.push(`edge case check 2: ${index}`);
|
||
// DEBUG.push(` -> collapsible? ${collapsible}`);
|
||
if (collapsible) {
|
||
const prevSize = initialLayout[index];
|
||
assert(
|
||
prevSize != null,
|
||
`Previous layout not found for panel index ${index}`
|
||
);
|
||
|
||
if (fuzzyNumbersEqual(prevSize, minSize)) {
|
||
const localDelta = prevSize - collapsedSize;
|
||
// DEBUG.push(` -> expand delta: ${localDelta}`);
|
||
|
||
if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
|
||
delta = delta < 0 ? 0 - localDelta : localDelta;
|
||
// DEBUG.push(` -> delta: ${delta}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// DEBUG.push("");
|
||
}
|
||
|
||
{
|
||
// Pre-calculate max available delta in the opposite direction of our pivot.
|
||
// This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
|
||
// If this amount is less than the requested delta, adjust the requested delta.
|
||
// If this amount is greater than the requested delta, that's useful information too–
|
||
// as an expanding panel might change from collapsed to min size.
|
||
|
||
const increment = delta < 0 ? 1 : -1;
|
||
|
||
let index = delta < 0 ? secondPivotIndex : firstPivotIndex;
|
||
let maxAvailableDelta = 0;
|
||
|
||
// DEBUG.push("pre calc...");
|
||
while (true) {
|
||
const prevSize = initialLayout[index];
|
||
assert(
|
||
prevSize != null,
|
||
`Previous layout not found for panel index ${index}`
|
||
);
|
||
|
||
const maxSafeSize = resizePanel({
|
||
panelConstraints: panelConstraintsArray,
|
||
panelIndex: index,
|
||
size: 100,
|
||
});
|
||
const delta = maxSafeSize - prevSize;
|
||
// DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
|
||
|
||
maxAvailableDelta += delta;
|
||
index += increment;
|
||
|
||
if (index < 0 || index >= panelConstraintsArray.length) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
// DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
|
||
const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
|
||
delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
|
||
// DEBUG.push(` -> adjusted delta: ${delta}`);
|
||
// DEBUG.push("");
|
||
}
|
||
|
||
{
|
||
// Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
|
||
|
||
const pivotIndex = delta < 0 ? firstPivotIndex : secondPivotIndex;
|
||
let index = pivotIndex;
|
||
while (index >= 0 && index < panelConstraintsArray.length) {
|
||
const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
|
||
|
||
const prevSize = initialLayout[index];
|
||
assert(
|
||
prevSize != null,
|
||
`Previous layout not found for panel index ${index}`
|
||
);
|
||
|
||
const unsafeSize = prevSize - deltaRemaining;
|
||
const safeSize = resizePanel({
|
||
panelConstraints: panelConstraintsArray,
|
||
panelIndex: index,
|
||
size: unsafeSize,
|
||
});
|
||
|
||
if (!fuzzyNumbersEqual(prevSize, safeSize)) {
|
||
deltaApplied += prevSize - safeSize;
|
||
|
||
nextLayout[index] = safeSize;
|
||
|
||
if (
|
||
deltaApplied
|
||
.toPrecision(3)
|
||
.localeCompare(Math.abs(delta).toPrecision(3), undefined, {
|
||
numeric: true,
|
||
}) >= 0
|
||
) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (delta < 0) {
|
||
index--;
|
||
} else {
|
||
index++;
|
||
}
|
||
}
|
||
}
|
||
// DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
|
||
// DEBUG.push(` deltaApplied: ${deltaApplied}`);
|
||
// DEBUG.push("");
|
||
|
||
// If we were unable to resize any of the panels panels, return the previous state.
|
||
// This will essentially bailout and ignore e.g. drags past a panel's boundaries
|
||
if (fuzzyLayoutsEqual(prevLayout, nextLayout)) {
|
||
// DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
|
||
// console.log(DEBUG.join("\n"));
|
||
|
||
return prevLayout;
|
||
}
|
||
|
||
{
|
||
// Now distribute the applied delta to the panels in the other direction
|
||
const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
|
||
|
||
const prevSize = initialLayout[pivotIndex];
|
||
assert(
|
||
prevSize != null,
|
||
`Previous layout not found for panel index ${pivotIndex}`
|
||
);
|
||
|
||
const unsafeSize = prevSize + deltaApplied;
|
||
const safeSize = resizePanel({
|
||
panelConstraints: panelConstraintsArray,
|
||
panelIndex: pivotIndex,
|
||
size: unsafeSize,
|
||
});
|
||
|
||
// Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
|
||
nextLayout[pivotIndex] = safeSize;
|
||
|
||
// Edge case where expanding or contracting one panel caused another one to change collapsed state
|
||
if (!fuzzyNumbersEqual(safeSize, unsafeSize)) {
|
||
let deltaRemaining = unsafeSize - safeSize;
|
||
|
||
const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
|
||
let index = pivotIndex;
|
||
while (index >= 0 && index < panelConstraintsArray.length) {
|
||
const prevSize = nextLayout[index];
|
||
assert(
|
||
prevSize != null,
|
||
`Previous layout not found for panel index ${index}`
|
||
);
|
||
|
||
const unsafeSize = prevSize + deltaRemaining;
|
||
const safeSize = resizePanel({
|
||
panelConstraints: panelConstraintsArray,
|
||
panelIndex: index,
|
||
size: unsafeSize,
|
||
});
|
||
|
||
if (!fuzzyNumbersEqual(prevSize, safeSize)) {
|
||
deltaRemaining -= safeSize - prevSize;
|
||
|
||
nextLayout[index] = safeSize;
|
||
}
|
||
|
||
if (fuzzyNumbersEqual(deltaRemaining, 0)) {
|
||
break;
|
||
}
|
||
|
||
if (delta > 0) {
|
||
index--;
|
||
} else {
|
||
index++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
|
||
// DEBUG.push(` deltaApplied: ${deltaApplied}`);
|
||
// DEBUG.push("");
|
||
|
||
const totalSize = nextLayout.reduce((total, size) => size + total, 0);
|
||
// DEBUG.push(`total size: ${totalSize}`);
|
||
|
||
// If our new layout doesn't add up to 100%, that means the requested delta can't be applied
|
||
// In that case, fall back to our most recent valid layout
|
||
if (!fuzzyNumbersEqual(totalSize, 100)) {
|
||
// DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
|
||
// console.log(DEBUG.join("\n"));
|
||
|
||
return prevLayout;
|
||
}
|
||
|
||
// console.log(DEBUG.join("\n"));
|
||
return nextLayout;
|
||
}
|