1use crate::{
2 math::{vec2, Rect, Vec2},
3 ui::{widgets::Editbox, ElementState, Id, Layout, Ui, UiContent},
4};
5
6use std::any::Any;
7
8pub trait Num:
9 Copy + std::string::ToString + std::str::FromStr + Into<f64> + 'static + Default + std::fmt::Display
10{
11}
12
13#[derive(Clone, Copy)]
14struct DragState {
15 start_value: f64,
16 start_mouse: f32,
17}
18
19struct State {
20 string_represents: f64,
21 string: String,
22 before: String,
23 drag: Option<DragState>,
24 in_editbox: bool,
25}
26
27impl Default for State {
28 fn default() -> Self {
29 Self {
30 string_represents: 0.0,
31 string: String::new(),
32 before: String::new(),
33 drag: None,
34 in_editbox: false,
35 }
36 }
37}
38
39pub struct Drag<'a> {
40 id: Id,
41 label: &'a str,
42 range: Option<(f64, f64)>,
43 size: Option<Vec2>,
44 step: f32,
45}
46
47impl<'a> Drag<'a> {
48 pub const fn new(id: Id) -> Drag<'a> {
49 Drag {
50 id,
51 size: None,
52 range: None,
53 label: "",
54 step: 0.1,
55 }
56 }
57
58 pub const fn label<'b>(self, label: &'b str) -> Drag<'b> {
59 Drag {
60 label,
61 id: self.id,
62 range: self.range,
63 size: self.size,
64 step: self.step,
65 }
66 }
67
68 pub fn range<T: Num>(self, range: Option<(T, T)>) -> Drag<'a> {
69 Drag {
70 range: range.map(|(start, end)| (start.into(), end.into())),
71 ..self
72 }
73 }
74 pub fn ui<T>(self, ui: &mut Ui, data: &mut T)
81 where
82 T: std::any::Any + Num,
83 {
84 let context = ui.get_active_window_context();
85 let state_hash = hash!(self.id, "input_float_state");
86 let mut s: State = std::mem::take(context.storage_any.get_or_default(state_hash));
87
88 let label_size = context.window.painter.content_with_margins_size(
89 &context.style.label_style,
90 &UiContent::Label(self.label.into()),
91 );
92 let size = vec2(
93 context.window.cursor.area.w - context.style.margin * 2. - context.window.cursor.ident,
94 label_size.y.max(22.),
95 );
96
97 let pos = context.window.cursor.fit(size, Layout::Vertical);
98 let editbox_area = Vec2::new(
99 if self.label.is_empty() {
100 size.x
101 } else {
102 size.x / 2.0
103 },
104 size.y,
105 );
106 let hovered = Rect::new(pos.x, pos.y, editbox_area.x, editbox_area.y)
107 .contains(context.input.mouse_position);
108
109 if s.in_editbox == false {
111 if hovered && context.input.is_mouse_down() && context.input.modifier_ctrl {
112 s.in_editbox = true;
113 }
114 } else {
115 if context.input.escape
116 || context.input.enter
117 || (hovered == false && context.input.is_mouse_down())
118 {
119 s.in_editbox = false;
120 }
121 }
122
123 if s.in_editbox == false {
124 let context = ui.get_active_window_context();
125
126 let label = format!("{:.2}", (*data));
133 let value_size = context.window.painter.content_with_margins_size(
134 &context.style.label_style,
135 &UiContent::Label((&label).into()),
136 );
137
138 context.window.painter.draw_element_label(
139 &context.style.label_style,
140 pos + Vec2::new(size.x / 2. - value_size.x - 15., 0.),
141 &label,
142 ElementState {
143 focused: context.focused,
144 hovered: false,
145 clicked: false,
146 selected: false,
147 },
148 );
149
150 if let Some(drag) = s.drag {
151 if context.input.is_mouse_down == false {
152 s.drag = None;
153 context.input.cursor_grabbed = false;
154 if !hovered {
155 *context.input_focus = None;
156 }
157 } else {
158 let mouse_delta =
159 (context.input.mouse_position.x - drag.start_mouse) * self.step;
160
161 if (data as &mut dyn Any).is::<f32>() {
162 let data = (data as &mut dyn Any).downcast_mut::<f32>().unwrap();
163 *data = drag.start_value as f32 + mouse_delta;
164 if let Some((start, end)) = self.range {
165 *data = data.max(start as f32).min(end as f32);
166 }
167 }
168 if (data as &mut dyn Any).is::<u32>() {
169 let data = (data as &mut dyn Any).downcast_mut::<u32>().unwrap();
170 *data = (drag.start_value as i32 + mouse_delta as i32).max(0) as u32;
171 if let Some((start, end)) = self.range {
172 *data = (*data).max(start as u32).min(end as u32);
173 }
174 }
175 }
176 } else {
177 if hovered && context.input.is_mouse_down() {
178 s.drag = Some(DragState {
179 start_mouse: context.input.mouse_position.x,
180 start_value: (*data).into(),
181 });
182 *context.input_focus = Some(self.id);
183 context.input.cursor_grabbed = true;
184 }
185 }
186 } else {
187 if s.string_represents != (*data).into() {
188 s.string = data.to_string();
189 }
190
191 Editbox::new(self.id, editbox_area)
192 .position(pos)
193 .multiline(false)
194 .ui(ui, &mut s.string);
195
196 if let Ok(n) = s.string.parse() {
197 *data = n;
198 s.string_represents = n.into();
199 s.before = s.string.clone();
200 } else if s.string.is_empty() {
201 *data = T::default();
202 s.string_represents = 0.0;
203 s.before = s.string.clone();
204 } else {
205 s.string = s.before.clone();
206 }
207 }
208
209 let context = ui.get_active_window_context();
210
211 if self.label.is_empty() == false {
212 context.window.painter.draw_element_label(
213 &context.style.label_style,
214 Vec2::new(pos.x + size.x / 2. + 5., pos.y),
215 self.label,
216 ElementState {
217 focused: context.focused,
218 hovered: false,
219 clicked: false,
220 selected: false,
221 },
222 );
223 }
224
225 *context.storage_any.get_or_default(state_hash) = s;
226 }
227}
228
229impl Num for u32 {}
230impl Num for f32 {}
231
232impl Ui {
233 pub fn drag<T: Num, T1: Into<Option<(T, T)>>>(
234 &mut self,
235 id: Id,
236 label: &str,
237 range: T1,
238 data: &mut T,
239 ) {
240 let range = range.into();
241
242 Drag::new(id).label(label).range(range).ui(self, data);
243 }
244}