//! Settings popup component for scene viewer configuration. use leptos::ev::MouseEvent; use leptos::prelude::*; use super::settings::{calculate_min_zoom, ViewerSettings, ZOOM_MAX, ZOOM_STEP}; /// Settings popup component for scene viewer configuration. /// /// Provides controls for: /// - Panning mode (native resolution with scroll) /// - Zoom level (when panning enabled) /// - Enlarge props (when panning disabled) /// /// Props: /// - `open`: Signal controlling visibility /// - `settings`: RwSignal for viewer settings (read/write) /// - `on_close`: Callback when popup should close #[component] pub fn SettingsPopup( #[prop(into)] open: Signal, settings: RwSignal, on_close: Callback<()>, /// Scene dimensions (width, height) for calculating min zoom. #[prop(default = (800.0, 600.0))] scene_dimensions: (f64, f64), /// Viewport dimensions signal for calculating min zoom. #[prop(into, optional)] viewport_dimensions: Option>, ) -> impl IntoView { // Derived signals for each setting let panning = Signal::derive(move || settings.get().panning_enabled); let zoom = Signal::derive(move || settings.get().zoom_level); let enlarge = Signal::derive(move || settings.get().enlarge_props); // Calculate effective minimum zoom based on scene/viewport dimensions let effective_min_zoom = Signal::derive(move || { let (scene_w, scene_h) = scene_dimensions; let (vp_w, vp_h) = viewport_dimensions .map(|s| s.get()) .unwrap_or((800.0, 600.0)); calculate_min_zoom(scene_w, scene_h, vp_w, vp_h) }); // Toggle handlers let on_panning_toggle = move |_| { settings.update(|s| { s.panning_enabled = !s.panning_enabled; // Reset scroll when disabling pan mode if !s.panning_enabled { s.reset_scroll(); } s.save(); }); }; let on_enlarge_toggle = move |_| { settings.update(|s| { s.enlarge_props = !s.enlarge_props; s.save(); }); }; let on_zoom_decrease = move |_| { let min_zoom = effective_min_zoom.get(); settings.update(|s| { s.adjust_zoom_with_min(-ZOOM_STEP, min_zoom); s.save(); }); }; let on_zoom_increase = move |_| { let min_zoom = effective_min_zoom.get(); settings.update(|s| { s.adjust_zoom_with_min(ZOOM_STEP, min_zoom); s.save(); }); }; let on_zoom_input = move |ev| { let val: f64 = event_target_value(&ev).parse().unwrap_or(1.0); let min_zoom = effective_min_zoom.get(); settings.update(|s| { s.zoom_level = val.clamp(min_zoom, ZOOM_MAX); s.save(); }); }; // Handle escape key to close #[cfg(feature = "hydrate")] { use leptos::web_sys; use wasm_bindgen::{closure::Closure, JsCast}; Effect::new(move |_| { if !open.get() { return; } let on_close_clone = on_close.clone(); let closure = Closure::::new(move |ev: web_sys::KeyboardEvent| { if ev.key() == "Escape" { on_close_clone.run(()); } }); if let Some(window) = web_sys::window() { let _ = window .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()); } // Intentionally not cleaning up - closure lives for session closure.forget(); }); } let on_close_backdrop = on_close.clone(); let on_close_button = on_close.clone(); view! {