Add min and max values and fix rendering a bit

This commit is contained in:
Mikayla Maki 2025-08-19 16:52:16 -07:00
parent e6f06f14fd
commit b775a2e3cb
2 changed files with 144 additions and 55 deletions

View file

@ -255,7 +255,7 @@ impl EditableSettingControl for UiFontSizeControl {
} }
impl RenderOnce for UiFontSizeControl { impl RenderOnce for UiFontSizeControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
// let value = Self::read(cx); // let value = Self::read(cx);
h_flex().gap_2().child(Icon::new(IconName::FontSize)) h_flex().gap_2().child(Icon::new(IconName::FontSize))

View file

@ -24,7 +24,15 @@ pub enum NumericStepperMode {
} }
pub trait NumericStepperType: pub trait NumericStepperType:
Display + Add<Output = Self> + Sub<Output = Self> + Copy + Clone + Sized + FromStr + 'static Display
+ Add<Output = Self>
+ Sub<Output = Self>
+ Copy
+ Clone
+ Sized
+ PartialOrd
+ FromStr
+ 'static
{ {
fn default_format(value: &Self) -> String { fn default_format(value: &Self) -> String {
format!("{}", value) format!("{}", value)
@ -32,6 +40,8 @@ pub trait NumericStepperType:
fn default_step() -> Self; fn default_step() -> Self;
fn large_step() -> Self; fn large_step() -> Self;
fn small_step() -> Self; fn small_step() -> Self;
fn min_value() -> Self;
fn max_value() -> Self;
} }
macro_rules! impl_numeric_stepper_int { macro_rules! impl_numeric_stepper_int {
@ -48,6 +58,14 @@ macro_rules! impl_numeric_stepper_int {
fn small_step() -> Self { fn small_step() -> Self {
1 1
} }
fn min_value() -> Self {
<$type>::MIN
}
fn max_value() -> Self {
<$type>::MAX
}
} }
}; };
} }
@ -56,7 +74,10 @@ macro_rules! impl_numeric_stepper_float {
($type:ident) => { ($type:ident) => {
impl NumericStepperType for $type { impl NumericStepperType for $type {
fn default_format(value: &Self) -> String { fn default_format(value: &Self) -> String {
format!("{:.2}", value) format!("{:^4}", value)
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
} }
fn default_step() -> Self { fn default_step() -> Self {
@ -70,6 +91,14 @@ macro_rules! impl_numeric_stepper_float {
fn small_step() -> Self { fn small_step() -> Self {
0.1 0.1
} }
fn min_value() -> Self {
<$type>::MIN
}
fn max_value() -> Self {
<$type>::MAX
}
} }
}; };
} }
@ -83,8 +112,8 @@ impl_numeric_stepper_int!(u32);
impl_numeric_stepper_int!(i64); impl_numeric_stepper_int!(i64);
impl_numeric_stepper_int!(u64); impl_numeric_stepper_int!(u64);
// TODO: Add a new register component macro to support a specific type when using generics #[derive(RegisterComponent)]
pub struct NumericStepper<T> { pub struct NumericStepper<T = usize> {
id: ElementId, id: ElementId,
value: Entity<T>, value: Entity<T>,
style: NumericStepperStyle, style: NumericStepperStyle,
@ -94,6 +123,8 @@ pub struct NumericStepper<T> {
large_step: T, large_step: T,
small_step: T, small_step: T,
step: T, step: T,
min_value: T,
max_value: T,
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>, on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
tab_index: Option<isize>, tab_index: Option<isize>,
} }
@ -106,18 +137,25 @@ impl<T: NumericStepperType> NumericStepper<T> {
cx: &mut App, cx: &mut App,
) -> Self { ) -> Self {
let id = id.into(); let id = id.into();
let mode = window.use_state(cx, |_, _| NumericStepperMode::default());
let (mode, focus_handle) = window.with_id(id.clone(), |window| {
let mode = window.use_state(cx, |_, _| NumericStepperMode::default());
let focus_handle = window.use_state(cx, |_, cx| cx.focus_handle());
(mode, focus_handle)
});
Self { Self {
id, id,
focus_handle: cx.focus_handle(),
mode, mode,
value, value,
focus_handle: focus_handle.read(cx).clone(),
style: NumericStepperStyle::default(), style: NumericStepperStyle::default(),
format: Box::new(T::default_format), format: Box::new(T::default_format),
large_step: T::large_step(), large_step: T::large_step(),
step: T::default_step(), step: T::default_step(),
small_step: T::small_step(), small_step: T::small_step(),
min_value: T::min_value(),
max_value: T::max_value(),
on_reset: None, on_reset: None,
tab_index: None, tab_index: None,
} }
@ -143,6 +181,16 @@ impl<T: NumericStepperType> NumericStepper<T> {
self self
} }
pub fn min(mut self, min: T) -> Self {
self.min_value = min;
self
}
pub fn max(mut self, max: T) -> Self {
self.max_value = max;
self
}
pub fn style(mut self, style: NumericStepperStyle) -> Self { pub fn style(mut self, style: NumericStepperStyle) -> Self {
self.style = style; self.style = style;
self self
@ -226,10 +274,15 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
.map(|decrement| { .map(|decrement| {
let decrement_handler = { let decrement_handler = {
let value = self.value.clone(); let value = self.value.clone();
move |click: &ClickEvent, _: &mut Window, cx: &mut App| { let focus = self.focus_handle.clone();
let min = self.min_value;
move |click: &ClickEvent, window: &mut Window, cx: &mut App| {
let step = get_step(click.modifiers()); let step = get_step(click.modifiers());
let current_value = *value.read(cx); let current_value = *value.read(cx);
value.write(cx, current_value - step); let new_value = current_value - step;
let new_value = if new_value < min { min } else { new_value };
value.write(cx, new_value);
window.focus(&focus);
} }
}; };
@ -265,59 +318,93 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
) )
} }
}) })
.child(match *self.mode.read(cx) { .child(
NumericStepperMode::Read => div() div()
.id("numeric_stepper_label") .text_color(gpui::red())
.child(Label::new((self.format)(self.value.read(cx))).mx_3()) .in_focus(|this| {
.on_click({ this.border_1()
let mode = self.mode.clone(); .border_color(cx.theme().colors().border_focused)
move |click, _, cx| {
if click.click_count() == 2 {
mode.write(cx, NumericStepperMode::Edit);
}
}
}) })
.into_any_element(), .child(match *self.mode.read(cx) {
NumericStepperMode::Edit => div() NumericStepperMode::Read => div()
.child(window.use_state(cx, { .id("numeric_stepper_label")
|window, cx| { .child(Label::new((self.format)(self.value.read(cx))).mx_3())
let mut editor = Editor::single_line(window, cx); .on_click({
editor.set_text(format!("{}", self.value.read(cx)), window, cx);
cx.on_focus_out(&editor.focus_handle(cx), window, {
let mode = self.mode.clone(); let mode = self.mode.clone();
let value = self.value.clone();
move |this, _, _window, cx| { move |click, _, cx| {
if let Ok(new_value) = this.text(cx).parse::<T>() { if click.click_count() == 2 {
value.write(cx, new_value); mode.write(cx, NumericStepperMode::Edit);
}; }
mode.write(cx, NumericStepperMode::Read);
} }
}) })
.detach(); .w(px(4.0 * 14.0)) // w_14
.h_8()
.overflow_scroll()
.into_any_element(),
NumericStepperMode::Edit => div()
.child(window.use_state(cx, {
|window, cx| {
let mut editor = Editor::single_line(window, cx);
window.focus(&editor.focus_handle(cx)); editor.set_text(
format!("{}", self.value.read(cx)),
window,
cx,
);
cx.on_focus_out(&editor.focus_handle(cx), window, {
let mode = self.mode.clone();
let value = self.value.clone();
let min = self.min_value;
let max = self.max_value;
move |this, _, _window, cx| {
if let Ok(new_value) =
this.text(cx).parse::<T>()
{
let new_value = if new_value < min {
min
} else if new_value > max {
max
} else {
new_value
};
value.write(cx, new_value);
};
mode.write(cx, NumericStepperMode::Read);
}
})
.detach();
editor window.focus(&editor.focus_handle(cx));
}
})) editor
.on_action::<menu::Confirm>({ }
let focus = self.focus_handle.clone(); }))
move |_, window, _| { .on_action::<menu::Confirm>({
window.focus(&focus); let focus = self.focus_handle.clone();
} move |_, window, _| {
}) window.focus(&focus);
.w_full() }
.mx_3() })
.into_any_element(), .size_full()
}) .w(px(4.0 * 14.0)) // w_14
.h_8()
.mx_3()
.into_any_element(),
}),
)
.map(|increment| { .map(|increment| {
let increment_handler = { let increment_handler = {
let value = self.value.clone(); let value = self.value.clone();
move |click: &ClickEvent, _: &mut Window, cx: &mut App| { let focus = self.focus_handle.clone();
let max = self.max_value;
move |click: &ClickEvent, window: &mut Window, cx: &mut App| {
let step = get_step(click.modifiers()); let step = get_step(click.modifiers());
let current_value = *value.read(cx); let current_value = *value.read(cx);
value.write(cx, current_value + step); let new_value = current_value + step;
let new_value = if new_value > max { max } else { new_value };
value.write(cx, new_value);
window.focus(&focus);
} }
}; };
@ -342,7 +429,7 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
) )
} else { } else {
increment.child( increment.child(
IconButton::new("increment", IconName::Dash) IconButton::new("increment", IconName::Plus)
.shape(shape) .shape(shape)
.icon_size(icon_size) .icon_size(icon_size)
.when_some(tab_index.as_mut(), |this, tab_index| { .when_some(tab_index.as_mut(), |this, tab_index| {
@ -357,7 +444,7 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
} }
} }
impl<T: NumericStepperType> Component for NumericStepper<T> { impl Component for NumericStepper<usize> {
fn scope() -> ComponentScope { fn scope() -> ComponentScope {
ComponentScope::Input ComponentScope::Input
} }
@ -375,8 +462,8 @@ impl<T: NumericStepperType> Component for NumericStepper<T> {
} }
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> { fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
let first_stepper = window.use_state(cx, |_, _| 10usize); let first_stepper = window.use_state(cx, |_, _| 100usize);
let second_stepper = window.use_state(cx, |_, _| 10.0); let second_stepper = window.use_state(cx, |_, _| 100.0);
Some( Some(
v_flex() v_flex()
.gap_6() .gap_6()
@ -401,6 +488,8 @@ impl<T: NumericStepperType> Component for NumericStepper<T> {
window, window,
cx, cx,
) )
.min(1.0)
.max(100.0)
.style(NumericStepperStyle::Outlined) .style(NumericStepperStyle::Outlined)
.into_any_element(), .into_any_element(),
), ),