Files

200 lines
6.9 KiB
JavaScript

// views/split_view.js — Split-View: zwei nebeneinander gerenderte Views (links/rechts)
import { DEFAULT_TOP_INSET } from '../core/theme.js';
import {
CHILD_VIEWS,
computeMultiViewBaseLayout,
drawEmptyPlot,
drawMetersPanel,
readMultiViewMeters,
sanitizeChildId,
sanitizePlotId,
withClippedSubRect,
} from './multi_view_shared.js';
export const id = 'split-view';
const CONTENT_TOP = DEFAULT_TOP_INSET;
const CONTENT_BOTTOM = 0;
function readSplitMeters(CONFIG) {
return readMultiViewMeters(CONFIG, 'SPLIT_VIEW');
}
function computeSplitLayout(rect, slots) {
return computeMultiViewBaseLayout(rect, slots, CONTENT_TOP, CONTENT_BOTTOM);
}
function destroyChild(child) {
if (!child || child.id === 'none') return;
const mod = CHILD_VIEWS[child.id];
if (mod && typeof mod.destroy === 'function') {
try { mod.destroy(child.state); } catch (e) { console.warn('Split child destroy error:', e); }
}
}
function initChild(env, childId) {
const id = sanitizeChildId(childId, 'none');
if (id === 'none') return { id: 'none', state: {} };
const mod = CHILD_VIEWS[id];
const state = (mod && typeof mod.init === 'function') ? (mod.init(env) || {}) : {};
return { id, state };
}
function resizeChild(env, rect, child) {
if (!child || child.id === 'none') return;
const mod = CHILD_VIEWS[child.id];
if (!mod || typeof mod.resize !== 'function') return;
try { mod.resize({ rect }, child.state); } catch (e) { console.warn('Split child resize error:', e); }
}
async function renderChild(env, rect, child) {
const { ctx: g } = env;
if (!child || child.id === 'none') return;
const mod = CHILD_VIEWS[child.id];
if (!mod || typeof mod.render !== 'function') return;
await withClippedSubRect(g, rect, async () => {
const plotOnlySlots = (viewId) => {
if (viewId === 'peak-history' || viewId === 'classic-needles' || viewId === 'panel') {
const configured = env?.slots?.(viewId);
if (Array.isArray(configured) && configured.length) return configured;
}
if (viewId === 'peak-history') return ['ppm-din'];
if (viewId === 'classic-needles') return ['vu'];
if (viewId === 'panel') return ['vu', 'ppm-ebu', 'ppm-din', 'tp', 'rms'];
return ['none'];
};
const subEnv = Object.assign({}, env, {
rect: { x: 0, y: 0, w: rect.w, h: rect.h },
embedded: true,
containerView: 'split-view',
slots: plotOnlySlots,
embeddedOffsetX: rect.x,
embeddedOffsetY: rect.y,
});
await mod.render(subEnv, child.state);
});
}
export function init(env) {
const leftId = sanitizePlotId(env?.config?.SPLIT_VIEW_LEFT, 'phase-wheel');
const rightId = sanitizePlotId(env?.config?.SPLIT_VIEW_RIGHT, 'realtime');
const state = {
staticLayers: new Map(),
left: initChild(env, leftId),
right: initChild(env, rightId),
popup: initChild(env, 'none'),
lastLeftId: leftId,
lastRightId: rightId,
lastPopupId: 'none',
lastRectSig: '',
};
return state;
}
export function destroy(state) {
if (state) state.staticLayers = null;
destroyChild(state?.left);
destroyChild(state?.right);
destroyChild(state?.popup);
}
export function resize({ rect }, state) {
if (!state || !rect) return;
const sig = `${rect.w}x${rect.h}`;
if (state.lastRectSig === sig) return;
state.lastRectSig = sig;
const layout = computeLayout(rect);
resizeChild(null, { x: 0, y: 0, w: layout.left.w, h: layout.left.h }, state.left);
resizeChild(null, { x: 0, y: 0, w: layout.right.w, h: layout.right.h }, state.right);
resizeChild(null, { x: 0, y: 0, w: rect.w, h: rect.h }, state.popup);
}
function computeLayout(rect) {
const gap = OUTER_GAP;
const w = Math.max(0, rect.w);
const h = Math.max(0, rect.h);
const half = Math.floor((w - gap) / 2);
const left = { x: 0, y: 0, w: Math.max(0, half), h };
const right = { x: Math.max(0, half + gap), y: 0, w: Math.max(0, w - (half + gap)), h };
return { left, right, gap };
}
export async function render(env, state) {
const { ctx: g, rect, config: CONFIG } = env;
const popupCfg = env?.splitPopup;
const popupWanted = popupCfg && popupCfg.open ? sanitizeChildId(popupCfg.viewId, 'none') : 'none';
// Popup-Modus: rendere die View in Originalgröße (vollflächig),
// ohne Split-Hintergrund/Layout. So sieht es exakt wie die Einzel-View aus.
if (popupWanted !== 'none' && state) {
if (state.lastPopupId !== popupWanted) {
destroyChild(state.popup);
state.popup = initChild(env, popupWanted);
state.lastPopupId = popupWanted;
resizeChild(null, { x: 0, y: 0, w: rect.w, h: rect.h }, state.popup);
}
state._splitHit = {
contentY: 0,
plots: null,
meters: [],
popupBox: { x: 0, y: 0, w: rect.w, h: rect.h },
};
const mod = CHILD_VIEWS[state.popup.id];
if (mod && typeof mod.render === 'function') {
const subEnv = Object.assign({}, env, {
rect: { x: 0, y: 0, w: rect.w, h: rect.h },
embedded: false,
});
await mod.render(subEnv, state.popup.state);
}
return;
}
if (state && state.lastPopupId !== 'none') {
destroyChild(state.popup);
state.popup = initChild(env, 'none');
state.lastPopupId = 'none';
}
const desiredLeftId = sanitizePlotId(env?.config?.SPLIT_VIEW_LEFT, 'phase-wheel');
const desiredRightId = sanitizePlotId(env?.config?.SPLIT_VIEW_RIGHT, 'realtime');
if (state.lastLeftId !== desiredLeftId) {
destroyChild(state.left);
state.left = initChild(env, desiredLeftId);
state.lastLeftId = desiredLeftId;
}
if (state.lastRightId !== desiredRightId) {
destroyChild(state.right);
state.right = initChild(env, desiredRightId);
state.lastRightId = desiredRightId;
}
const slots = readSplitMeters(CONFIG);
const layout = computeSplitLayout(rect, slots);
const leftPlotRect = layout?.plots?.left || { x: 0, y: 0, w: Math.floor(rect.w / 2), h: rect.h };
const rightPlotRect = layout?.plots?.right || { x: Math.floor(rect.w / 2), y: 0, w: rect.w - Math.floor(rect.w / 2), h: rect.h };
if (state) {
state._splitHit = {
contentY: 0,
plots: {
left: { ...leftPlotRect, viewId: desiredLeftId },
right: { ...rightPlotRect, viewId: desiredRightId },
},
meters: [layout?.meters?.left, layout?.meters?.center, layout?.meters?.right].filter(Boolean).map((r) => ({ ...r })),
popupBox: null,
};
}
if (state.left?.id === 'none') drawEmptyPlot(state, g, leftPlotRect, 'Links: (leer)');
else await renderChild(env, leftPlotRect, state.left);
if (state.right?.id === 'none') drawEmptyPlot(state, g, rightPlotRect, 'Rechts: (leer)');
else await renderChild(env, rightPlotRect, state.right);
if (layout?.meters?.left) await drawMetersPanel(env, state, layout.meters.left, layout.meterIds.left, layout.slotW);
if (layout?.meters?.center) await drawMetersPanel(env, state, layout.meters.center, layout.meterIds.center, layout.slotW);
if (layout?.meters?.right) await drawMetersPanel(env, state, layout.meters.right, layout.meterIds.right, layout.slotW);
}