1use std::cell::Cell;
4use std::rc::Rc;
5use std::sync::Arc;
6
7use crate::event::{Event, EventResult, Key, MouseButton};
8use crate::geometry::{Rect, Size};
9use crate::draw_ctx::DrawCtx;
10use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
11use crate::text::Font;
12use crate::widget::{Widget, paint_subtree};
13use crate::widgets::label::{Label, LabelAlign};
14
15const TRACK_H: f64 = 4.0;
16const THUMB_R: f64 = 7.0;
17const WIDGET_H: f64 = 22.0;
21const VALUE_W: f64 = 44.0;
25const VALUE_GAP: f64 = 6.0;
27
28pub struct Slider {
30 bounds: Rect,
31 children: Vec<Box<dyn Widget>>, base: WidgetBase,
33 value: f64,
34 min: f64,
35 max: f64,
36 step: f64,
37 show_value: bool,
38 decimals: Option<usize>,
41 font: Arc<Font>,
42 font_size: f64,
43 dragging: bool,
44 focused: bool,
45 hovered: bool,
46 on_change: Option<Box<dyn FnMut(f64)>>,
47 value_cell: Option<Rc<Cell<f64>>>,
53 value_label: Label,
60 last_value_text: String,
63}
64
65impl Slider {
66 pub fn new(value: f64, min: f64, max: f64, font: Arc<Font>) -> Self {
67 let v = value.clamp(min, max);
68 let font_size = 12.0;
69 let value_label = Label::new("", Arc::clone(&font))
70 .with_font_size(font_size)
71 .with_align(LabelAlign::Right);
72 Self {
73 bounds: Rect::default(),
74 children: Vec::new(),
75 base: WidgetBase::new(),
76 value: v,
77 min,
78 max,
79 step: (max - min) / 100.0,
80 show_value: true,
81 decimals: None,
82 font,
83 font_size,
84 dragging: false,
85 focused: false,
86 hovered: false,
87 on_change: None,
88 value_cell: None,
89 value_label,
90 last_value_text: String::new(),
91 }
92 }
93
94 pub fn with_step(mut self, step: f64) -> Self { self.step = step; self }
95
96 pub fn with_value_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
103 self.value = cell.get().clamp(self.min, self.max);
104 self.value_cell = Some(cell);
105 self
106 }
107 pub fn with_show_value(mut self, show: bool) -> Self { self.show_value = show; self }
108
109 pub fn with_decimals(mut self, decimals: usize) -> Self {
112 self.decimals = Some(decimals); self
113 }
114
115 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
116 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
117 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
118 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
119 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
120
121 pub fn on_change(mut self, cb: impl FnMut(f64) + 'static) -> Self {
122 self.on_change = Some(Box::new(cb));
123 self
124 }
125
126 pub fn value(&self) -> f64 { self.value }
127
128 pub fn set_value(&mut self, v: f64) {
129 self.value = v.clamp(self.min, self.max);
130 if let Some(cell) = &self.value_cell { cell.set(self.value); }
131 }
132
133 fn fire(&mut self) {
134 let v = self.value;
135 if let Some(cell) = &self.value_cell { cell.set(v); }
136 if let Some(cb) = self.on_change.as_mut() { cb(v); }
137 }
138
139 fn track_right(&self) -> f64 {
143 let reserved = if self.show_value { VALUE_W + VALUE_GAP } else { 0.0 };
144 (self.bounds.width - reserved - THUMB_R).max(THUMB_R + 1.0)
145 }
146
147 fn thumb_x(&self) -> f64 {
149 let track_left = THUMB_R;
150 let track_right = self.track_right();
151 let t = if self.max > self.min {
152 (self.value - self.min) / (self.max - self.min)
153 } else {
154 0.0
155 };
156 track_left + t * (track_right - track_left)
157 }
158
159 fn value_from_x(&self, x: f64) -> f64 {
160 let track_left = THUMB_R;
161 let track_right = self.track_right();
162 let t = ((x - track_left) / (track_right - track_left)).clamp(0.0, 1.0);
163 let raw = self.min + t * (self.max - self.min);
164 let snapped = (raw / self.step).round() * self.step;
166 snapped.clamp(self.min, self.max)
167 }
168
169 fn format_value(&self) -> String {
172 if let Some(d) = self.decimals {
173 return format!("{:.*}", d, self.value);
174 }
175 if self.step >= 1.0 {
176 format!("{:.0}", self.value)
177 } else if self.step >= 0.1 {
178 format!("{:.1}", self.value)
179 } else if self.step >= 0.01 {
180 format!("{:.2}", self.value)
181 } else {
182 format!("{:.3}", self.value)
183 }
184 }
185}
186
187impl Widget for Slider {
188 fn type_name(&self) -> &'static str { "Slider" }
189 fn bounds(&self) -> Rect { self.bounds }
190 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
191 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
192 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
193
194 fn is_focusable(&self) -> bool { true }
195
196 fn margin(&self) -> Insets { self.base.margin }
197 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
198 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
199 fn min_size(&self) -> Size { self.base.min_size }
200 fn max_size(&self) -> Size { self.base.max_size }
201
202 fn layout(&mut self, available: Size) -> Size {
203 if !self.dragging {
208 if let Some(cell) = &self.value_cell {
209 self.value = cell.get().clamp(self.min, self.max);
210 }
211 }
212
213 if self.show_value {
218 let new_text = self.format_value();
219 if new_text != self.last_value_text {
220 self.value_label.set_text(new_text.clone());
221 self.last_value_text = new_text;
222 }
223 let lh = self.font_size * 1.5;
226 let _ = self.value_label.layout(Size::new(VALUE_W, lh));
227 self.value_label.set_bounds(Rect::new(0.0, 0.0, VALUE_W, lh));
228 }
229
230 Size::new(available.width, WIDGET_H)
231 }
232
233 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
234 let v = ctx.visuals();
235 let w = self.bounds.width;
236 let h = self.bounds.height;
237 let cy = h * 0.5;
238
239 let track_right = self.track_right();
240 let track_w = (track_right - THUMB_R).max(0.0);
241
242 ctx.set_fill_color(v.track_bg);
244 ctx.begin_path();
245 ctx.rounded_rect(THUMB_R, cy - TRACK_H * 0.5, track_w, TRACK_H, TRACK_H * 0.5);
246 ctx.fill();
247
248 let tx = self.thumb_x();
250 if tx > THUMB_R {
251 ctx.set_fill_color(v.accent);
252 ctx.begin_path();
253 ctx.rounded_rect(THUMB_R, cy - TRACK_H * 0.5, tx - THUMB_R, TRACK_H, TRACK_H * 0.5);
254 ctx.fill();
255 }
256
257 if self.focused {
259 ctx.set_stroke_color(v.accent_focus);
260 ctx.set_line_width(2.0);
261 ctx.begin_path();
262 ctx.circle(tx, cy, THUMB_R + 3.0);
263 ctx.stroke();
264 }
265
266 let thumb_color = if self.dragging || self.focused {
268 v.accent_pressed
269 } else if self.hovered {
270 v.accent_hovered
271 } else {
272 v.accent
273 };
274 ctx.set_fill_color(thumb_color);
275 ctx.begin_path();
276 ctx.circle(tx, cy, THUMB_R);
277 ctx.fill();
278
279 ctx.set_fill_color(v.widget_bg);
280 ctx.begin_path();
281 ctx.circle(tx, cy, THUMB_R - 2.5);
282 ctx.fill();
283
284 if self.show_value {
289 self.value_label.set_color(v.text_color);
290 let lb = self.value_label.bounds();
291 let strip_left = track_right + VALUE_GAP;
292 let ly = cy - lb.height * 0.5;
293 self.value_label.set_bounds(Rect::new(strip_left, ly, lb.width, lb.height));
294 ctx.save();
295 ctx.translate(strip_left, ly);
296 paint_subtree(&mut self.value_label, ctx);
297 ctx.restore();
298 }
299 }
300
301 fn on_event(&mut self, event: &Event) -> EventResult {
302 match event {
303 Event::MouseMove { pos } => {
304 let was = self.hovered;
305 self.hovered = self.hit_test(*pos);
306 if self.dragging {
307 self.value = self.value_from_x(pos.x);
308 self.fire();
309 crate::animation::request_tick();
310 return EventResult::Consumed;
311 }
312 if was != self.hovered { crate::animation::request_tick(); }
313 EventResult::Ignored
314 }
315 Event::MouseDown { button: MouseButton::Left, pos, .. } => {
316 self.dragging = true;
317 self.value = self.value_from_x(pos.x);
318 self.fire();
319 crate::animation::request_tick();
320 EventResult::Consumed
321 }
322 Event::MouseUp { button: MouseButton::Left, .. } => {
323 let was = self.dragging;
324 self.dragging = false;
325 if was { crate::animation::request_tick(); }
326 EventResult::Consumed
327 }
328 Event::KeyDown { key, .. } => {
329 let changed = match key {
330 Key::ArrowLeft => { self.value = (self.value - self.step).clamp(self.min, self.max); true }
331 Key::ArrowRight => { self.value = (self.value + self.step).clamp(self.min, self.max); true }
332 Key::ArrowDown => { self.value = (self.value - self.step * 10.0).clamp(self.min, self.max); true }
333 Key::ArrowUp => { self.value = (self.value + self.step * 10.0).clamp(self.min, self.max); true }
334 _ => false,
335 };
336 if changed {
337 self.fire();
338 crate::animation::request_tick();
339 EventResult::Consumed
340 } else {
341 EventResult::Ignored
342 }
343 }
344 Event::FocusGained => {
345 self.focused = true;
346 crate::animation::request_tick();
347 EventResult::Ignored
348 }
349 Event::FocusLost => {
350 self.focused = false;
351 self.dragging = false;
352 crate::animation::request_tick();
353 EventResult::Ignored
354 }
355 _ => EventResult::Ignored,
356 }
357 }
358}