diff --git a/crates/chattyness-user-ui/src/components/scene_viewer.rs b/crates/chattyness-user-ui/src/components/scene_viewer.rs index d104f66..112aca4 100644 --- a/crates/chattyness-user-ui/src/components/scene_viewer.rs +++ b/crates/chattyness-user-ui/src/components/scene_viewer.rs @@ -106,10 +106,11 @@ pub fn RealmSceneViewer( let outer_container_ref = NodeRef::::new(); // Store scale factors for coordinate conversion (shared between both canvases) - let scale_x = StoredValue::new(1.0_f64); - let scale_y = StoredValue::new(1.0_f64); - let offset_x = StoredValue::new(0.0_f64); - let offset_y = StoredValue::new(0.0_f64); + // Using RwSignal for reactivity - derived signals will recompute on resize + let (scale_x, set_scale_x) = signal(1.0_f64); + let (scale_y, set_scale_y) = signal(1.0_f64); + let (offset_x, set_offset_x) = signal(0.0_f64); + let (offset_y, set_offset_y) = signal(0.0_f64); // Signal to track when scale factors have been properly calculated let (scales_ready, set_scales_ready) = signal(false); @@ -129,10 +130,10 @@ pub fn RealmSceneViewer( let click_x = ev.client_x() as f64 - rect.left(); let click_y = ev.client_y() as f64 - rect.top(); - let sx = scale_x.get_value(); - let sy = scale_y.get_value(); - let ox = offset_x.get_value(); - let oy = offset_y.get_value(); + let sx = scale_x.get(); + let sy = scale_y.get(); + let ox = offset_x.get(); + let oy = offset_y.get(); if sx > 0.0 && sy > 0.0 { let scene_x = (click_x - ox) / sx; @@ -176,7 +177,7 @@ pub fn RealmSceneViewer( // ========================================================= // Viewport Dimensions Effect - tracks outer container size - // Uses setTimeout to ensure DOM is ready after mount + // Uses window resize event to detect size changes // ========================================================= Effect::new(move |_| { // Track pan mode to re-run when it changes (affects container layout) @@ -188,46 +189,69 @@ pub fn RealmSceneViewer( let container_el: web_sys::HtmlElement = container.into(); - // Use setTimeout to ensure DOM has settled after any layout changes - let update_dimensions = Closure::once(Box::new(move || { - let width = container_el.client_width() as f64; - let height = container_el.client_height() as f64; - - if width > 0.0 && height > 0.0 { - set_viewport_dimensions.set((width, height)); + // Measure and update dimensions + let measure_container = { + let container_el = container_el.clone(); + move || { + let width = container_el.client_width() as f64; + let height = container_el.client_height() as f64; + if width > 0.0 && height > 0.0 { + set_viewport_dimensions.set((width, height)); + } } - }) as Box); + }; + + // Measure immediately + measure_container(); + + // Also measure on window resize + let resize_handler = Closure::wrap(Box::new({ + let container_el = container_el.clone(); + move |_: web_sys::Event| { + let width = container_el.client_width() as f64; + let height = container_el.client_height() as f64; + if width > 0.0 && height > 0.0 { + set_viewport_dimensions.set((width, height)); + } + } + }) as Box); let window = web_sys::window().unwrap(); - let _ = window.set_timeout_with_callback_and_timeout_and_arguments_0( - update_dimensions.as_ref().unchecked_ref(), - 150, // Slightly longer delay to ensure DOM settles + let _ = window.add_event_listener_with_callback( + "resize", + resize_handler.as_ref().unchecked_ref(), ); - update_dimensions.forget(); + + // Keep the closure alive + resize_handler.forget(); }); // Track the last settings to detect changes let last_pan_mode = Rc::new(RefCell::new(None::)); let last_zoom = Rc::new(RefCell::new(None::)); + let last_viewport = Rc::new(RefCell::new(None::<(f64, f64)>)); // ========================================================= - // Background Effect - redraws when settings change + // Background Effect - redraws when settings or viewport change // ========================================================= Effect::new(move |_| { // Track settings signals - this Effect reruns when they change let current_pan_mode = is_pan_mode.get(); let current_zoom = zoom_level.get(); + let current_viewport = viewport_dimensions.get(); let Some(canvas) = bg_canvas_ref.get() else { return; }; - // Check if we need to redraw (settings changed or first render) + // Check if we need to redraw (settings or viewport changed, or first render) let needs_redraw = { let last_pan = *last_pan_mode.borrow(); let last_z = *last_zoom.borrow(); + let last_vp = *last_viewport.borrow(); last_pan != Some(current_pan_mode) || (current_pan_mode && last_z != Some(current_zoom)) + || (!current_pan_mode && last_vp != Some(current_viewport)) }; if !needs_redraw { @@ -237,6 +261,7 @@ pub fn RealmSceneViewer( // Update last values *last_pan_mode.borrow_mut() = Some(current_pan_mode); *last_zoom.borrow_mut() = Some(current_zoom); + *last_viewport.borrow_mut() = Some(current_viewport); let canvas_el: &web_sys::HtmlCanvasElement = &canvas; let canvas_el = canvas_el.clone(); @@ -254,10 +279,10 @@ pub fn RealmSceneViewer( canvas_el.set_height(canvas_height); // Store scale factors (zoom level, no offset) - scale_x.set_value(current_zoom); - scale_y.set_value(current_zoom); - offset_x.set_value(0.0); - offset_y.set_value(0.0); + set_scale_x.set(current_zoom); + set_scale_y.set(current_zoom); + set_offset_x.set(0.0); + set_offset_y.set(0.0); // Signal that scale factors are ready set_scales_ready.set(true); @@ -323,10 +348,10 @@ pub fn RealmSceneViewer( // Store scale factors let sx = draw_width / scene_width_f; let sy = draw_height / scene_height_f; - scale_x.set_value(sx); - scale_y.set_value(sy); - offset_x.set_value(draw_x); - offset_y.set_value(draw_y); + set_scale_x.set(sx); + set_scale_y.set(sy); + set_offset_x.set(draw_x); + set_offset_y.set(draw_y); // Signal that scale factors are ready set_scales_ready.set(true); @@ -410,10 +435,10 @@ pub fn RealmSceneViewer( ctx.clear_rect(0.0, 0.0, canvas_width as f64, canvas_height as f64); // Get stored scale factors - let sx = scale_x.get_value(); - let sy = scale_y.get_value(); - let ox = offset_x.get_value(); - let oy = offset_y.get_value(); + let sx = scale_x.get(); + let sy = scale_y.get(); + let ox = offset_x.get(); + let oy = offset_y.get(); // Calculate prop size based on mode let prop_size = calculate_prop_size( @@ -715,8 +740,8 @@ pub fn RealmSceneViewer( let current_pan_mode = is_pan_mode.get(); let current_zoom = zoom_level.get(); let current_enlarge = enlarge_props.get(); - let sx = scale_x.get_value(); - let sy = scale_y.get_value(); + let sx = scale_x.get(); + let sy = scale_y.get(); // Reference scale factor for "enlarge props" mode let ref_scale = (scene_width_f / REFERENCE_WIDTH).max(scene_height_f / REFERENCE_HEIGHT); @@ -738,10 +763,10 @@ pub fn RealmSceneViewer( let text_em_size = Signal::derive(move || settings.get().text_em_size); // Create signals for scale/offset values to pass to AvatarCanvas - let scale_x_signal = Signal::derive(move || scale_x.get_value()); - let scale_y_signal = Signal::derive(move || scale_y.get_value()); - let offset_x_signal = Signal::derive(move || offset_x.get_value()); - let offset_y_signal = Signal::derive(move || offset_y.get_value()); + let scale_x_signal = Signal::derive(move || scale_x.get()); + let scale_y_signal = Signal::derive(move || scale_y.get()); + let offset_x_signal = Signal::derive(move || offset_x.get()); + let offset_y_signal = Signal::derive(move || offset_y.get()); // Create a map of members by key for efficient lookup let members_by_key = Signal::derive(move || {