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 = 28.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 { self.step = step; self }
145
146 pub fn with_speed(mut self, speed: f64) -> Self { self.speed = speed; self }
149
150 pub fn with_decimals(mut self, d: usize) -> Self {
152 self.decimals = d;
153 self.sync_label();
154 self
155 }
156
157 pub fn on_change(mut self, cb: impl FnMut(f64) + 'static) -> Self {
159 self.on_change = Some(Box::new(cb));
160 self
161 }
162
163 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
164 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
165 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
166 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
167 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
168
169 pub fn value(&self) -> f64 { self.value }
173
174 fn format_value(&self) -> String {
177 format_value(self.value, self.decimals)
178 }
179
180 fn sync_label(&mut self) {
181 self.value_label.set_text(self.format_value());
182 }
183
184 fn apply_step_and_clamp(&self, raw: f64) -> f64 {
185 let snapped = if self.step > 0.0 {
186 (raw / self.step).round() * self.step
187 } else {
188 raw
189 };
190 snapped.clamp(self.min, self.max)
191 }
192
193 fn update_from_drag(&mut self, current_x: f64) {
194 let delta = (current_x - self.drag_start_x) * self.speed;
195 let raw = self.drag_start_value + delta;
196 self.value = self.apply_step_and_clamp(raw);
197 self.sync_label();
198 let v = self.value;
199 if let Some(cb) = self.on_change.as_mut() { cb(v); }
200 }
201
202 fn enter_edit_mode(&mut self) {
203 self.editing = true;
204 self.edit_text = self.format_value();
205 self.edit_cursor = self.edit_text.chars().count();
206 }
207
208 fn commit_edit(&mut self) {
209 self.editing = false;
210 if let Ok(raw) = self.edit_text.trim().parse::<f64>() {
211 self.value = self.apply_step_and_clamp(raw);
212 }
213 self.sync_label();
215 let v = self.value;
216 if let Some(cb) = self.on_change.as_mut() { cb(v); }
217 }
218
219 fn cancel_edit(&mut self) {
220 self.editing = false;
221 self.sync_label();
222 }
223
224 fn cursor_byte_offset(&self, char_idx: usize) -> usize {
226 self.edit_text
227 .char_indices()
228 .nth(char_idx)
229 .map(|(b, _)| b)
230 .unwrap_or(self.edit_text.len())
231 }
232}
233
234impl Widget for DragValue {
237 fn type_name(&self) -> &'static str { "DragValue" }
238
239 fn bounds(&self) -> Rect { self.bounds }
240 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
241 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
242 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
243
244 fn is_focusable(&self) -> bool { true }
245
246 fn margin(&self) -> Insets { self.base.margin }
247 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
248 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
249 fn min_size(&self) -> Size { self.base.min_size }
250 fn max_size(&self) -> Size { self.base.max_size }
251
252 fn layout(&mut self, available: Size) -> Size {
253 Size::new(available.width, WIDGET_H)
254 }
255
256 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
257 let v = ctx.visuals();
258 let w = self.bounds.width;
259 let h = self.bounds.height;
260 let a = v.accent;
261
262 if self.editing {
263 let bg = Color::rgba(a.r, a.g, a.b, 0.10);
265 ctx.set_fill_color(bg);
266 ctx.begin_path();
267 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
268 ctx.fill();
269
270 ctx.set_stroke_color(Color::rgba(a.r, a.g, a.b, 0.80));
272 ctx.set_line_width(1.5);
273 ctx.begin_path();
274 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
275 ctx.stroke();
276
277 self.value_label.set_text(self.edit_text.clone());
279 let avail_w = (w - 8.0).max(1.0);
280 let lsz = self.value_label.layout(Size::new(avail_w, h));
281 let lx = (w - lsz.width) * 0.5;
282 let ly = (h - lsz.height) * 0.5;
283 self.value_label.set_bounds(Rect::new(0.0, 0.0, lsz.width, lsz.height));
284 ctx.save();
285 ctx.translate(lx, ly);
286 paint_subtree(&mut self.value_label, ctx);
287 ctx.restore();
288
289 let prefix: String = self.edit_text.chars().take(self.edit_cursor).collect();
291 ctx.set_font(Arc::clone(&self.font));
292 ctx.set_font_size(self.font_size);
293 let prefix_w = ctx.measure_text(&prefix).map(|m| m.width).unwrap_or(0.0);
294 let text_x = lx + (lsz.width - ctx.measure_text(&self.edit_text).map(|m| m.width).unwrap_or(lsz.width)) * 0.5;
296 let cursor_x = text_x + prefix_w;
297 ctx.set_fill_color(Color::rgba(v.text_color.r, v.text_color.g, v.text_color.b, 0.85));
298 ctx.begin_path();
299 ctx.rect(cursor_x, ly + 2.0, 1.5, lsz.height - 4.0);
300 ctx.fill();
301 } else {
302 let bg = if self.dragging {
304 Color::rgba(a.r, a.g, a.b, 0.22)
305 } else if self.hovered {
306 Color::rgba(a.r, a.g, a.b, 0.14)
307 } else {
308 Color::rgba(a.r, a.g, a.b, 0.08)
309 };
310 let border = Color::rgba(a.r, a.g, a.b, 0.35);
311 let arrow = Color::rgba(a.r, a.g, a.b, 0.45);
312
313 ctx.set_fill_color(bg);
314 ctx.begin_path();
315 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
316 ctx.fill();
317
318 ctx.set_stroke_color(border);
319 ctx.set_line_width(1.0);
320 ctx.begin_path();
321 ctx.rounded_rect(0.0, 0.0, w, h, 4.0);
322 ctx.stroke();
323
324 let mid = h * 0.5;
326 let tri_half = 4.0;
327 let tri_w = 6.0;
328 ctx.set_fill_color(arrow);
329 ctx.begin_path();
330 ctx.move_to(ARROW_MARGIN, mid);
331 ctx.line_to(ARROW_MARGIN + tri_w, mid - tri_half);
332 ctx.line_to(ARROW_MARGIN + tri_w, mid + tri_half);
333 ctx.close_path();
334 ctx.fill();
335 ctx.begin_path();
336 ctx.move_to(w - ARROW_MARGIN, mid);
337 ctx.line_to(w - ARROW_MARGIN - tri_w, mid - tri_half);
338 ctx.line_to(w - ARROW_MARGIN - tri_w, mid + tri_half);
339 ctx.close_path();
340 ctx.fill();
341
342 let avail_w = (w - (ARROW_MARGIN + tri_w + 4.0) * 2.0).max(1.0);
343 let lsz = self.value_label.layout(Size::new(avail_w, h));
344 let lx = (w - lsz.width) * 0.5;
345 let ly = (h - lsz.height) * 0.5;
346 self.value_label.set_bounds(Rect::new(0.0, 0.0, lsz.width, lsz.height));
347 ctx.save();
348 ctx.translate(lx, ly);
349 paint_subtree(&mut self.value_label, ctx);
350 ctx.restore();
351 }
352 }
353
354 fn on_event(&mut self, event: &Event) -> EventResult {
355 match event {
356 Event::KeyDown { key, .. } if self.editing => {
358 match key {
359 Key::Char(c) => {
360 if c.is_ascii_digit() || *c == '.' || (*c == '-' && self.edit_cursor == 0) {
362 let byte = self.cursor_byte_offset(self.edit_cursor);
363 self.edit_text.insert(byte, *c);
364 self.edit_cursor += 1;
365 }
366 }
367 Key::Backspace => {
368 if self.edit_cursor > 0 {
369 self.edit_cursor -= 1;
370 let byte = self.cursor_byte_offset(self.edit_cursor);
371 self.edit_text.remove(byte);
372 }
373 }
374 Key::Delete => {
375 let n = self.edit_text.chars().count();
376 if self.edit_cursor < n {
377 let byte = self.cursor_byte_offset(self.edit_cursor);
378 self.edit_text.remove(byte);
379 }
380 }
381 Key::ArrowLeft => {
382 if self.edit_cursor > 0 { self.edit_cursor -= 1; }
383 }
384 Key::ArrowRight => {
385 let n = self.edit_text.chars().count();
386 if self.edit_cursor < n { self.edit_cursor += 1; }
387 }
388 Key::Enter => { self.commit_edit(); }
389 Key::Escape => { self.cancel_edit(); }
390 _ => {}
391 }
392 crate::animation::request_tick();
393 EventResult::Consumed
394 }
395
396 Event::MouseMove { pos } => {
398 let was = self.hovered;
399 self.hovered = self.hit_test(*pos);
400 if self.mouse_pressed && !self.editing {
401 let dx = (pos.x - self.press_x).abs();
402 if !self.dragging && dx >= DRAG_THRESHOLD {
403 self.dragging = true;
405 self.drag_start_x = self.press_x;
406 self.drag_start_value = self.value;
407 }
408 if self.dragging {
409 self.update_from_drag(pos.x);
410 crate::animation::request_tick();
411 return EventResult::Consumed;
412 }
413 }
414 if was != self.hovered { crate::animation::request_tick(); }
415 EventResult::Ignored
416 }
417 Event::MouseDown { button: MouseButton::Left, pos, .. } => {
418 if self.editing {
419 return EventResult::Consumed;
421 }
422 self.mouse_pressed = true;
423 self.dragging = false;
424 self.press_x = pos.x;
425 EventResult::Consumed
426 }
427 Event::MouseUp { button: MouseButton::Left, .. } => {
428 let was_drag = self.dragging;
429 let was_pressed = self.mouse_pressed;
430 self.dragging = false;
431 self.mouse_pressed = false;
432 if was_pressed && !was_drag && !self.editing {
433 self.enter_edit_mode();
434 crate::animation::request_tick();
435 } else if was_drag {
436 crate::animation::request_tick();
437 }
438 EventResult::Consumed
439 }
440
441 Event::FocusGained => {
443 self.focused = true;
444 crate::animation::request_tick();
445 EventResult::Ignored
446 }
447 Event::FocusLost => {
448 let was_focused = self.focused;
449 let was_editing = self.editing;
450 self.focused = false;
451 if self.editing { self.commit_edit(); }
452 self.dragging = false;
453 self.mouse_pressed = false;
454 if was_focused || was_editing { crate::animation::request_tick(); }
455 EventResult::Ignored
456 }
457
458 _ => EventResult::Ignored,
459 }
460 }
461}