mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
159 lines
5.8 KiB
JavaScript
159 lines
5.8 KiB
JavaScript
/**
|
|
* Fits expanded panels into a shared vertical corridor. Natural heights are
|
|
* retained when they fit; constrained panels keep a usable floor and share
|
|
* the remaining room in proportion to their unmet height.
|
|
*
|
|
* @param {object} input Layout measurements.
|
|
* @param {number[]} input.naturalHeights Expanded-panel natural heights.
|
|
* @param {number} input.availableHeight Height available to expanded panels.
|
|
* @param {number} [input.minimumHeight=96] Preferred usable floor per panel.
|
|
* @returns {number[]} One allocated height per expanded panel.
|
|
*/
|
|
export function allocatePanelStackHeights({
|
|
naturalHeights,
|
|
availableHeight,
|
|
minimumHeight = 96,
|
|
}) {
|
|
const natural = naturalHeights.map((height) =>
|
|
Math.max(0, Number(height) || 0),
|
|
);
|
|
if (!natural.length) return [];
|
|
|
|
const available = Math.max(0, Number(availableHeight) || 0);
|
|
const naturalTotal = natural.reduce((sum, height) => sum + height, 0);
|
|
if (naturalTotal <= available) return natural;
|
|
if (available === 0) return natural.map(() => 0);
|
|
|
|
const preferredFloor = Math.max(0, Number(minimumHeight) || 0);
|
|
const base = natural.map((height) => Math.min(height, preferredFloor));
|
|
const baseTotal = base.reduce((sum, height) => sum + height, 0);
|
|
if (baseTotal >= available) {
|
|
const scale = baseTotal > 0 ? available / baseTotal : 0;
|
|
return base.map((height) => height * scale);
|
|
}
|
|
|
|
const remaining = available - baseTotal;
|
|
const unmet = natural.map((height, index) =>
|
|
Math.max(0, height - base[index]),
|
|
);
|
|
const unmetTotal = unmet.reduce((sum, height) => sum + height, 0);
|
|
if (unmetTotal <= 0) return base;
|
|
return base.map(
|
|
(height, index) => height + remaining * (unmet[index] / unmetTotal),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Solves the accordion corridor's bottom boundary against the obstacles that
|
|
* still limit it.
|
|
*
|
|
* Every painted obstacle limits the corridor. This includes Cockpit CONTACT
|
|
* and peripheral HUD surfaces, so reopening a map panel cannot cover them.
|
|
*
|
|
* @param {object} input Corridor measurements.
|
|
* @param {number} input.baseBottom Viewport-inset bottom boundary, in px.
|
|
* @param {Array<{top: number}>} [input.obstacles] Live obstacle tops.
|
|
* @param {number} [input.safeGap=0] Clearance kept above each obstacle, in px.
|
|
* @returns {number} Bottom boundary in px.
|
|
*/
|
|
export function resolveLeftStackBottomBoundary({
|
|
baseBottom,
|
|
obstacles = [],
|
|
safeGap = 0,
|
|
}) {
|
|
let bottom = Number(baseBottom) || 0;
|
|
const gap = Number(safeGap) || 0;
|
|
for (const obstacle of obstacles) {
|
|
const top = Number(obstacle?.top);
|
|
if (!Number.isFinite(top)) continue;
|
|
bottom = Math.min(bottom, top - gap);
|
|
}
|
|
return bottom;
|
|
}
|
|
|
|
/**
|
|
* Returns later expanded panels whose allocation would expose less than the
|
|
* requested share of their intrinsic height. The first panel always remains
|
|
* expanded so every lane keeps one useful primary surface.
|
|
*
|
|
* @param {object} input Panel height measurements.
|
|
* @param {number[]} input.naturalHeights Intrinsic expanded heights.
|
|
* @param {number[]} input.allocatedHeights Allocated expanded heights.
|
|
* @param {number} [input.minimumVisibleRatio=0.5] Minimum useful height share.
|
|
* @param {boolean} [input.collapseLaterPanels=false] Collapse every competitor after the primary panel.
|
|
* @returns {number[]} Candidate indices to present as collapsed controls.
|
|
*/
|
|
export function panelStackAutoCollapseIndices({
|
|
naturalHeights,
|
|
allocatedHeights,
|
|
minimumVisibleRatio = 0.5,
|
|
collapseLaterPanels = false,
|
|
}) {
|
|
const threshold = Math.max(0, Number(minimumVisibleRatio) || 0);
|
|
const collapsed = [];
|
|
for (let index = 1; index < naturalHeights.length; index += 1) {
|
|
if (collapseLaterPanels) {
|
|
collapsed.push(index);
|
|
continue;
|
|
}
|
|
const natural = Math.max(0, Number(naturalHeights[index]) || 0);
|
|
const allocated = Math.max(0, Number(allocatedHeights[index]) || 0);
|
|
if (natural > 0 && allocated / natural < threshold) collapsed.push(index);
|
|
}
|
|
return collapsed;
|
|
}
|
|
|
|
/**
|
|
* Balance a desktop panel corridor around the viewport midpoint without
|
|
* crossing its measured obstacle boundaries. If centering would shrink the
|
|
* lane below its usable minimum, retain the original aligned corridor.
|
|
*
|
|
* @param {object} input Corridor measurements.
|
|
* @param {number} input.viewportHeight Current viewport height.
|
|
* @param {number} input.safeTop Proposed corridor top.
|
|
* @param {number} input.safeBottom Proposed corridor bottom.
|
|
* @param {number} input.obstacleSafeTop Highest obstacle-safe top boundary.
|
|
* @param {number} input.obstacleSafeBottom Lowest obstacle-safe bottom boundary.
|
|
* @param {number} input.minimumHeight Minimum useful lane height.
|
|
* @returns {{ safeTop: number, safeBottom: number }} Bounded corridor.
|
|
*/
|
|
export function resolvePanelStackCorridor({
|
|
viewportHeight,
|
|
safeTop,
|
|
safeBottom,
|
|
obstacleSafeTop,
|
|
obstacleSafeBottom,
|
|
minimumHeight,
|
|
}) {
|
|
const height = Math.max(1, Number(viewportHeight) || 1);
|
|
const boundaryTop = Math.max(0, Number(obstacleSafeTop) || 0);
|
|
const boundaryBottom = Math.max(
|
|
boundaryTop,
|
|
Math.min(height, Number(obstacleSafeBottom) || 0),
|
|
);
|
|
let top = Math.max(
|
|
boundaryTop,
|
|
Math.min(boundaryBottom, Number(safeTop) || 0),
|
|
);
|
|
let bottom = Math.max(top, Math.min(boundaryBottom, Number(safeBottom) || 0));
|
|
const minimum = Math.max(0, Number(minimumHeight) || 0);
|
|
const midpoint = height * 0.5;
|
|
|
|
if (top < midpoint && bottom > midpoint) {
|
|
const centeredHalfHeight = Math.min(midpoint - top, bottom - midpoint);
|
|
const centeredTop = midpoint - centeredHalfHeight;
|
|
const centeredBottom = midpoint + centeredHalfHeight;
|
|
if (centeredBottom - centeredTop >= minimum) {
|
|
top = centeredTop;
|
|
bottom = centeredBottom;
|
|
}
|
|
}
|
|
|
|
if (bottom - top < minimum) {
|
|
top = Math.max(boundaryTop, bottom - minimum);
|
|
bottom = Math.min(boundaryBottom, Math.max(bottom, top + minimum));
|
|
}
|
|
|
|
return { safeTop: top, safeBottom: bottom };
|
|
}
|