app_ui/
cursor_inspector.rs1use edit::EditProject;
14use edit::style::CursorConfig;
15use leptos::prelude::*;
16
17use crate::style_inspector::parse_u32_field;
18
19type ProjectSignal = Option<RwSignal<Option<EditProject>>>;
20type HistoryStore = Option<StoredValue<Option<edit::History>>>;
21
22#[must_use]
24pub fn clamp_size_pct(v: u32) -> u32 {
25 v.clamp(25, 400)
26}
27
28fn commit(project: ProjectSignal, history: HistoryStore, edit: impl FnOnce(&mut CursorConfig)) {
29 if let (Some(p), Some(h)) = (project, history) {
30 let mut cfg = p.get_untracked().map(|pr| pr.cursor).unwrap_or_default();
31 edit(&mut cfg);
32 crate::editor_edits::set_cursor(p, h, cfg);
33 }
34}
35
36fn number_field(
37 project: ProjectSignal,
38 history: HistoryStore,
39 label: &'static str,
40 value: u32,
41 max: u32,
42 set: fn(&mut CursorConfig, u32),
43) -> AnyView {
44 view! {
45 <label class="style-field">
46 <span class="style-field-label">{label}</span>
47 <input
48 class="style-field-input"
49 type="number"
50 min="0"
51 max=max.to_string()
52 prop:value=value.to_string()
53 on:change=move |ev| {
54 let v = parse_u32_field(&event_target_value(&ev), max);
55 commit(project, history, |c| set(c, v));
56 }
57 />
58 </label>
59 }
60 .into_any()
61}
62
63fn toggle_field(
64 project: ProjectSignal,
65 history: HistoryStore,
66 label: &'static str,
67 checked: bool,
68 set: fn(&mut CursorConfig, bool),
69) -> AnyView {
70 view! {
71 <label class="cursor-toggle">
72 <input
73 type="checkbox"
74 prop:checked=checked
75 on:change=move |ev| {
76 let b = event_target_checked(&ev);
77 commit(project, history, |c| set(c, b));
78 }
79 />
80 <span>{label}</span>
81 </label>
82 }
83 .into_any()
84}
85
86#[component]
88pub fn CursorInspector() -> impl IntoView {
89 let project = use_context::<RwSignal<Option<EditProject>>>();
90 let history = use_context::<StoredValue<Option<edit::History>>>();
91 view! {
92 <div class="cursor-inspector">
93 <div class="clip-inspector-section">
94 <h3 class="clip-inspector-title">"Cursor"</h3>
95 {move || {
96 let c = project.and_then(|s| s.get().map(|p| p.cursor)).unwrap_or_default();
97 view! {
98 <div class="style-fields">
99 {number_field(project, history, "Size %", c.size_pct, 400, |c, v| c.size_pct = clamp_size_pct(v))}
100 {number_field(project, history, "Smooth", c.smoothing, 100, |c, v| c.smoothing = v)}
101 </div>
102 {toggle_field(project, history, "Click ripples", c.click_ripples, |c, b| c.click_ripples = b)}
103 {toggle_field(project, history, "Hide when static", c.hide_static, |c, b| c.hide_static = b)}
104 {toggle_field(
105 project,
106 history,
107 "Auto-zoom on clicks",
108 c.auto_zoom.detect_from_cursor,
109 |c, b| c.auto_zoom.detect_from_cursor = b,
110 )}
111 }
112 }}
113 </div>
114 </div>
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn size_pct_clamps_to_authoring_range() {
124 assert_eq!(clamp_size_pct(180), 180);
125 assert_eq!(clamp_size_pct(0), 25); assert_eq!(clamp_size_pct(9999), 400); }
128}