1use std::cell::Cell;
15use std::rc::Rc;
16use std::sync::Arc;
17
18use crate::color::Color;
19use crate::draw_ctx::DrawCtx;
20use crate::event::{Event, EventResult, Key, MouseButton};
21use crate::geometry::{Rect, Size};
22use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
23use crate::text::{measure_advance, Font};
24use crate::widget::{paint_subtree, Widget};
25use crate::widgets::label::{Label, LabelAlign};
26
27fn format_value(value: f64, decimals: usize) -> String {
30 format!("{:.prec$}", value, prec = decimals)
31}
32
33const WIDGET_H: f64 = 24.0;
36const ARROW_MARGIN: f64 = 8.0;
38const ARROW_TRI_W: f64 = 6.0;
40const LABEL_SIDE_PAD: f64 = 4.0;
42const DRAG_THRESHOLD: f64 = 3.0;
44
45const LABEL_SIDE_INSET: f64 = ARROW_MARGIN + ARROW_TRI_W + LABEL_SIDE_PAD;
49
50pub struct DragValue {
60 bounds: Rect,
61 children: Vec<Box<dyn Widget>>, base: WidgetBase,
63
64 value: f64,
65 min: f64,
66 max: f64,
67
68 speed: f64,
70 step: f64,
73 decimals: usize,
75 suffix: String,
79
80 font: Arc<Font>,
81 font_size: f64,
82
83 dragging: bool,
86 mouse_pressed: bool,
88 press_x: f64,
90 drag_start_x: f64,
92 drag_start_value: f64,
94
95 focused: bool,
97 editing: bool,
98 edit_text: String,
99 edit_cursor: usize,
101
102 hovered: bool,
103 on_change: Option<Box<dyn FnMut(f64)>>,
104
105 value_cell: Option<Rc<Cell<f64>>>,
110 last_value_text: String,
113
114 value_label: Label,
120}
121
122impl DragValue {
125 pub fn new(value: f64, min: f64, max: f64, font: Arc<Font>) -> Self {
127 let clamped = value.clamp(min, max);
128 let initial_text = format_value(clamped, 2);
129 let value_label = Label::new(initial_text.clone(), Arc::clone(&font))
130 .with_font_size(13.0)
131 .with_align(LabelAlign::Center);
132 Self {
133 bounds: Rect::default(),
134 children: Vec::new(),
135 base: WidgetBase::new(),
136 value: clamped,
137 min,
138 max,
139 speed: 1.0,
140 step: 0.0,
141 decimals: 2,
142 suffix: String::new(),
143 font,
144 font_size: 13.0,
145 dragging: false,
146 mouse_pressed: false,
147 press_x: 0.0,
148 drag_start_x: 0.0,
149 drag_start_value: 0.0,
150 focused: false,
151 editing: false,
152 edit_text: String::new(),
153 edit_cursor: 0,
154 hovered: false,
155 on_change: None,
156 value_cell: None,
157 last_value_text: initial_text,
158 value_label,
159 }
160 }
161
162 pub fn with_font_size(mut self, s: f64) -> Self {
164 self.font_size = s;
165 self.value_label.set_font_size(s);
166 self
167 }
168
169 pub fn with_step(mut self, step: f64) -> Self {
172 self.step = step;
173 self
174 }
175
176 pub fn with_speed(mut self, speed: f64) -> Self {
179 self.speed = speed;
180 self
181 }
182
183 pub fn with_decimals(mut self, d: usize) -> Self {
185 self.decimals = d;
186 self.sync_label();
187 self
188 }
189
190 pub fn with_suffix(mut self, suffix: impl Into<String>) -> Self {
196 self.suffix = suffix.into();
197 self.sync_label();
198 self
199 }
200
201 pub fn on_change(mut self, cb: impl FnMut(f64) + 'static) -> Self {
203 self.on_change = Some(Box::new(cb));
204 self
205 }
206
207 pub fn with_value_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
216 let v = cell.get().clamp(self.min, self.max);
217 self.value = v;
218 self.sync_label();
219 self.value_cell = Some(cell);
220 self
221 }
222
223 pub fn with_margin(mut self, m: Insets) -> Self {
224 self.base.margin = m;
225 self
226 }
227 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
228 self.base.h_anchor = h;
229 self
230 }
231 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
232 self.base.v_anchor = v;
233 self
234 }
235 pub fn with_min_size(mut self, s: Size) -> Self {
236 self.base.min_size = s;
237 self
238 }
239 pub fn with_max_size(mut self, s: Size) -> Self {
240 self.base.max_size = s;
241 self
242 }
243
244 pub fn value(&self) -> f64 {
248 self.value
249 }
250
251 fn format_value(&self) -> String {
255 format_value(self.value, self.decimals)
256 }
257
258 fn display_text(&self) -> String {
260 format!("{}{}", self.format_value(), self.suffix)
261 }
262
263 fn sync_label(&mut self) {
264 let text = self.display_text();
265 self.last_value_text = text.clone();
266 self.value_label.set_text(text);
267 }
268
269 pub fn intrinsic_min_width(&self) -> f64 {
279 let text_w = measure_advance(&self.font, &self.display_text(), self.font_size);
280 let digit_w = measure_advance(&self.font, "0", self.font_size);
281 text_w + digit_w + LABEL_SIDE_INSET * 2.0
282 }
283
284 fn write_back(&self) {
288 if let Some(cell) = &self.value_cell {
289 cell.set(self.value);
290 }
291 }
292
293 fn apply_step_and_clamp(&self, raw: f64) -> f64 {
294 let snapped = if self.step > 0.0 {
295 (raw / self.step).round() * self.step
296 } else {
297 raw
298 };
299 snapped.clamp(self.min, self.max)
300 }
301
302 fn update_from_drag(&mut self, current_x: f64) {
303 let delta = (current_x - self.drag_start_x) * self.speed;
304 let raw = self.drag_start_value + delta;
305 self.value = self.apply_step_and_clamp(raw);
306 self.sync_label();
307 self.write_back();
308 let v = self.value;
309 if let Some(cb) = self.on_change.as_mut() {
310 cb(v);
311 }
312 }
313
314 fn enter_edit_mode(&mut self) {
315 self.editing = true;
316 self.edit_text = self.format_value();
317 self.edit_cursor = self.edit_text.chars().count();
318 }
319
320 fn commit_edit(&mut self) {
321 self.editing = false;
322 if let Ok(raw) = self.edit_text.trim().parse::<f64>() {
323 self.value = self.apply_step_and_clamp(raw);
324 }
325 self.sync_label();
327 self.write_back();
328 let v = self.value;
329 if let Some(cb) = self.on_change.as_mut() {
330 cb(v);
331 }
332 }
333
334 fn cancel_edit(&mut self) {
335 self.editing = false;
336 self.sync_label();
337 }
338
339 fn cursor_byte_offset(&self, char_idx: usize) -> usize {
341 self.edit_text
342 .char_indices()
343 .nth(char_idx)
344 .map(|(b, _)| b)
345 .unwrap_or(self.edit_text.len())
346 }
347}
348
349impl Widget for DragValue {
352 fn type_name(&self) -> &'static str {
353 "DragValue"
354 }
355
356 fn bounds(&self) -> Rect {
357 self.bounds
358 }
359 fn set_bounds(&mut self, b: Rect) {
360 self.bounds = b;
361 }
362 fn children(&self) -> &[Box<dyn Widget>] {
363 &self.children
364 }
365 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
366 &mut self.children
367 }
368
369 fn is_focusable(&self) -> bool {
370 true
371 }
372
373 fn margin(&self) -> Insets {
374 self.base.margin
375 }
376 fn widget_base(&self) -> Option<&WidgetBase> {
377 Some(&self.base)
378 }
379 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
380 Some(&mut self.base)
381 }
382 fn h_anchor(&self) -> HAnchor {
383 self.base.h_anchor
384 }
385 fn v_anchor(&self) -> VAnchor {
386 self.base.v_anchor
387 }
388 fn min_size(&self) -> Size {
389 let w = self.base.min_size.width.max(self.intrinsic_min_width());
392 Size::new(w, self.base.min_size.height)
393 }
394 fn max_size(&self) -> Size {
395 self.base.max_size
396 }
397
398 fn measure_min_height(&self, _available_w: f64) -> f64 {
399 WIDGET_H.max(self.base.min_size.height)
400 }
401
402 fn layout(&mut self, available: Size) -> Size {
403 if !self.dragging && !self.editing {
408 if let Some(cell) = &self.value_cell {
409 let raw = cell.get();
410 if !raw.is_nan() {
411 let clamped = raw.clamp(self.min, self.max);
412 if clamped != self.value {
413 self.value = clamped;
414 let text = self.display_text();
415 if text != self.last_value_text {
419 self.sync_label();
420 crate::animation::request_draw();
421 }
422 }
423 }
424 }
425 }
426 let w = available.width.max(self.intrinsic_min_width());
429 Size::new(w, WIDGET_H)
430 }
431
432 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
433 let v = ctx.visuals();
434 let w = self.bounds.width;
435 let h = self.bounds.height;
436 let a = v.accent;
437
438 if self.editing {
439 let bg = Color::rgba(a.r, a.g, a.b, 0.10);
441 ctx.set_fill_color(bg);
442 ctx.begin_path();
443 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
444 ctx.fill();
445
446 ctx.set_stroke_color(Color::rgba(a.r, a.g, a.b, 0.80));
448 ctx.set_line_width(1.5);
449 ctx.begin_path();
450 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
451 ctx.stroke();
452
453 self.value_label.set_text(self.edit_text.clone());
455 let avail_w = (w - 8.0).max(1.0);
456 let lsz = self.value_label.layout(Size::new(avail_w, h));
457 let lx = (w - lsz.width) * 0.5;
458 let ly = (h - lsz.height) * 0.5;
459 self.value_label
460 .set_bounds(Rect::new(0.0, 0.0, lsz.width, lsz.height));
461 ctx.save();
462 ctx.translate(lx, ly);
463 paint_subtree(&mut self.value_label, ctx);
464 ctx.restore();
465
466 let prefix: String = self.edit_text.chars().take(self.edit_cursor).collect();
468 ctx.set_font(Arc::clone(&self.font));
469 ctx.set_font_size(self.font_size);
470 let prefix_w = ctx.measure_text(&prefix).map(|m| m.width).unwrap_or(0.0);
471 let text_x = lx
473 + (lsz.width
474 - ctx
475 .measure_text(&self.edit_text)
476 .map(|m| m.width)
477 .unwrap_or(lsz.width))
478 * 0.5;
479 let cursor_x = text_x + prefix_w;
480 ctx.set_fill_color(Color::rgba(
481 v.text_color.r,
482 v.text_color.g,
483 v.text_color.b,
484 0.85,
485 ));
486 ctx.begin_path();
487 ctx.rect(cursor_x, ly + 2.0, 1.5, lsz.height - 4.0);
488 ctx.fill();
489 } else {
490 let bg = if self.dragging {
492 Color::rgba(a.r, a.g, a.b, 0.22)
493 } else if self.hovered {
494 Color::rgba(a.r, a.g, a.b, 0.14)
495 } else {
496 Color::rgba(a.r, a.g, a.b, 0.08)
497 };
498 let border = Color::rgba(a.r, a.g, a.b, 0.35);
499 let arrow = Color::rgba(a.r, a.g, a.b, 0.45);
500
501 ctx.set_fill_color(bg);
502 ctx.begin_path();
503 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
504 ctx.fill();
505
506 ctx.set_stroke_color(border);
507 ctx.set_line_width(1.0);
508 ctx.begin_path();
509 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
510 ctx.stroke();
511
512 let mid = h * 0.5;
514 let tri_half = 4.0;
515 let tri_w = ARROW_TRI_W;
516 ctx.set_fill_color(arrow);
517 ctx.begin_path();
518 ctx.move_to(ARROW_MARGIN, mid);
519 ctx.line_to(ARROW_MARGIN + tri_w, mid - tri_half);
520 ctx.line_to(ARROW_MARGIN + tri_w, mid + tri_half);
521 ctx.close_path();
522 ctx.fill();
523 ctx.begin_path();
524 ctx.move_to(w - ARROW_MARGIN, mid);
525 ctx.line_to(w - ARROW_MARGIN - tri_w, mid - tri_half);
526 ctx.line_to(w - ARROW_MARGIN - tri_w, mid + tri_half);
527 ctx.close_path();
528 ctx.fill();
529
530 let avail_w = (w - LABEL_SIDE_INSET * 2.0).max(1.0);
531 let lsz = self.value_label.layout(Size::new(avail_w, h));
532 let lx = (w - lsz.width) * 0.5;
533 let ly = (h - lsz.height) * 0.5;
534 self.value_label
535 .set_bounds(Rect::new(0.0, 0.0, lsz.width, lsz.height));
536 ctx.save();
537 ctx.translate(lx, ly);
538 paint_subtree(&mut self.value_label, ctx);
539 ctx.restore();
540 }
541 }
542
543 fn on_event(&mut self, event: &Event) -> EventResult {
544 match event {
545 Event::KeyDown { key, .. } if self.editing => {
547 match key {
548 Key::Char(c) => {
549 if c.is_ascii_digit() || *c == '.' || (*c == '-' && self.edit_cursor == 0) {
551 let byte = self.cursor_byte_offset(self.edit_cursor);
552 self.edit_text.insert(byte, *c);
553 self.edit_cursor += 1;
554 }
555 }
556 Key::Backspace => {
557 if self.edit_cursor > 0 {
558 self.edit_cursor -= 1;
559 let byte = self.cursor_byte_offset(self.edit_cursor);
560 self.edit_text.remove(byte);
561 }
562 }
563 Key::Delete => {
564 let n = self.edit_text.chars().count();
565 if self.edit_cursor < n {
566 let byte = self.cursor_byte_offset(self.edit_cursor);
567 self.edit_text.remove(byte);
568 }
569 }
570 Key::ArrowLeft => {
571 if self.edit_cursor > 0 {
572 self.edit_cursor -= 1;
573 }
574 }
575 Key::ArrowRight => {
576 let n = self.edit_text.chars().count();
577 if self.edit_cursor < n {
578 self.edit_cursor += 1;
579 }
580 }
581 Key::Enter => {
582 self.commit_edit();
583 }
584 Key::Escape => {
585 self.cancel_edit();
586 }
587 _ => {}
588 }
589 crate::animation::request_draw();
590 EventResult::Consumed
591 }
592
593 Event::MouseMove { pos } => {
595 let was = self.hovered;
596 self.hovered = self.hit_test(*pos);
597 if self.mouse_pressed && !self.editing {
598 let dx = (pos.x - self.press_x).abs();
599 if !self.dragging && dx >= DRAG_THRESHOLD {
600 self.dragging = true;
602 self.drag_start_x = self.press_x;
603 self.drag_start_value = self.value;
604 }
605 if self.dragging {
606 self.update_from_drag(pos.x);
607 crate::animation::request_draw();
608 return EventResult::Consumed;
609 }
610 }
611 if was != self.hovered {
612 crate::animation::request_draw();
613 return EventResult::Consumed;
614 }
615 EventResult::Ignored
616 }
617 Event::MouseDown {
618 button: MouseButton::Left,
619 pos,
620 ..
621 } => {
622 if self.editing {
623 return EventResult::Consumed;
625 }
626 self.mouse_pressed = true;
627 self.dragging = false;
628 self.press_x = pos.x;
629 EventResult::Consumed
630 }
631 Event::MouseUp {
632 button: MouseButton::Left,
633 ..
634 } => {
635 let was_drag = self.dragging;
636 let was_pressed = self.mouse_pressed;
637 self.dragging = false;
638 self.mouse_pressed = false;
639 if was_pressed && !was_drag && !self.editing {
640 self.enter_edit_mode();
641 crate::animation::request_draw();
642 } else if was_drag {
643 crate::animation::request_draw();
644 }
645 EventResult::Consumed
646 }
647
648 Event::FocusGained => {
650 self.focused = true;
651 crate::animation::request_draw();
652 EventResult::Ignored
653 }
654 Event::FocusLost => {
655 let was_focused = self.focused;
656 let was_editing = self.editing;
657 self.focused = false;
658 if self.editing {
659 self.commit_edit();
660 }
661 self.dragging = false;
662 self.mouse_pressed = false;
663 if was_focused || was_editing {
664 crate::animation::request_draw();
665 }
666 EventResult::Ignored
667 }
668
669 _ => EventResult::Ignored,
670 }
671 }
672}
673
674#[cfg(test)]
675mod tests;