class CameraControlsManager { constructor(app) { this.app = app; this.controlState = { isDragging: false, currentScale: 1, currentX: 0, currentY: 0, startX: 0, startY: 0 }; } // Setup 3D camera control event listeners setup3DCameraControls() { // View preset buttons const setupViewBtn = (id, view) => { const btn = document.getElementById(id); if (btn) { btn.addEventListener('click', () => { this.app.threeJSManager.setCameraView(view); }); } }; setupViewBtn('topViewBtn', 'top'); setupViewBtn('frontViewBtn', 'front'); setupViewBtn('sideViewBtn', 'side'); setupViewBtn('isoViewBtn', 'isometric'); // Zoom controls const zoomInBtn3D = document.getElementById('zoomInBtn3D'); const zoomOutBtn3D = document.getElementById('zoomOutBtn3D'); const resetCameraBtn = document.getElementById('resetCameraBtn'); if (zoomInBtn3D) zoomInBtn3D.addEventListener('click', () => this.app.threeJSManager.zoomIn()); if (zoomOutBtn3D) zoomOutBtn3D.addEventListener('click', () => this.app.threeJSManager.zoomOut()); if (resetCameraBtn) resetCameraBtn.addEventListener('click', () => this.app.threeJSManager.resetCamera()); // Pan controls const setupPanBtn = (id, direction) => { const btn = document.getElementById(id); if (btn) { btn.addEventListener('click', () => { this.app.threeJSManager.panCamera(direction); }); } }; setupPanBtn('panUpBtn', 'up'); setupPanBtn('panDownBtn', 'down'); setupPanBtn('panLeftBtn', 'left'); setupPanBtn('panRightBtn', 'right'); // Center button resets target const panCenterBtn = document.getElementById('panCenterBtn'); if (panCenterBtn) { panCenterBtn.addEventListener('click', () => { this.app.threeJSManager.cameraState.target.set(0, 0, 0); this.app.threeJSManager.updateCameraPosition(); }); } } // Setup draggable and scalable 3D controls setup3DControlsDragAndScale() { const controls = document.getElementById('camera3DControls'); const dragHandle = document.getElementById('dragHandle'); const scaleUpBtn = document.getElementById('scaleUpBtn'); const scaleDownBtn = document.getElementById('scaleDownBtn'); const resetPositionBtn = document.getElementById('resetPositionBtn'); const scaleIndicator = document.getElementById('scaleIndicator'); if (!controls) return; // Update scale indicator const updateScaleIndicator = () => { if (scaleIndicator) { scaleIndicator.textContent = Math.round(this.controlState.currentScale * 100) + '%'; } }; // Make the entire controls draggable const makeDraggable = (element) => { element.addEventListener('mousedown', (e) => { // Don't drag if clicking on buttons if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { return; } this.controlState.isDragging = true; this.controlState.startX = e.clientX - this.controlState.currentX; this.controlState.startY = e.clientY - this.controlState.currentY; controls.style.cursor = 'grabbing'; controls.style.userSelect = 'none'; controls.style.boxShadow = '0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'; controls.style.borderColor = '#3b82f6'; e.preventDefault(); }); }; // Mouse move handler document.addEventListener('mousemove', (e) => { if (!this.controlState.isDragging) return; this.controlState.currentX = e.clientX - this.controlState.startX; this.controlState.currentY = e.clientY - this.controlState.startY; // Update position while maintaining scale controls.style.transform = `translate(calc(-50% + ${this.controlState.currentX}px), ${this.controlState.currentY}px) scale(${this.controlState.currentScale})`; }); // Mouse up handler document.addEventListener('mouseup', () => { if (this.controlState.isDragging) { this.controlState.isDragging = false; controls.style.cursor = 'move'; controls.style.userSelect = 'none'; controls.style.boxShadow = ''; controls.style.borderColor = ''; this.saveState(); } }); // Scale controls if (scaleUpBtn) { scaleUpBtn.addEventListener('click', (e) => { e.stopPropagation(); this.controlState.currentScale = Math.min(this.controlState.currentScale + 0.1, 2.0); // Max scale 2x controls.style.transform = `translate(calc(-50% + ${this.controlState.currentX}px), ${this.controlState.currentY}px) scale(${this.controlState.currentScale})`; updateScaleIndicator(); this.saveState(); // Visual feedback scaleUpBtn.style.backgroundColor = '#dbeafe'; setTimeout(() => { scaleUpBtn.style.backgroundColor = ''; }, 150); }); } if (scaleDownBtn) { scaleDownBtn.addEventListener('click', (e) => { e.stopPropagation(); this.controlState.currentScale = Math.max(this.controlState.currentScale - 0.1, 0.5); // Min scale 0.5x controls.style.transform = `translate(calc(-50% + ${this.controlState.currentX}px), ${this.controlState.currentY}px) scale(${this.controlState.currentScale})`; updateScaleIndicator(); this.saveState(); // Visual feedback scaleDownBtn.style.backgroundColor = '#dbeafe'; setTimeout(() => { scaleDownBtn.style.backgroundColor = ''; }, 150); }); } // Reset position and scale if (resetPositionBtn) { resetPositionBtn.addEventListener('click', (e) => { e.stopPropagation(); this.controlState.currentX = 0; this.controlState.currentY = 0; this.controlState.currentScale = 1; controls.style.transform = `translate(-50%, 0) scale(1)`; updateScaleIndicator(); this.saveState(); // Visual feedback resetPositionBtn.style.backgroundColor = '#bfdbfe'; setTimeout(() => { resetPositionBtn.style.backgroundColor = ''; }, 150); }); } // Make controls draggable makeDraggable(controls); // Restore saved state this.restoreState(); // Initialize scale indicator updateScaleIndicator(); } // Save state to localStorage saveState() { localStorage.setItem('3d-controls-state', JSON.stringify({ x: this.controlState.currentX, y: this.controlState.currentY, scale: this.controlState.currentScale })); } // Restore state from localStorage restoreState() { const savedState = localStorage.getItem('3d-controls-state'); if (savedState) { try { const state = JSON.parse(savedState); this.controlState.currentX = state.x || 0; this.controlState.currentY = state.y || 0; this.controlState.currentScale = state.scale || 1; const controls = document.getElementById('camera3DControls'); if (controls) { controls.style.transform = `translate(calc(-50% + ${this.controlState.currentX}px), ${this.controlState.currentY}px) scale(${this.controlState.currentScale})`; } } catch (e) { console.warn('Failed to restore 3D controls state:', e); } } } }