169 lines
5.5 KiB
Rust
169 lines
5.5 KiB
Rust
//! Scene viewer display settings with localStorage persistence.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::utils::LocalStoragePersist;
|
|
|
|
/// Reference resolution for enlarged props calculation.
|
|
pub const REFERENCE_WIDTH: f64 = 1920.0;
|
|
pub const REFERENCE_HEIGHT: f64 = 1080.0;
|
|
|
|
/// Base size for props/avatars in scene coordinates.
|
|
/// SVG assets are 120x120 pixels - this is the native/full size.
|
|
pub const BASE_PROP_SIZE: f64 = 120.0;
|
|
|
|
/// Scale factor for avatar rendering relative to BASE_PROP_SIZE.
|
|
/// Avatars render at 50% (60px cells) to allow merit-based scaling up later.
|
|
pub const BASE_AVATAR_SCALE: f64 = 0.5;
|
|
|
|
/// Scale factor for dropped loose props relative to BASE_PROP_SIZE.
|
|
/// Props render at 75% (90px) at default scale=1.0.
|
|
pub const BASE_PROP_SCALE: f64 = 0.75;
|
|
|
|
/// Minimum zoom level (25%).
|
|
pub const ZOOM_MIN: f64 = 0.25;
|
|
|
|
/// Maximum zoom level (400%).
|
|
pub const ZOOM_MAX: f64 = 4.0;
|
|
|
|
/// Zoom step increment.
|
|
pub const ZOOM_STEP: f64 = 0.25;
|
|
|
|
/// Pan step in pixels for keyboard navigation.
|
|
pub const PAN_STEP: f64 = 50.0;
|
|
|
|
/// Minimum text em size (50%).
|
|
pub const TEXT_EM_MIN: f64 = 0.5;
|
|
|
|
/// Maximum text em size (200%).
|
|
pub const TEXT_EM_MAX: f64 = 2.0;
|
|
|
|
/// Text em size step increment.
|
|
pub const TEXT_EM_STEP: f64 = 0.1;
|
|
|
|
/// Calculate the minimum zoom level for pan mode.
|
|
///
|
|
/// - Large scenes: min zoom fills the viewport
|
|
/// - Small scenes: min zoom is 1.0 (native resolution, centered)
|
|
pub fn calculate_min_zoom(
|
|
scene_width: f64,
|
|
scene_height: f64,
|
|
viewport_width: f64,
|
|
viewport_height: f64,
|
|
) -> f64 {
|
|
if scene_width <= 0.0 || scene_height <= 0.0 || viewport_width <= 0.0 || viewport_height <= 0.0
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
let min_x = viewport_width / scene_width;
|
|
let min_y = viewport_height / scene_height;
|
|
// For large scenes: min zoom fills viewport
|
|
// For small scenes: clamp to 1.0 (native resolution)
|
|
min_x.max(min_y).min(1.0)
|
|
}
|
|
|
|
/// Scene viewer display settings.
|
|
///
|
|
/// These settings control how the scene is rendered and are persisted
|
|
/// to localStorage for user preference retention.
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
pub struct ViewerSettings {
|
|
/// When true, canvas shows at native resolution with scrolling.
|
|
/// When false, canvas scales to fit viewport (default).
|
|
pub panning_enabled: bool,
|
|
|
|
/// Zoom level (0.25 to 4.0). Only applicable when `panning_enabled` is true.
|
|
pub zoom_level: f64,
|
|
|
|
/// When true, props use reference scaling based on 1920x1080.
|
|
/// Applies in both fit mode and pan mode.
|
|
pub enlarge_props: bool,
|
|
|
|
/// Saved horizontal scroll position for pan mode.
|
|
pub scroll_x: f64,
|
|
|
|
/// Saved vertical scroll position for pan mode.
|
|
pub scroll_y: f64,
|
|
|
|
/// Text size multiplier for display names, chat bubbles, and badges.
|
|
/// Range: 0.5 to 2.0 (50% to 200%).
|
|
pub text_em_size: f64,
|
|
}
|
|
|
|
impl Default for ViewerSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
panning_enabled: false,
|
|
zoom_level: 1.0,
|
|
enlarge_props: true,
|
|
scroll_x: 0.0,
|
|
scroll_y: 0.0,
|
|
text_em_size: 1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implement LocalStoragePersist trait for automatic load/save
|
|
impl LocalStoragePersist for ViewerSettings {
|
|
const STORAGE_KEY: &'static str = "chattyness_viewer_settings";
|
|
}
|
|
|
|
impl ViewerSettings {
|
|
/// Calculate the effective prop size based on current settings.
|
|
///
|
|
/// In pan mode without enlarge, returns base size * zoom level.
|
|
/// In pan mode with enlarge, returns base size * reference_scale * zoom level.
|
|
/// In fit mode with enlarge_props, returns size adjusted for reference resolution.
|
|
/// Otherwise returns base size (caller should multiply by canvas scale).
|
|
pub fn calculate_prop_size(&self, scene_width: f64, scene_height: f64) -> f64 {
|
|
// Reference scale factor for "enlarge props" mode
|
|
let ref_scale = (scene_width / REFERENCE_WIDTH).max(scene_height / REFERENCE_HEIGHT);
|
|
|
|
if self.panning_enabled {
|
|
if self.enlarge_props {
|
|
BASE_PROP_SIZE * ref_scale * self.zoom_level
|
|
} else {
|
|
BASE_PROP_SIZE * self.zoom_level
|
|
}
|
|
} else if self.enlarge_props {
|
|
// Reference scaling: ensure minimum size based on 1920x1080
|
|
BASE_PROP_SIZE * ref_scale
|
|
} else {
|
|
// Default: base size (will be scaled by canvas scale factor)
|
|
BASE_PROP_SIZE
|
|
}
|
|
}
|
|
|
|
/// Adjust zoom level by a delta, clamping to valid range.
|
|
///
|
|
/// If `min_zoom` is provided, uses that as the floor instead of `ZOOM_MIN`.
|
|
pub fn adjust_zoom(&mut self, delta: f64) {
|
|
self.zoom_level = (self.zoom_level + delta).clamp(ZOOM_MIN, ZOOM_MAX);
|
|
}
|
|
|
|
/// Adjust zoom level with a custom minimum.
|
|
pub fn adjust_zoom_with_min(&mut self, delta: f64, min_zoom: f64) {
|
|
let effective_min = min_zoom.max(ZOOM_MIN);
|
|
self.zoom_level = (self.zoom_level + delta).clamp(effective_min, ZOOM_MAX);
|
|
}
|
|
|
|
/// Clamp zoom level to an effective minimum.
|
|
pub fn clamp_zoom_min(&mut self, min_zoom: f64) {
|
|
let effective_min = min_zoom.max(ZOOM_MIN);
|
|
if self.zoom_level < effective_min {
|
|
self.zoom_level = effective_min;
|
|
}
|
|
}
|
|
|
|
/// Reset scroll position to origin.
|
|
pub fn reset_scroll(&mut self) {
|
|
self.scroll_x = 0.0;
|
|
self.scroll_y = 0.0;
|
|
}
|
|
|
|
/// Adjust text em size by a delta, clamping to valid range.
|
|
pub fn adjust_text_em(&mut self, delta: f64) {
|
|
self.text_em_size = (self.text_em_size + delta).clamp(TEXT_EM_MIN, TEXT_EM_MAX);
|
|
}
|
|
}
|