110 lines
3.3 KiB
Rust
110 lines
3.3 KiB
Rust
use std::rc::Rc;
|
|
|
|
use gpui::{App, ElementId, IntoElement, ParentElement, RenderOnce, Styled, Window, div};
|
|
|
|
use super::options::OnNumber;
|
|
use crate::{
|
|
buttons::{ButtonVariant, IconButton},
|
|
primitives::IconName,
|
|
theme::{ActiveTheme, ControlSize, Radius, TextSize},
|
|
typography::format::{self, Separators},
|
|
};
|
|
|
|
/// Minus and plus around a number that cannot be typed.
|
|
#[derive(IntoElement)]
|
|
pub struct Stepper {
|
|
id: ElementId,
|
|
value: f64,
|
|
min: f64,
|
|
max: f64,
|
|
step: f64,
|
|
precision: usize,
|
|
on_change: Option<OnNumber>,
|
|
}
|
|
|
|
impl Stepper {
|
|
pub fn new(id: impl Into<ElementId>, value: f64) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
value,
|
|
min: f64::MIN,
|
|
max: f64::MAX,
|
|
step: 1.0,
|
|
precision: 0,
|
|
on_change: None,
|
|
}
|
|
}
|
|
|
|
pub fn range(mut self, min: f64, max: f64) -> Self {
|
|
assert!(min <= max, "stepper range {min}..{max} is empty");
|
|
self.min = min;
|
|
self.max = max;
|
|
self
|
|
}
|
|
|
|
pub fn step(mut self, step: f64) -> Self {
|
|
assert!(step > 0.0, "stepper step {step} must be positive");
|
|
self.step = step;
|
|
self
|
|
}
|
|
|
|
/// Decimal places shown.
|
|
pub fn precision(mut self, places: usize) -> Self {
|
|
self.precision = places;
|
|
self
|
|
}
|
|
|
|
pub fn on_change(mut self, handler: impl Fn(f64, &mut Window, &mut App) + 'static) -> Self {
|
|
self.on_change = Some(Rc::new(handler));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for Stepper {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let theme = cx.theme();
|
|
let (value, min, max) = (self.value, self.min, self.max);
|
|
let nudge = |by: f64| {
|
|
let (id, on_change) = (self.id.clone(), self.on_change.clone());
|
|
move |_: &gpui::ClickEvent, window: &mut Window, cx: &mut App| {
|
|
let next = (value + by).clamp(min, max);
|
|
log::info!("stepper {id:?}: {next}");
|
|
if let Some(on_change) = &on_change {
|
|
on_change(next, window, cx);
|
|
}
|
|
}
|
|
};
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.h(theme.control_height(ControlSize::Md))
|
|
.p_0p5()
|
|
.rounded(theme.radius(Radius::Md))
|
|
.border_1()
|
|
.border_color(theme.colors.border_strong)
|
|
.bg(theme.colors.surface)
|
|
.child(
|
|
IconButton::new((self.id.clone(), "less"), IconName::Minus)
|
|
.variant(ButtonVariant::Ghost)
|
|
.size(ControlSize::Sm)
|
|
.disabled(value <= min)
|
|
.on_click(nudge(-self.step)),
|
|
)
|
|
.child(
|
|
div()
|
|
.min_w(theme.control_height(ControlSize::Md))
|
|
.text_center()
|
|
.text_size(theme.text_size(TextSize::Base))
|
|
.text_color(theme.colors.fg)
|
|
.child(format::number(value, self.precision, Separators::EN)),
|
|
)
|
|
.child(
|
|
IconButton::new((self.id.clone(), "more"), IconName::Plus)
|
|
.variant(ButtonVariant::Ghost)
|
|
.size(ControlSize::Sm)
|
|
.disabled(value >= max)
|
|
.on_click(nudge(self.step)),
|
|
)
|
|
}
|
|
}
|