fix: scaling, and chat
* Chat ergonomics vastly improved. * Scaling now done through client side settings
This commit is contained in:
parent
98f38c9714
commit
b430c80000
8 changed files with 1564 additions and 439 deletions
|
|
@ -13,7 +13,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::components::{
|
||||
ActiveBubble, Card, ChatInput, ChatMessage, InventoryPopup, MessageLog, RealmHeader,
|
||||
RealmSceneViewer, DEFAULT_BUBBLE_TIMEOUT_MS,
|
||||
RealmSceneViewer, SettingsPopup, ViewerSettings, DEFAULT_BUBBLE_TIMEOUT_MS,
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
use crate::components::use_channel_websocket;
|
||||
|
|
@ -26,6 +26,44 @@ use chattyness_db::ws_messages::ClientMessage;
|
|||
|
||||
use crate::components::ws_client::WsSender;
|
||||
|
||||
/// Parse bounds WKT to extract width and height.
|
||||
///
|
||||
/// Expected format: "POLYGON((0 0, WIDTH 0, WIDTH HEIGHT, 0 HEIGHT, 0 0))"
|
||||
fn parse_bounds_dimensions(bounds_wkt: &str) -> Option<(u32, u32)> {
|
||||
let trimmed = bounds_wkt.trim();
|
||||
let coords_str = trimmed
|
||||
.strip_prefix("POLYGON((")
|
||||
.and_then(|s| s.strip_suffix("))"))?;
|
||||
|
||||
let points: Vec<&str> = coords_str.split(',').collect();
|
||||
if points.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut max_x: f64 = 0.0;
|
||||
let mut max_y: f64 = 0.0;
|
||||
|
||||
for point in points.iter() {
|
||||
let coords: Vec<&str> = point.trim().split_whitespace().collect();
|
||||
if coords.len() >= 2 {
|
||||
if let (Ok(x), Ok(y)) = (coords[0].parse::<f64>(), coords[1].parse::<f64>()) {
|
||||
if x > max_x {
|
||||
max_x = x;
|
||||
}
|
||||
if y > max_y {
|
||||
max_y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if max_x > 0.0 && max_y > 0.0 {
|
||||
Some((max_x as u32, max_y as u32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Realm landing page component.
|
||||
#[component]
|
||||
pub fn RealmPage() -> impl IntoView {
|
||||
|
|
@ -58,6 +96,16 @@ pub fn RealmPage() -> impl IntoView {
|
|||
// Inventory popup state
|
||||
let (inventory_open, set_inventory_open) = signal(false);
|
||||
|
||||
// Settings popup state
|
||||
let (settings_open, set_settings_open) = signal(false);
|
||||
let viewer_settings = RwSignal::new(ViewerSettings::load());
|
||||
|
||||
// Scene dimensions (extracted from bounds_wkt when scene loads)
|
||||
let (scene_dimensions, set_scene_dimensions) = signal((800.0_f64, 600.0_f64));
|
||||
|
||||
// Chat focus prefix (: or /)
|
||||
let (focus_prefix, set_focus_prefix) = signal(':');
|
||||
|
||||
// Loose props state
|
||||
let (loose_props, set_loose_props) = signal(Vec::<LooseProp>::new());
|
||||
|
||||
|
|
@ -205,7 +253,7 @@ pub fn RealmPage() -> impl IntoView {
|
|||
on_prop_picked_up,
|
||||
);
|
||||
|
||||
// Set channel ID when scene loads (triggers WebSocket connection)
|
||||
// Set channel ID and scene dimensions when scene loads
|
||||
// Note: Currently using scene.id as the channel_id since channel_members
|
||||
// uses scenes directly. Proper channel infrastructure can be added later.
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
@ -215,6 +263,11 @@ pub fn RealmPage() -> impl IntoView {
|
|||
return;
|
||||
};
|
||||
set_channel_id.set(Some(scene.id));
|
||||
|
||||
// Extract scene dimensions from bounds_wkt
|
||||
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||
set_scene_dimensions.set((w as f64, h as f64));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -304,8 +357,22 @@ pub fn RealmPage() -> impl IntoView {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle ':' to focus chat input
|
||||
// Handle space to focus chat input (no prefix)
|
||||
if key == " " {
|
||||
set_focus_prefix.set(' ');
|
||||
set_focus_chat_trigger.set(true);
|
||||
use gloo_timers::callback::Timeout;
|
||||
Timeout::new(100, move || {
|
||||
set_focus_chat_trigger.set(false);
|
||||
})
|
||||
.forget();
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle ':' to focus chat input with colon prefix
|
||||
if key == ":" {
|
||||
set_focus_prefix.set(':');
|
||||
set_focus_chat_trigger.set(true);
|
||||
// Reset trigger after a short delay so it can be triggered again
|
||||
use gloo_timers::callback::Timeout;
|
||||
|
|
@ -317,9 +384,68 @@ pub fn RealmPage() -> impl IntoView {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle 'i' to open inventory
|
||||
// Handle '/' to focus chat input with slash prefix
|
||||
if key == "/" {
|
||||
set_focus_prefix.set('/');
|
||||
set_focus_chat_trigger.set(true);
|
||||
use gloo_timers::callback::Timeout;
|
||||
Timeout::new(100, move || {
|
||||
set_focus_chat_trigger.set(false);
|
||||
})
|
||||
.forget();
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle 's' to toggle settings
|
||||
if key == "s" || key == "S" {
|
||||
set_settings_open.update(|v| *v = !*v);
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle arrow keys for panning (only in pan mode)
|
||||
let settings = viewer_settings.get_untracked();
|
||||
if settings.panning_enabled {
|
||||
let pan_step = 50.0;
|
||||
let scroll_delta = match key.as_str() {
|
||||
"ArrowLeft" => Some((-pan_step, 0.0)),
|
||||
"ArrowRight" => Some((pan_step, 0.0)),
|
||||
"ArrowUp" => Some((0.0, -pan_step)),
|
||||
"ArrowDown" => Some((0.0, pan_step)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((dx, dy)) = scroll_delta {
|
||||
// Find the scene container and scroll it
|
||||
if let Some(document) = web_sys::window().and_then(|w| w.document()) {
|
||||
if let Some(container) = document.query_selector(".scene-container").ok().flatten() {
|
||||
let container_el: web_sys::Element = container;
|
||||
container_el.scroll_by_with_x_and_y(dx, dy);
|
||||
}
|
||||
}
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle +/- for zoom
|
||||
let zoom_delta = match key.as_str() {
|
||||
"+" | "=" => Some(0.25),
|
||||
"-" | "_" => Some(-0.25),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(delta) = zoom_delta {
|
||||
viewer_settings.update(|s| s.adjust_zoom(delta));
|
||||
viewer_settings.get_untracked().save();
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 'i' to toggle inventory
|
||||
if key == "i" || key == "I" {
|
||||
set_inventory_open.set(true);
|
||||
set_inventory_open.update(|v| *v = !*v);
|
||||
ev.prevent_default();
|
||||
return;
|
||||
}
|
||||
|
|
@ -502,6 +628,13 @@ pub fn RealmPage() -> impl IntoView {
|
|||
let ws_for_chat: StoredValue<Option<WsSender>, LocalStorage> = StoredValue::new_local(None);
|
||||
let active_bubbles_signal = Signal::derive(move || active_bubbles.get());
|
||||
let loose_props_signal = Signal::derive(move || loose_props.get());
|
||||
let focus_prefix_signal = Signal::derive(move || focus_prefix.get());
|
||||
let on_open_settings_cb = Callback::new(move |_: ()| {
|
||||
set_settings_open.set(true);
|
||||
});
|
||||
let on_open_inventory_cb = Callback::new(move |_: ()| {
|
||||
set_inventory_open.set(true);
|
||||
});
|
||||
view! {
|
||||
<div class="relative w-full">
|
||||
<RealmSceneViewer
|
||||
|
|
@ -512,6 +645,13 @@ pub fn RealmPage() -> impl IntoView {
|
|||
loose_props=loose_props_signal
|
||||
on_move=on_move.clone()
|
||||
on_prop_click=on_prop_click.clone()
|
||||
settings=Signal::derive(move || viewer_settings.get())
|
||||
on_zoom_change=Callback::new(move |delta: f64| {
|
||||
viewer_settings.update(|s| {
|
||||
s.adjust_zoom(delta);
|
||||
s.save();
|
||||
});
|
||||
})
|
||||
/>
|
||||
<div class="absolute bottom-0 left-0 right-0 z-10 pb-4 px-4 pointer-events-none">
|
||||
<ChatInput
|
||||
|
|
@ -519,7 +659,10 @@ pub fn RealmPage() -> impl IntoView {
|
|||
emotion_availability=emotion_avail_signal
|
||||
skin_preview_path=skin_path_signal
|
||||
focus_trigger=focus_trigger_signal
|
||||
focus_prefix=focus_prefix_signal
|
||||
on_focus_change=on_chat_focus_change.clone()
|
||||
on_open_settings=on_open_settings_cb
|
||||
on_open_inventory=on_open_inventory_cb
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -560,6 +703,16 @@ pub fn RealmPage() -> impl IntoView {
|
|||
/>
|
||||
}
|
||||
}
|
||||
|
||||
// Settings popup
|
||||
<SettingsPopup
|
||||
open=Signal::derive(move || settings_open.get())
|
||||
settings=viewer_settings
|
||||
on_close=Callback::new(move |_: ()| {
|
||||
set_settings_open.set(false);
|
||||
})
|
||||
scene_dimensions=scene_dimensions.get()
|
||||
/>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue