1use std::sync::Arc;
15
16use crate::color::Color;
17use crate::draw_ctx::DrawCtx;
18use crate::event::{Event, EventResult, Key, MouseButton};
19use crate::geometry::{Rect, Size};
20use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
21use crate::text::Font;
22use crate::widget::{paint_subtree, Widget};
23use crate::widgets::label::{Label, LabelAlign};
24
25fn format_value(value: f64, decimals: usize) -> String {
28 format!("{:.prec$}", value, prec = decimals)
29}
30
31const WIDGET_H: f64 = 24.0;
34const ARROW_MARGIN: f64 = 8.0;
36const DRAG_THRESHOLD: f64 = 3.0;
38
39pub struct DragValue {
49 bounds: Rect,
50 children: Vec<Box<dyn Widget>>, base: WidgetBase,
52
53 value: f64,
54 min: f64,
55 max: f64,
56
57 speed: f64,
59 step: f64,
62 decimals: usize,
64
65 font: Arc<Font>,
66 font_size: f64,
67
68 dragging: bool,
71 mouse_pressed: bool,
73 press_x: f64,
75 drag_start_x: f64,
77 drag_start_value: f64,
79
80 focused: bool,
82 editing: bool,
83 edit_text: String,
84 edit_cursor: usize,
86
87 hovered: bool,
88 on_change: Option<Box<dyn FnMut(f64)>>,
89
90 value_label: Label,
96}
97
98impl DragValue {
101 pub fn new(value: f64, min: f64, max: f64, font: Arc<Font>) -> Self {
103 let clamped = value.clamp(min, max);
104 let initial_text = format_value(clamped, 2);
105 let value_label = Label::new(initial_text, Arc::clone(&font))
106 .with_font_size(13.0)
107 .with_align(LabelAlign::Center);
108 Self {
109 bounds: Rect::default(),
110 children: Vec::new(),
111 base: WidgetBase::new(),
112 value: clamped,
113 min,
114 max,
115 speed: 1.0,
116 step: 0.0,
117 decimals: 2,
118 font,
119 font_size: 13.0,
120 dragging: false,
121 mouse_pressed: false,
122 press_x: 0.0,
123 drag_start_x: 0.0,
124 drag_start_value: 0.0,
125 focused: false,
126 editing: false,
127 edit_text: String::new(),
128 edit_cursor: 0,
129 hovered: false,
130 on_change: None,
131 value_label,
132 }
133 }
134
135 pub fn with_font_size(mut self, s: f64) -> Self {
137 self.font_size = s;
138 self.value_label.set_font_size(s);
139 self
140 }
141
142 pub fn with_step(mut self, step: f64) -> Self {
145 self.step = step;
146 self
147 }
148
149 pub fn with_speed(mut self, speed: f64) -> Self {
152 self.speed = speed;
153 self
154 }
155
156 pub fn with_decimals(mut self, d: usize) -> Self {
158 self.decimals = d;
159 self.sync_label();
160 self
161 }
162
163 pub fn on_change(mut self, cb: impl FnMut(f64) + 'static) -> Self {
165 self.on_change = Some(Box::new(cb));
166 self
167 }
168
169 pub fn with_margin(mut self, m: Insets) -> Self {
170 self.base.margin = m;
171 self
172 }
173 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
174 self.base.h_anchor = h;
175 self
176 }
177 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
178 self.base.v_anchor = v;
179 self
180 }
181 pub fn with_min_size(mut self, s: Size) -> Self {
182 self.base.min_size = s;
183 self
184 }
185 pub fn with_max_size(mut self, s: Size) -> Self {
186 self.base.max_size = s;
187 self
188 }
189
190 pub fn value(&self) -> f64 {
194 self.value
195 }
196
197 fn format_value(&self) -> String {
200 format_value(self.value, self.decimals)
201 }
202
203 fn sync_label(&mut self) {
204 self.value_label.set_text(self.format_value());
205 }
206
207 fn apply_step_and_clamp(&self, raw: f64) -> f64 {
208 let snapped = if self.step > 0.0 {
209 (raw / self.step).round() * self.step
210 } else {
211 raw
212 };
213 snapped.clamp(self.min, self.max)
214 }
215
216 fn update_from_drag(&mut self, current_x: f64) {
217 let delta = (current_x - self.drag_start_x) * self.speed;
218 let raw = self.drag_start_value + delta;
219 self.value = self.apply_step_and_clamp(raw);
220 self.sync_label();
221 let v = self.value;
222 if let Some(cb) = self.on_change.as_mut() {
223 cb(v);
224 }
225 }
226
227 fn enter_edit_mode(&mut self) {
228 self.editing = true;
229 self.edit_text = self.format_value();
230 self.edit_cursor = self.edit_text.chars().count();
231 }
232
233 fn commit_edit(&mut self) {
234 self.editing = false;
235 if let Ok(raw) = self.edit_text.trim().parse::<f64>() {
236 self.value = self.apply_step_and_clamp(raw);
237 }
238 self.sync_label();
240 let v = self.value;
241 if let Some(cb) = self.on_change.as_mut() {
242 cb(v);
243 }
244 }
245
246 fn cancel_edit(&mut self) {
247 self.editing = false;
248 self.sync_label();
249 }
250
251 fn cursor_byte_offset(&self, char_idx: usize) -> usize {
253 self.edit_text
254 .char_indices()
255 .nth(char_idx)
256 .map(|(b, _)| b)
257 .unwrap_or(self.edit_text.len())
258 }
259}
260
261impl Widget for DragValue {
264 fn type_name(&self) -> &'static str {
265 "DragValue"
266 }
267
268 fn bounds(&self) -> Rect {
269 self.bounds
270 }
271 fn set_bounds(&mut self, b: Rect) {
272 self.bounds = b;
273 }
274 fn children(&self) -> &[Box<dyn Widget>] {
275 &self.children
276 }
277 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
278 &mut self.children
279 }
280
281 fn is_focusable(&self) -> bool {
282 true
283 }
284
285 fn margin(&self) -> Insets {
286 self.base.margin
287 }
288 fn widget_base(&self) -> Option<&WidgetBase> {
289 Some(&self.base)
290 }
291 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
292 Some(&mut self.base)
293 }
294 fn h_anchor(&self) -> HAnchor {
295 self.base.h_anchor
296 }
297 fn v_anchor(&self) -> VAnchor {
298 self.base.v_anchor
299 }
300 fn min_size(&self) -> Size {
301 self.base.min_size
302 }
303 fn max_size(&self) -> Size {
304 self.base.max_size
305 }
306
307 fn layout(&mut self, available: Size) -> Size {
308 Size::new(available.width, WIDGET_H)
309 }
310
311 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
312 let v = ctx.visuals();
313 let w = self.bounds.width;
314 let h = self.bounds.height;
315 let a = v.accent;
316
317 if self.editing {
318 let bg = Color::rgba(a.r, a.g, a.b, 0.10);
320 ctx.set_fill_color(bg);
321 ctx.begin_path();
322 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
323 ctx.fill();
324
325 ctx.set_stroke_color(Color::rgba(a.r, a.g, a.b, 0.80));
327 ctx.set_line_width(1.5);
328 ctx.begin_path();
329 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
330 ctx.stroke();
331
332 self.value_label.set_text(self.edit_text.clone());
334 let avail_w = (w - 8.0).max(1.0);
335 let lsz = self.value_label.layout(Size::new(avail_w, h));
336 let lx = (w - lsz.width) * 0.5;
337 let ly = (h - lsz.height) * 0.5;
338 self.value_label
339 .set_bounds(Rect::new(0.0, 0.0, lsz.width, lsz.height));
340 ctx.save();
341 ctx.translate(lx, ly);
342 paint_subtree(&mut self.value_label, ctx);
343 ctx.restore();
344
345 let prefix: String = self.edit_text.chars().take(self.edit_cursor).collect();
347 ctx.set_font(Arc::clone(&self.font));
348 ctx.set_font_size(self.font_size);
349 let prefix_w = ctx.measure_text(&prefix).map(|m| m.width).unwrap_or(0.0);
350 let text_x = lx
352 + (lsz.width
353 - ctx
354 .measure_text(&self.edit_text)
355 .map(|m| m.width)
356 .unwrap_or(lsz.width))
357 * 0.5;
358 let cursor_x = text_x + prefix_w;
359 ctx.set_fill_color(Color::rgba(
360 v.text_color.r,
361 v.text_color.g,
362 v.text_color.b,
363 0.85,
364 ));
365 ctx.begin_path();
366 ctx.rect(cursor_x, ly + 2.0, 1.5, lsz.height - 4.0);
367 ctx.fill();
368 } else {
369 let bg = if self.dragging {
371 Color::rgba(a.r, a.g, a.b, 0.22)
372 } else if self.hovered {
373 Color::rgba(a.r, a.g, a.b, 0.14)
374 } else {
375 Color::rgba(a.r, a.g, a.b, 0.08)
376 };
377 let border = Color::rgba(a.r, a.g, a.b, 0.35);
378 let arrow = Color::rgba(a.r, a.g, a.b, 0.45);
379
380 ctx.set_fill_color(bg);
381 ctx.begin_path();
382 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
383 ctx.fill();
384
385 ctx.set_stroke_color(border);
386 ctx.set_line_width(1.0);
387 ctx.begin_path();
388 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
389 ctx.stroke();
390
391 let mid = h * 0.5;
393 let tri_half = 4.0;
394 let tri_w = 6.0;
395 ctx.set_fill_color(arrow);
396 ctx.begin_path();
397 ctx.move_to(ARROW_MARGIN, mid);
398 ctx.line_to(ARROW_MARGIN + tri_w, mid - tri_half);
399 ctx.line_to(ARROW_MARGIN + tri_w, mid + tri_half);
400 ctx.close_path();
401 ctx.fill();
402 ctx.begin_path();
403 ctx.move_to(w - ARROW_MARGIN, mid);
404 ctx.line_to(w - ARROW_MARGIN - tri_w, mid - tri_half);
405 ctx.line_to(w - ARROW_MARGIN - tri_w, mid + tri_half);
406 ctx.close_path();
407 ctx.fill();
408
409 let avail_w = (w - (ARROW_MARGIN + tri_w + 4.0) * 2.0).max(1.0);
410 let lsz = self.value_label.layout(Size::new(avail_w, h));
411 let lx = (w - lsz.width) * 0.5;
412 let ly = (h - lsz.height) * 0.5;
413 self.value_label
414 .set_bounds(Rect::new(0.0, 0.0, lsz.width, lsz.height));
415 ctx.save();
416 ctx.translate(lx, ly);
417 paint_subtree(&mut self.value_label, ctx);
418 ctx.restore();
419 }
420 }
421
422 fn on_event(&mut self, event: &Event) -> EventResult {
423 match event {
424 Event::KeyDown { key, .. } if self.editing => {
426 match key {
427 Key::Char(c) => {
428 if c.is_ascii_digit() || *c == '.' || (*c == '-' && self.edit_cursor == 0) {
430 let byte = self.cursor_byte_offset(self.edit_cursor);
431 self.edit_text.insert(byte, *c);
432 self.edit_cursor += 1;
433 }
434 }
435 Key::Backspace => {
436 if self.edit_cursor > 0 {
437 self.edit_cursor -= 1;
438 let byte = self.cursor_byte_offset(self.edit_cursor);
439 self.edit_text.remove(byte);
440 }
441 }
442 Key::Delete => {
443 let n = self.edit_text.chars().count();
444 if self.edit_cursor < n {
445 let byte = self.cursor_byte_offset(self.edit_cursor);
446 self.edit_text.remove(byte);
447 }
448 }
449 Key::ArrowLeft => {
450 if self.edit_cursor > 0 {
451 self.edit_cursor -= 1;
452 }
453 }
454 Key::ArrowRight => {
455 let n = self.edit_text.chars().count();
456 if self.edit_cursor < n {
457 self.edit_cursor += 1;
458 }
459 }
460 Key::Enter => {
461 self.commit_edit();
462 }
463 Key::Escape => {
464 self.cancel_edit();
465 }
466 _ => {}
467 }
468 crate::animation::request_draw();
469 EventResult::Consumed
470 }
471
472 Event::MouseMove { pos } => {
474 let was = self.hovered;
475 self.hovered = self.hit_test(*pos);
476 if self.mouse_pressed && !self.editing {
477 let dx = (pos.x - self.press_x).abs();
478 if !self.dragging && dx >= DRAG_THRESHOLD {
479 self.dragging = true;
481 self.drag_start_x = self.press_x;
482 self.drag_start_value = self.value;
483 }
484 if self.dragging {
485 self.update_from_drag(pos.x);
486 crate::animation::request_draw();
487 return EventResult::Consumed;
488 }
489 }
490 if was != self.hovered {
491 crate::animation::request_draw();
492 return EventResult::Consumed;
493 }
494 EventResult::Ignored
495 }
496 Event::MouseDown {
497 button: MouseButton::Left,
498 pos,
499 ..
500 } => {
501 if self.editing {
502 return EventResult::Consumed;
504 }
505 self.mouse_pressed = true;
506 self.dragging = false;
507 self.press_x = pos.x;
508 EventResult::Consumed
509 }
510 Event::MouseUp {
511 button: MouseButton::Left,
512 ..
513 } => {
514 let was_drag = self.dragging;
515 let was_pressed = self.mouse_pressed;
516 self.dragging = false;
517 self.mouse_pressed = false;
518 if was_pressed && !was_drag && !self.editing {
519 self.enter_edit_mode();
520 crate::animation::request_draw();
521 } else if was_drag {
522 crate::animation::request_draw();
523 }
524 EventResult::Consumed
525 }
526
527 Event::FocusGained => {
529 self.focused = true;
530 crate::animation::request_draw();
531 EventResult::Ignored
532 }
533 Event::FocusLost => {
534 let was_focused = self.focused;
535 let was_editing = self.editing;
536 self.focused = false;
537 if self.editing {
538 self.commit_edit();
539 }
540 self.dragging = false;
541 self.mouse_pressed = false;
542 if was_focused || was_editing {
543 crate::animation::request_draw();
544 }
545 EventResult::Ignored
546 }
547
548 _ => EventResult::Ignored,
549 }
550 }
551}