diff --git a/crates/chattyness-user-ui/src/components/avatar.rs b/crates/chattyness-user-ui/src/components/avatar.rs index f05c698..2f7d3d2 100644 --- a/crates/chattyness-user-ui/src/components/avatar.rs +++ b/crates/chattyness-user-ui/src/components/avatar.rs @@ -354,12 +354,8 @@ pub fn Avatar( /// Opacity for fade-out animation (0.0 to 1.0, default 1.0). #[prop(default = 1.0)] opacity: f64, - /// Scene width in scene coordinates (for boundary calculations). - #[prop(optional)] - scene_width: Option>, - /// Scene height in scene coordinates (for boundary calculations). - #[prop(optional)] - scene_height: Option>, + /// Screen bounds for clamping (computed once at scene level). + screen_bounds: Signal, /// Active speech bubble for this avatar (None if not speaking). #[prop(optional)] active_bubble: Option>>, @@ -397,12 +393,8 @@ pub fn Avatar( &m.avatar.emotion_layer, ); - // Get scene dimensions (use large defaults if not provided) - let sw = scene_width.map(|s| s.get()).unwrap_or(10000.0); - let sh = scene_height.map(|s| s.get()).unwrap_or(10000.0); - - // Compute screen boundaries and avatar screen position - let boundaries = ScreenBounds::from_transform(sw, sh, sx, sy, ox, oy); + // Use passed-in screen bounds (computed once at scene level) + let boundaries = screen_bounds.get(); let avatar_screen_x = m.member.position_x * sx + ox; let avatar_screen_y = m.member.position_y * sy + oy; @@ -513,16 +505,14 @@ pub fn Avatar( &m.avatar.emotion_layer, ); - // Get scene dimensions and transform parameters + // Get transform parameters for computing avatar screen position let sx = scale_x.get(); let sy = scale_y.get(); let ox = offset_x.get(); let oy = offset_y.get(); - let sw = scene_width.map(|s| s.get()).unwrap_or(10000.0); - let sh = scene_height.map(|s| s.get()).unwrap_or(10000.0); - // Create unified layout - same calculation as style closure - let boundaries = ScreenBounds::from_transform(sw, sh, sx, sy, ox, oy); + // Use passed-in screen bounds (computed once at scene level) + let boundaries = screen_bounds.get(); let avatar_screen_x = m.member.position_x * sx + ox; let avatar_screen_y = m.member.position_y * sy + oy; diff --git a/crates/chattyness-user-ui/src/components/scene_viewer.rs b/crates/chattyness-user-ui/src/components/scene_viewer.rs index b87c99a..0adb53a 100644 --- a/crates/chattyness-user-ui/src/components/scene_viewer.rs +++ b/crates/chattyness-user-ui/src/components/scene_viewer.rs @@ -22,7 +22,7 @@ use uuid::Uuid; use chattyness_db::models::{ChannelMemberWithAvatar, LooseProp, Scene}; -use super::avatar::{Avatar, member_key}; +use super::avatar::{Avatar, ScreenBounds, member_key}; #[cfg(feature = "hydrate")] use super::canvas_utils::hit_test_canvas; use super::chat_types::ActiveBubble; @@ -450,6 +450,18 @@ pub fn RealmSceneViewer( let scene_width_signal = Signal::derive(move || scene_width_f); let scene_height_signal = Signal::derive(move || scene_height_f); + // Compute ScreenBounds once for all avatars (instead of each avatar computing it) + let screen_bounds = Signal::derive(move || { + ScreenBounds::from_transform( + scene_width_signal.get(), + scene_height_signal.get(), + scale_x_signal.get(), + scale_y_signal.get(), + offset_x_signal.get(), + offset_y_signal.get(), + ) + }); + let members_by_key = Signal::derive(move || { sorted_members.get().into_iter().enumerate() .map(|(idx, m)| (member_key(&m), (idx, m))) @@ -520,8 +532,7 @@ pub fn RealmSceneViewer( prop_size=prop_size z_index=z text_em_size=text_em_size - scene_width=scene_width_signal - scene_height=scene_height_signal + screen_bounds=screen_bounds active_bubble=bubble_signal /> } @@ -553,8 +564,7 @@ pub fn RealmSceneViewer( z_index=5 text_em_size=text_em_size opacity=opacity - scene_width=scene_width_signal - scene_height=scene_height_signal + screen_bounds=screen_bounds /> }) } else { None }