fix: eliminate redraws on position changes
This commit is contained in:
parent
5fcd49e847
commit
b361460485
4 changed files with 235 additions and 176 deletions
|
|
@ -737,6 +737,31 @@ pub fn RealmSceneViewer(
|
|||
// Text size multiplier from settings
|
||||
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());
|
||||
|
||||
// Create a map of members by key for efficient lookup
|
||||
let members_by_key = Signal::derive(move || {
|
||||
use std::collections::HashMap;
|
||||
sorted_members.get()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, m)| (member_key(&m), (idx, m)))
|
||||
.collect::<HashMap<_, _>>()
|
||||
});
|
||||
|
||||
// Get the list of member keys - use Memo so it only updates when keys actually change
|
||||
// (not when member data like position changes)
|
||||
let member_keys = Memo::new(move |_| {
|
||||
sorted_members.get()
|
||||
.iter()
|
||||
.map(member_key)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let scene_name = scene.name.clone();
|
||||
|
||||
view! {
|
||||
|
|
@ -764,76 +789,97 @@ pub fn RealmSceneViewer(
|
|||
class="avatars-container absolute inset-0"
|
||||
style="z-index: 2; pointer-events: none;"
|
||||
>
|
||||
{move || {
|
||||
// Wait for scale factors to be calculated before rendering avatars
|
||||
if !scales_ready.get() {
|
||||
return Vec::new().into_iter().collect_view();
|
||||
}
|
||||
// Wait for scale factors before rendering
|
||||
<Show
|
||||
when=move || scales_ready.get()
|
||||
fallback=|| ()
|
||||
>
|
||||
// Use stable keys - each AvatarCanvas gets its own derived signal
|
||||
{move || {
|
||||
member_keys.get().into_iter().map(|key| {
|
||||
// Create a derived signal for this specific member
|
||||
let member_signal = Signal::derive(move || {
|
||||
members_by_key.get()
|
||||
.get(&key)
|
||||
.map(|(_, m)| m.clone())
|
||||
.expect("member key should exist")
|
||||
});
|
||||
|
||||
let current_bubbles = active_bubbles.get();
|
||||
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 ps = prop_size.get();
|
||||
let te = text_em_size.get();
|
||||
// Derive z-index from position in sorted list
|
||||
let z_index_signal = Signal::derive(move || {
|
||||
members_by_key.get()
|
||||
.get(&key)
|
||||
.map(|(idx, _)| (*idx as i32) + 10)
|
||||
.unwrap_or(10)
|
||||
});
|
||||
|
||||
// Derive bubble for this member
|
||||
let bubble_signal = Signal::derive(move || {
|
||||
active_bubbles.get().get(&key).cloned()
|
||||
});
|
||||
|
||||
let z = z_index_signal.get_untracked();
|
||||
|
||||
// Render active members
|
||||
let mut views: Vec<_> = sorted_members.get()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, member)| {
|
||||
let key = member_key(&member);
|
||||
let bubble = current_bubbles.get(&key).cloned();
|
||||
let z = (idx as i32) + 10;
|
||||
view! {
|
||||
<AvatarCanvas
|
||||
member=member
|
||||
scale_x=sx
|
||||
scale_y=sy
|
||||
offset_x=ox
|
||||
offset_y=oy
|
||||
prop_size=ps
|
||||
member=member_signal
|
||||
scale_x=scale_x_signal
|
||||
scale_y=scale_y_signal
|
||||
offset_x=offset_x_signal
|
||||
offset_y=offset_y_signal
|
||||
prop_size=prop_size
|
||||
z_index=z
|
||||
active_bubble=bubble
|
||||
text_em_size=te
|
||||
active_bubble=bubble_signal
|
||||
text_em_size=text_em_size
|
||||
/>
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}).collect_view()
|
||||
}}
|
||||
// Fading members use closure approach (temporary, per-frame updates)
|
||||
{move || {
|
||||
let Some(fading_signal) = fading_members else {
|
||||
return Vec::new().into_iter().collect_view();
|
||||
};
|
||||
|
||||
// Render fading members with calculated opacity
|
||||
if let Some(fading_signal) = fading_members {
|
||||
#[cfg(feature = "hydrate")]
|
||||
let now = js_sys::Date::now() as i64;
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
let now = 0i64;
|
||||
|
||||
for fading in fading_signal.get() {
|
||||
let elapsed = now - fading.fade_start;
|
||||
if elapsed < fading.fade_duration {
|
||||
let opacity = 1.0 - (elapsed as f64 / fading.fade_duration as f64);
|
||||
let opacity = opacity.max(0.0).min(1.0);
|
||||
views.push(view! {
|
||||
<AvatarCanvas
|
||||
member=fading.member
|
||||
scale_x=sx
|
||||
scale_y=sy
|
||||
offset_x=ox
|
||||
offset_y=oy
|
||||
prop_size=ps
|
||||
z_index=5
|
||||
active_bubble=None
|
||||
text_em_size=te
|
||||
opacity=opacity
|
||||
/>
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
views.into_iter().collect_view()
|
||||
}}
|
||||
fading_signal.get()
|
||||
.into_iter()
|
||||
.filter_map(|fading| {
|
||||
let elapsed = now - fading.fade_start;
|
||||
if elapsed < fading.fade_duration {
|
||||
let opacity = 1.0 - (elapsed as f64 / fading.fade_duration as f64);
|
||||
let opacity = opacity.max(0.0).min(1.0);
|
||||
// Fading members get static signals (they're temporary)
|
||||
let member_signal = Signal::derive({
|
||||
let m = fading.member.clone();
|
||||
move || m.clone()
|
||||
});
|
||||
let bubble_signal: Signal<Option<ActiveBubble>> = Signal::derive(|| None);
|
||||
Some(view! {
|
||||
<AvatarCanvas
|
||||
member=member_signal
|
||||
scale_x=scale_x_signal
|
||||
scale_y=scale_y_signal
|
||||
offset_x=offset_x_signal
|
||||
offset_y=offset_y_signal
|
||||
prop_size=prop_size
|
||||
z_index=5
|
||||
active_bubble=bubble_signal
|
||||
text_em_size=text_em_size
|
||||
opacity=opacity
|
||||
/>
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}}
|
||||
</Show>
|
||||
</div>
|
||||
// Click overlay - captures clicks for movement and hit-testing
|
||||
<div
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue