1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
4import React, { useRef, useState } from 'react';
5import { Box, IconButton, Tooltip } from '@mui/material';
6import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
7import { Canvas } from '@react-three/fiber';
12 type ImperativePanelGroupHandle,
13} from 'react-resizable-panels';
14import WorldView from '@/components/WorldView';
15import SceneTree from '@/components/SceneTree';
16import PropertyInspector from '@/components/PropertyInspector';
17import Timeline from '@/components/Timeline';
18import ViewControls from '@/components/ViewControls';
19import { type MapControls as MapControlsImpl } from 'three-stdlib';
20import { ScaleManager } from '@/components/ScaleManager';
21import { useScenarioStore } from '@/stores/scenarioStore';
22import { fersColors } from '@/theme';
25 * A styled resize handle for the resizable panels.
27function ResizeHandle() {
34 backgroundColor: 'divider',
35 transition: 'background-color 0.2s ease-in-out',
36 '&[data-resize-handle-state="drag"]': {
37 backgroundColor: 'primary.main',
40 backgroundColor: 'primary.light',
49 * ScenarioView is the primary workbench for building and visualizing 3D scenes.
51export const ScenarioView = React.memo(function ScenarioView() {
52 const isSimulating = useScenarioStore((state) => state.isSimulating);
53 const [isInspectorCollapsed, setIsInspectorCollapsed] = useState(false);
54 const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
56 // 1. Lift refs to this level to bridge Logic (Canvas) and UI (DOM)
57 const controlsRef = useRef<MapControlsImpl>(null);
58 const scaleLabelRef = useRef<HTMLDivElement>(null);
59 const scaleBarRef = useRef<HTMLDivElement>(null);
61 const handleExpandInspector = () => {
62 // Restore panels to their default sizes: [SceneTree, Main, Inspector]
63 panelGroupRef.current?.setLayout([25, 50, 25]);
72 position: 'relative', // Establish positioning context
73 pointerEvents: isSimulating ? 'none' : 'auto',
74 opacity: isSimulating ? 0.5 : 1,
75 transition: 'opacity 0.3s ease-in-out',
77 WebkitUserSelect: 'none',
80 <PanelGroup direction="horizontal" ref={panelGroupRef}>
81 <Panel id="scene-tree" defaultSize={25} minSize={20} order={1}>
87 <Panel id="main-content" minSize={30} order={2}>
92 flexDirection: 'column',
93 minWidth: 0, // Allow flex item to shrink below content size
94 overflow: 'hidden', // Prevent overflow
100 position: 'relative',
101 minHeight: 0, // Allow flex item to shrink
109 position: [100, 100, 100],
115 logarithmicDepthBuffer: true,
120 args={[fersColors.background.canvas]}
122 {/* Pass controlsRef down to WorldView */}
123 <WorldView controlsRef={controlsRef} />
125 {/* Logic-only component for Scale Bar calculations */}
127 controlsRef={controlsRef}
128 labelRef={scaleLabelRef}
133 {/* View Controls (Top-Left) */}
136 position: 'absolute',
145 {/* Scale Bar Overlay (Bottom-Left) */}
148 position: 'absolute',
152 pointerEvents: 'none',
154 flexDirection: 'column',
155 alignItems: 'center',
156 textShadow: '0px 1px 2px rgba(0,0,0,0.8)',
162 color: fersColors.text.primary,
166 fontFamily: 'Roboto, sans-serif',
175 border: `1px solid ${fersColors.text.primary}`,
187 borderColor: 'divider',
198 id="property-inspector"
201 onCollapse={() => setIsInspectorCollapsed(true)}
202 onExpand={() => setIsInspectorCollapsed(false)}
207 <PropertyInspector />
211 {isInspectorCollapsed && (
212 <Tooltip title="Show Properties">
214 onClick={handleExpandInspector}
216 position: 'absolute',
219 transform: 'translateY(-50%)',
221 bgcolor: 'background.paper',
223 borderColor: 'divider',
225 borderTopRightRadius: 0,
226 borderBottomRightRadius: 0,
228 bgcolor: 'action.hover',