// 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); }