use crate::{
math::{vec2, Rect, Vec2},
ui::{widgets::Editbox, ElementState, Id, Layout, Ui, UiContent},
};
use std::any::Any;
pub trait Num:
Copy + std::string::ToString + std::str::FromStr + Into<f64> + 'static + Default + std::fmt::Display
{
}
#[derive(Clone, Copy)]
struct DragState {
start_value: f64,
start_mouse: f32,
}
struct State {
string_represents: f64,
string: String,
before: String,
drag: Option<DragState>,
in_editbox: bool,
}
impl Default for State {
fn default() -> Self {
Self {
string_represents: 0.0,
string: String::new(),
before: String::new(),
drag: None,
in_editbox: false,
}
}
}
pub struct Drag<'a> {
id: Id,
label: &'a str,
range: Option<(f64, f64)>,
size: Option<Vec2>,
step: f32,
}
impl<'a> Drag<'a> {
pub fn new(id: Id) -> Drag<'a> {
Drag {
id,
size: None,
range: None,
label: "",
step: 0.1,
}
}
pub fn label<'b>(self, label: &'b str) -> Drag<'b> {
Drag {
label,
id: self.id,
range: self.range,
size: self.size,
step: self.step,
}
}
pub fn range<T: Num>(self, range: Option<(T, T)>) -> Drag<'a> {
Drag {
range: range.map(|(start, end)| (start.into(), end.into())),
..self
}
}
pub fn ui<T>(self, ui: &mut Ui, data: &mut T)
where
T: std::any::Any + Num,
{
let context = ui.get_active_window_context();
let state_hash = hash!(self.id, "input_float_state");
let mut s: State = std::mem::take(context.storage_any.get_or_default(state_hash));
let label_size = context.window.painter.content_with_margins_size(
&context.style.label_style,
&UiContent::Label(self.label.into()),
);
let size = vec2(
context.window.cursor.area.w - context.style.margin * 2. - context.window.cursor.ident,
label_size.y.max(22.),
);
let pos = context.window.cursor.fit(size, Layout::Vertical);
let editbox_area = Vec2::new(
if self.label.is_empty() {
size.x
} else {
size.x / 2.0
},
size.y,
);
let hovered = Rect::new(pos.x, pos.y, editbox_area.x, editbox_area.y)
.contains(context.input.mouse_position);
if s.in_editbox == false {
if hovered && context.input.is_mouse_down() && context.input.modifier_ctrl {
s.in_editbox = true;
}
} else {
if context.input.escape
|| context.input.enter
|| (hovered == false && context.input.is_mouse_down())
{
s.in_editbox = false;
}
}
if s.in_editbox == false {
let context = ui.get_active_window_context();
let label = format!("{:.2}", (*data));
let value_size = context.window.painter.content_with_margins_size(
&context.style.label_style,
&UiContent::Label((&label).into()),
);
context.window.painter.draw_element_label(
&context.style.label_style,
pos + Vec2::new(size.x / 2. - value_size.x - 15., 0.),
&label,
ElementState {
focused: context.focused,
hovered: false,
clicked: false,
selected: false,
},
);
if let Some(drag) = s.drag {
if context.input.is_mouse_down == false {
s.drag = None;
context.input.cursor_grabbed = false;
if !hovered {
*context.input_focus = None;
}
} else {
let mouse_delta =
(context.input.mouse_position.x - drag.start_mouse) * self.step;
if (data as &mut dyn Any).is::<f32>() {
let data = (data as &mut dyn Any).downcast_mut::<f32>().unwrap();
*data = drag.start_value as f32 + mouse_delta;
if let Some((start, end)) = self.range {
*data = data.max(start as f32).min(end as f32);
}
}
if (data as &mut dyn Any).is::<u32>() {
let data = (data as &mut dyn Any).downcast_mut::<u32>().unwrap();
*data = (drag.start_value as i32 + mouse_delta as i32).max(0) as u32;
if let Some((start, end)) = self.range {
*data = (*data).max(start as u32).min(end as u32);
}
}
}
} else {
if hovered && context.input.is_mouse_down() {
s.drag = Some(DragState {
start_mouse: context.input.mouse_position.x,
start_value: (*data).into(),
});
*context.input_focus = Some(self.id);
context.input.cursor_grabbed = true;
}
}
} else {
if s.string_represents != (*data).into() {
s.string = data.to_string();
}
Editbox::new(self.id, editbox_area)
.position(pos)
.multiline(false)
.ui(ui, &mut s.string);
if let Ok(n) = s.string.parse() {
*data = n;
s.string_represents = n.into();
s.before = s.string.clone();
} else if s.string.is_empty() {
*data = T::default();
s.string_represents = 0.0;
s.before = s.string.clone();
} else {
s.string = s.before.clone();
}
}
let context = ui.get_active_window_context();
if self.label.is_empty() == false {
context.window.painter.draw_element_label(
&context.style.label_style,
Vec2::new(pos.x + size.x / 2. + 5., pos.y),
self.label,
ElementState {
focused: context.focused,
hovered: false,
clicked: false,
selected: false,
},
);
}
*context.storage_any.get_or_default(state_hash) = s;
}
}
impl Num for u32 {}
impl Num for f32 {}
impl Ui {
pub fn drag<T: Num, T1: Into<Option<(T, T)>>>(
&mut self,
id: Id,
label: &str,
range: T1,
data: &mut T,
) {
let range = range.into();
Drag::new(id).label(label).range(range).ui(self, data)
}
}