add transform signal to reduce 4 signals into one responsibility
This commit is contained in:
parent
30722bed8f
commit
a4cf8d3df4
4 changed files with 83 additions and 86 deletions
|
|
@ -75,6 +75,47 @@ impl ScreenBounds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Scene Transform - Exported for use by other components
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Transform parameters for converting scene coordinates to screen coordinates.
|
||||||
|
///
|
||||||
|
/// Used by Avatar and LoosePropCanvas to position elements on screen.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SceneTransform {
|
||||||
|
/// X scale factor (screen pixels per scene unit)
|
||||||
|
pub scale_x: f64,
|
||||||
|
/// Y scale factor (screen pixels per scene unit)
|
||||||
|
pub scale_y: f64,
|
||||||
|
/// X offset - screen pixel position of scene origin (0,0)
|
||||||
|
pub offset_x: f64,
|
||||||
|
/// Y offset - screen pixel position of scene origin (0,0)
|
||||||
|
pub offset_y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SceneTransform {
|
||||||
|
/// Convert scene coordinates to screen pixel coordinates.
|
||||||
|
pub fn to_screen(&self, scene_x: f64, scene_y: f64) -> (f64, f64) {
|
||||||
|
(
|
||||||
|
scene_x * self.scale_x + self.offset_x,
|
||||||
|
scene_y * self.scale_y + self.offset_y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert screen pixel coordinates to scene coordinates.
|
||||||
|
/// Returns None if scale is zero (invalid transform).
|
||||||
|
pub fn to_scene(&self, screen_x: f64, screen_y: f64) -> Option<(f64, f64)> {
|
||||||
|
if self.scale_x == 0.0 || self.scale_y == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((
|
||||||
|
(screen_x - self.offset_x) / self.scale_x,
|
||||||
|
(screen_y - self.offset_y) / self.scale_y,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Base text size multiplier. Text at 100% slider = base_sizes * 1.4
|
/// Base text size multiplier. Text at 100% slider = base_sizes * 1.4
|
||||||
const BASE_TEXT_SCALE: f64 = 1.4;
|
const BASE_TEXT_SCALE: f64 = 1.4;
|
||||||
|
|
||||||
|
|
@ -336,14 +377,8 @@ enum BubblePosition {
|
||||||
pub fn Avatar(
|
pub fn Avatar(
|
||||||
/// The member data for this avatar (as a signal for reactive updates).
|
/// The member data for this avatar (as a signal for reactive updates).
|
||||||
member: Signal<ChannelMemberWithAvatar>,
|
member: Signal<ChannelMemberWithAvatar>,
|
||||||
/// X scale factor for coordinate conversion.
|
/// Transform for converting scene coordinates to screen coordinates.
|
||||||
scale_x: Signal<f64>,
|
transform: Signal<SceneTransform>,
|
||||||
/// Y scale factor for coordinate conversion.
|
|
||||||
scale_y: Signal<f64>,
|
|
||||||
/// X offset for coordinate conversion.
|
|
||||||
offset_x: Signal<f64>,
|
|
||||||
/// Y offset for coordinate conversion.
|
|
||||||
offset_y: Signal<f64>,
|
|
||||||
/// Size of the avatar in pixels.
|
/// Size of the avatar in pixels.
|
||||||
prop_size: Signal<f64>,
|
prop_size: Signal<f64>,
|
||||||
/// Z-index for stacking order (higher = on top).
|
/// Z-index for stacking order (higher = on top).
|
||||||
|
|
@ -379,10 +414,7 @@ pub fn Avatar(
|
||||||
let compute_layout = move || {
|
let compute_layout = move || {
|
||||||
let m = member.get();
|
let m = member.get();
|
||||||
let ps = prop_size.get();
|
let ps = prop_size.get();
|
||||||
let sx = scale_x.get();
|
let t = transform.get();
|
||||||
let sy = scale_y.get();
|
|
||||||
let ox = offset_x.get();
|
|
||||||
let oy = offset_y.get();
|
|
||||||
let te = text_em_size.get();
|
let te = text_em_size.get();
|
||||||
|
|
||||||
// Calculate content bounds for centering on actual content
|
// Calculate content bounds for centering on actual content
|
||||||
|
|
@ -393,10 +425,10 @@ pub fn Avatar(
|
||||||
&m.avatar.emotion_layer,
|
&m.avatar.emotion_layer,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use passed-in screen bounds (computed once at scene level)
|
// Use passed-in screen bounds and transform (computed once at scene level)
|
||||||
let boundaries = screen_bounds.get();
|
let boundaries = screen_bounds.get();
|
||||||
let avatar_screen_x = m.member.position_x * sx + ox;
|
let (avatar_screen_x, avatar_screen_y) =
|
||||||
let avatar_screen_y = m.member.position_y * sy + oy;
|
t.to_screen(m.member.position_x, m.member.position_y);
|
||||||
|
|
||||||
// Create unified layout - all calculations happen in one place
|
// Create unified layout - all calculations happen in one place
|
||||||
CanvasLayout::new(
|
CanvasLayout::new(
|
||||||
|
|
@ -505,16 +537,11 @@ pub fn Avatar(
|
||||||
&m.avatar.emotion_layer,
|
&m.avatar.emotion_layer,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get transform parameters for computing avatar screen position
|
// Use passed-in transform and screen bounds (computed once at scene level)
|
||||||
let sx = scale_x.get();
|
let t = transform.get();
|
||||||
let sy = scale_y.get();
|
|
||||||
let ox = offset_x.get();
|
|
||||||
let oy = offset_y.get();
|
|
||||||
|
|
||||||
// Use passed-in screen bounds (computed once at scene level)
|
|
||||||
let boundaries = screen_bounds.get();
|
let boundaries = screen_bounds.get();
|
||||||
let avatar_screen_x = m.member.position_x * sx + ox;
|
let (avatar_screen_x, avatar_screen_y) =
|
||||||
let avatar_screen_y = m.member.position_y * sy + oy;
|
t.to_screen(m.member.position_x, m.member.position_y);
|
||||||
|
|
||||||
let layout = CanvasLayout::new(
|
let layout = CanvasLayout::new(
|
||||||
&content_bounds,
|
&content_bounds,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use chattyness_db::models::LooseProp;
|
use chattyness_db::models::LooseProp;
|
||||||
|
|
||||||
|
use super::avatar::SceneTransform;
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
pub use super::canvas_utils::hit_test_canvas;
|
pub use super::canvas_utils::hit_test_canvas;
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
|
|
@ -29,14 +30,8 @@ pub fn loose_prop_key(p: &LooseProp) -> Uuid {
|
||||||
pub fn LoosePropCanvas(
|
pub fn LoosePropCanvas(
|
||||||
/// The prop data (as a signal for reactive updates).
|
/// The prop data (as a signal for reactive updates).
|
||||||
prop: Signal<LooseProp>,
|
prop: Signal<LooseProp>,
|
||||||
/// X scale factor for coordinate conversion.
|
/// Transform for converting scene coordinates to screen coordinates.
|
||||||
scale_x: Signal<f64>,
|
transform: Signal<SceneTransform>,
|
||||||
/// Y scale factor for coordinate conversion.
|
|
||||||
scale_y: Signal<f64>,
|
|
||||||
/// X offset for coordinate conversion.
|
|
||||||
offset_x: Signal<f64>,
|
|
||||||
/// Y offset for coordinate conversion.
|
|
||||||
offset_y: Signal<f64>,
|
|
||||||
/// Base prop size in screen pixels (already includes viewport scaling).
|
/// Base prop size in screen pixels (already includes viewport scaling).
|
||||||
base_prop_size: Signal<f64>,
|
base_prop_size: Signal<f64>,
|
||||||
/// Z-index for stacking order.
|
/// Z-index for stacking order.
|
||||||
|
|
@ -47,10 +42,7 @@ pub fn LoosePropCanvas(
|
||||||
// Reactive style for CSS positioning (GPU-accelerated transforms)
|
// Reactive style for CSS positioning (GPU-accelerated transforms)
|
||||||
let style = move || {
|
let style = move || {
|
||||||
let p = prop.get();
|
let p = prop.get();
|
||||||
let sx = scale_x.get();
|
let t = transform.get();
|
||||||
let sy = scale_y.get();
|
|
||||||
let ox = offset_x.get();
|
|
||||||
let oy = offset_y.get();
|
|
||||||
let base_size = base_prop_size.get();
|
let base_size = base_prop_size.get();
|
||||||
|
|
||||||
// Calculate rendered prop size
|
// Calculate rendered prop size
|
||||||
|
|
@ -58,8 +50,7 @@ pub fn LoosePropCanvas(
|
||||||
let prop_size = base_size * prop_scale_ratio * p.scale as f64;
|
let prop_size = base_size * prop_scale_ratio * p.scale as f64;
|
||||||
|
|
||||||
// Screen position (center of prop)
|
// Screen position (center of prop)
|
||||||
let screen_x = p.position_x * sx + ox;
|
let (screen_x, screen_y) = t.to_screen(p.position_x, p.position_y);
|
||||||
let screen_y = p.position_y * sy + oy;
|
|
||||||
|
|
||||||
// Canvas positioned at top-left corner
|
// Canvas positioned at top-left corner
|
||||||
let canvas_x = screen_x - prop_size / 2.0;
|
let canvas_x = screen_x - prop_size / 2.0;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use chattyness_db::models::{ChannelMemberWithAvatar, LooseProp, Scene};
|
use chattyness_db::models::{ChannelMemberWithAvatar, LooseProp, Scene};
|
||||||
|
|
||||||
use super::avatar::{Avatar, ScreenBounds, member_key};
|
use super::avatar::{Avatar, ScreenBounds, SceneTransform, member_key};
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
use super::canvas_utils::hit_test_canvas;
|
use super::canvas_utils::hit_test_canvas;
|
||||||
use super::chat_types::ActiveBubble;
|
use super::chat_types::ActiveBubble;
|
||||||
|
|
@ -443,22 +443,25 @@ pub fn RealmSceneViewer(
|
||||||
});
|
});
|
||||||
|
|
||||||
let text_em_size = Signal::derive(move || settings.get().text_em_size);
|
let text_em_size = Signal::derive(move || settings.get().text_em_size);
|
||||||
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());
|
|
||||||
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)
|
// Compute SceneTransform once for all avatars and props
|
||||||
|
let scene_transform = Signal::derive(move || SceneTransform {
|
||||||
|
scale_x: scale_x.get(),
|
||||||
|
scale_y: scale_y.get(),
|
||||||
|
offset_x: offset_x.get(),
|
||||||
|
offset_y: offset_y.get(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compute ScreenBounds once for all avatars (for clamping)
|
||||||
let screen_bounds = Signal::derive(move || {
|
let screen_bounds = Signal::derive(move || {
|
||||||
|
let t = scene_transform.get();
|
||||||
ScreenBounds::from_transform(
|
ScreenBounds::from_transform(
|
||||||
scene_width_signal.get(),
|
scene_width_f,
|
||||||
scene_height_signal.get(),
|
scene_height_f,
|
||||||
scale_x_signal.get(),
|
t.scale_x,
|
||||||
scale_y_signal.get(),
|
t.scale_y,
|
||||||
offset_x_signal.get(),
|
t.offset_x,
|
||||||
offset_y_signal.get(),
|
t.offset_y,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -494,10 +497,7 @@ pub fn RealmSceneViewer(
|
||||||
view! {
|
view! {
|
||||||
<LoosePropCanvas
|
<LoosePropCanvas
|
||||||
prop=prop_signal
|
prop=prop_signal
|
||||||
scale_x=scale_x_signal
|
transform=scene_transform
|
||||||
scale_y=scale_y_signal
|
|
||||||
offset_x=offset_x_signal
|
|
||||||
offset_y=offset_y_signal
|
|
||||||
base_prop_size=prop_size
|
base_prop_size=prop_size
|
||||||
z_index=5
|
z_index=5
|
||||||
/>
|
/>
|
||||||
|
|
@ -525,10 +525,7 @@ pub fn RealmSceneViewer(
|
||||||
view! {
|
view! {
|
||||||
<Avatar
|
<Avatar
|
||||||
member=member_signal
|
member=member_signal
|
||||||
scale_x=scale_x_signal
|
transform=scene_transform
|
||||||
scale_y=scale_y_signal
|
|
||||||
offset_x=offset_x_signal
|
|
||||||
offset_y=offset_y_signal
|
|
||||||
prop_size=prop_size
|
prop_size=prop_size
|
||||||
z_index=z
|
z_index=z
|
||||||
text_em_size=text_em_size
|
text_em_size=text_em_size
|
||||||
|
|
@ -556,10 +553,7 @@ pub fn RealmSceneViewer(
|
||||||
Some(view! {
|
Some(view! {
|
||||||
<Avatar
|
<Avatar
|
||||||
member=member_signal
|
member=member_signal
|
||||||
scale_x=scale_x_signal
|
transform=scene_transform
|
||||||
scale_y=scale_y_signal
|
|
||||||
offset_x=offset_x_signal
|
|
||||||
offset_y=offset_y_signal
|
|
||||||
prop_size=prop_size
|
prop_size=prop_size
|
||||||
z_index=5
|
z_index=5
|
||||||
text_em_size=text_em_size
|
text_em_size=text_em_size
|
||||||
|
|
@ -736,10 +730,7 @@ pub fn RealmSceneViewer(
|
||||||
prop_scale=Signal::derive(move || move_mode_prop_scale.get())
|
prop_scale=Signal::derive(move || move_mode_prop_scale.get())
|
||||||
loose_props=loose_props
|
loose_props=loose_props
|
||||||
prop_size=prop_size
|
prop_size=prop_size
|
||||||
scale_x=scale_x_signal
|
transform=scene_transform
|
||||||
scale_y=scale_y_signal
|
|
||||||
offset_x=offset_x_signal
|
|
||||||
offset_y=offset_y_signal
|
|
||||||
on_apply=on_prop_move.unwrap_or_else(|| Callback::new(|_| {}))
|
on_apply=on_prop_move.unwrap_or_else(|| Callback::new(|_| {}))
|
||||||
on_cancel=Callback::new(move |_: ()| {
|
on_cancel=Callback::new(move |_: ()| {
|
||||||
set_move_mode_active.set(false);
|
set_move_mode_active.set(false);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use chattyness_db::models::LooseProp;
|
use chattyness_db::models::LooseProp;
|
||||||
|
|
||||||
|
use super::super::avatar::SceneTransform;
|
||||||
use super::super::settings::{BASE_AVATAR_SCALE, BASE_PROP_SCALE};
|
use super::super::settings::{BASE_AVATAR_SCALE, BASE_PROP_SCALE};
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
use super::super::canvas_utils::normalize_asset_path;
|
use super::super::canvas_utils::normalize_asset_path;
|
||||||
|
|
@ -146,10 +147,7 @@ pub fn MoveOverlay(
|
||||||
#[prop(into)] prop_scale: Signal<f32>,
|
#[prop(into)] prop_scale: Signal<f32>,
|
||||||
#[prop(into)] loose_props: Signal<Vec<LooseProp>>,
|
#[prop(into)] loose_props: Signal<Vec<LooseProp>>,
|
||||||
#[prop(into)] prop_size: Signal<f64>,
|
#[prop(into)] prop_size: Signal<f64>,
|
||||||
#[prop(into)] scale_x: Signal<f64>,
|
#[prop(into)] transform: Signal<SceneTransform>,
|
||||||
#[prop(into)] scale_y: Signal<f64>,
|
|
||||||
#[prop(into)] offset_x: Signal<f64>,
|
|
||||||
#[prop(into)] offset_y: Signal<f64>,
|
|
||||||
#[prop(optional)] on_apply: Option<Callback<(Uuid, f64, f64)>>,
|
#[prop(optional)] on_apply: Option<Callback<(Uuid, f64, f64)>>,
|
||||||
#[prop(optional)] on_cancel: Option<Callback<()>>,
|
#[prop(optional)] on_cancel: Option<Callback<()>>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
|
|
@ -189,14 +187,8 @@ pub fn MoveOverlay(
|
||||||
let viewer_x = mouse_x - rect.left();
|
let viewer_x = mouse_x - rect.left();
|
||||||
let viewer_y = mouse_y - rect.top();
|
let viewer_y = mouse_y - rect.top();
|
||||||
|
|
||||||
let sx = scale_x.get();
|
let t = transform.get();
|
||||||
let sy = scale_y.get();
|
if let Some((scene_x, scene_y)) = t.to_scene(viewer_x, viewer_y) {
|
||||||
let ox = offset_x.get();
|
|
||||||
let oy = offset_y.get();
|
|
||||||
|
|
||||||
if sx > 0.0 && sy > 0.0 {
|
|
||||||
let scene_x = (viewer_x - ox) / sx;
|
|
||||||
let scene_y = (viewer_y - oy) / sy;
|
|
||||||
preview_position.set((scene_x, scene_y));
|
preview_position.set((scene_x, scene_y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -241,10 +233,7 @@ pub fn MoveOverlay(
|
||||||
{
|
{
|
||||||
if let Some(ref prop) = prop_data {
|
if let Some(ref prop) = prop_data {
|
||||||
let (preview_x, preview_y) = preview_position.get();
|
let (preview_x, preview_y) = preview_position.get();
|
||||||
let sx = scale_x.get();
|
let t = transform.get();
|
||||||
let sy = scale_y.get();
|
|
||||||
let ox = offset_x.get();
|
|
||||||
let oy = offset_y.get();
|
|
||||||
|
|
||||||
// Get scene canvas position in viewport (inner container with actual scene)
|
// Get scene canvas position in viewport (inner container with actual scene)
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
|
|
@ -259,8 +248,7 @@ pub fn MoveOverlay(
|
||||||
.unwrap_or((0.0, 0.0));
|
.unwrap_or((0.0, 0.0));
|
||||||
|
|
||||||
// Convert scene coords to viewport coords
|
// Convert scene coords to viewport coords
|
||||||
let viewer_x = preview_x * sx + ox;
|
let (viewer_x, viewer_y) = t.to_screen(preview_x, preview_y);
|
||||||
let viewer_y = preview_y * sy + oy;
|
|
||||||
let viewport_x = viewer_x + viewer_offset.0;
|
let viewport_x = viewer_x + viewer_offset.0;
|
||||||
let viewport_y = viewer_y + viewer_offset.1;
|
let viewport_y = viewer_y + viewer_offset.1;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue