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