//! HTML-based username label component. //! //! Renders display names as HTML/CSS elements instead of canvas, //! allowing independent updates without triggering avatar redraws. use leptos::prelude::*; use uuid::Uuid; use super::avatar_canvas::AvatarBoundsStore; /// Base text size multiplier. Text at 100% slider = base_sizes * 1.4 const BASE_TEXT_SCALE: f64 = 1.4; /// Individual username label component. /// /// Renders as HTML/CSS for efficient updates independent of avatar canvas. /// Reads avatar position from the shared bounds store written by AvatarCanvas. #[component] pub fn UsernameLabel( /// The user ID this label belongs to (for reading bounds from store). user_id: Uuid, /// The display name to show. display_name: String, /// Shared store containing avatar bounds (written by AvatarCanvas). avatar_bounds_store: AvatarBoundsStore, /// Text size multiplier. #[prop(default = 1.0.into())] text_em_size: Signal, /// Optional opacity for fading members. #[prop(default = 1.0)] opacity: f64, ) -> impl IntoView { // Compute style based on avatar bounds let style = Memo::new(move |_| { let te = text_em_size.get(); let text_scale = te * BASE_TEXT_SCALE; let font_size = 12.0 * text_scale; let avatar_bounds = avatar_bounds_store .get() .get(&user_id) .copied() .unwrap_or_default(); // If bounds are all zero, avatar hasn't rendered yet if avatar_bounds.content_center_x == 0.0 && avatar_bounds.content_top_y == 0.0 { return "display: none;".to_string(); } let x = avatar_bounds.content_center_x; let y = avatar_bounds.content_bottom_y + 15.0 * text_scale; format!( "position: absolute; \ left: {}px; \ top: {}px; \ transform: translateX(-50%); \ --font-size: {}px; \ opacity: {}; \ z-index: 99998;", x, y, font_size, opacity ) }); view! {
{display_name}
} }