95 lines
2.9 KiB
Rust
95 lines
2.9 KiB
Rust
use std::rc::Rc;
|
|
|
|
use gpui::{
|
|
App, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce,
|
|
SharedString, StatefulInteractiveElement, Styled, Window, div,
|
|
};
|
|
|
|
use super::NumberInput;
|
|
use crate::theme::{ActiveTheme, Radius};
|
|
|
|
type OnUnit = Rc<dyn Fn(&SharedString, &mut Window, &mut App)>;
|
|
|
|
/// A number with a unit you click through, such as px, % and em.
|
|
#[derive(IntoElement)]
|
|
pub struct UnitInput {
|
|
field: NumberInput,
|
|
units: Vec<SharedString>,
|
|
unit: SharedString,
|
|
on_unit: Option<OnUnit>,
|
|
}
|
|
|
|
impl UnitInput {
|
|
pub fn new(
|
|
id: impl Into<ElementId>,
|
|
value: f64,
|
|
units: impl IntoIterator<Item = impl Into<SharedString>>,
|
|
unit: impl Into<SharedString>,
|
|
) -> Self {
|
|
let units: Vec<SharedString> = units.into_iter().map(Into::into).collect();
|
|
let unit = unit.into();
|
|
assert!(!units.is_empty(), "unit input has no units");
|
|
Self {
|
|
field: NumberInput::new(id, value),
|
|
units,
|
|
unit,
|
|
on_unit: None,
|
|
}
|
|
}
|
|
|
|
pub fn range(mut self, min: f64, max: f64) -> Self {
|
|
self.field = self.field.range(min, max);
|
|
self
|
|
}
|
|
|
|
pub fn precision(mut self, places: usize) -> Self {
|
|
self.field = self.field.precision(places);
|
|
self
|
|
}
|
|
|
|
pub fn on_change(mut self, handler: impl Fn(f64, &mut Window, &mut App) + 'static) -> Self {
|
|
self.field = self.field.on_change(handler);
|
|
self
|
|
}
|
|
|
|
/// Runs with the next unit when the unit is clicked.
|
|
pub fn on_unit(
|
|
mut self,
|
|
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
|
|
) -> Self {
|
|
self.on_unit = Some(Rc::new(handler));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for UnitInput {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let theme = cx.theme();
|
|
let next = match self.units.iter().position(|unit| *unit == self.unit) {
|
|
Some(at) => self.units[(at + 1) % self.units.len()].clone(),
|
|
None => {
|
|
log::error!("unit input: {} is not among {:?}", self.unit, self.units);
|
|
self.units[0].clone()
|
|
}
|
|
};
|
|
let on_unit = self.on_unit;
|
|
let hover = theme.colors.hover;
|
|
let unit = div()
|
|
.id((self.field.id.clone(), "unit"))
|
|
.px_1p5()
|
|
.rounded(theme.radius(Radius::Sm))
|
|
.text_color(theme.colors.fg_muted)
|
|
.cursor_pointer()
|
|
.hover(|style| style.bg(hover))
|
|
.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
|
|
.on_click(move |_, window, cx| {
|
|
log::info!("unit input: {next}");
|
|
if let Some(on_unit) = &on_unit {
|
|
on_unit(&next, window, cx);
|
|
}
|
|
})
|
|
.child(self.unit);
|
|
self.field.suffix(unit)
|
|
}
|
|
}
|