settings ui: Improve numeric stepper component interface (#36513)
This is the first step to allowing users to type into a numeric stepper
to set its value. This PR makes the numeric stepper take in a generic
type `T` where T: `NumericStepperType`
```rust
pub trait NumericStepperType:
Display
+ Add<Output = Self>
+ Sub<Output = Self>
+ Copy
+ Clone
+ Sized
+ PartialOrd
+ FromStr
+ 'static
{
fn default_format(value: &Self) -> String {
format!("{}", value)
}
fn default_step() -> Self;
fn large_step() -> Self;
fn small_step() -> Self;
fn min_value() -> Self;
fn max_value() -> Self;
}
```
This allows setting of step sizes and min/max values as well as making
the component easier to use.
cc @danilo-leal
Release Notes:
- N/A
---------
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Gaauwe Rombouts <mail@grombouts.nl>
This commit is contained in:
co-authored by
Mikayla Maki
Gaauwe Rombouts
parent
f499504b13
commit
e5f05a21ce
@@ -1,237 +0,0 @@
|
||||
use gpui::ClickEvent;
|
||||
|
||||
use crate::{IconButtonShape, prelude::*};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum NumericStepperStyle {
|
||||
Outlined,
|
||||
#[default]
|
||||
Ghost,
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct NumericStepper {
|
||||
id: ElementId,
|
||||
value: SharedString,
|
||||
style: NumericStepperStyle,
|
||||
on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
||||
on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
||||
/// Whether to reserve space for the reset button.
|
||||
reserve_space_for_reset: bool,
|
||||
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
tab_index: Option<isize>,
|
||||
}
|
||||
|
||||
impl NumericStepper {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
value: impl Into<SharedString>,
|
||||
on_decrement: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
on_increment: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
value: value.into(),
|
||||
style: NumericStepperStyle::default(),
|
||||
on_decrement: Box::new(on_decrement),
|
||||
on_increment: Box::new(on_increment),
|
||||
reserve_space_for_reset: false,
|
||||
on_reset: None,
|
||||
tab_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: NumericStepperStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
|
||||
self.reserve_space_for_reset = reserve_space_for_reset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_reset(
|
||||
mut self,
|
||||
on_reset: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_reset = Some(Box::new(on_reset));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tab_index(mut self, tab_index: isize) -> Self {
|
||||
self.tab_index = Some(tab_index);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for NumericStepper {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let shape = IconButtonShape::Square;
|
||||
let icon_size = IconSize::Small;
|
||||
|
||||
let is_outlined = matches!(self.style, NumericStepperStyle::Outlined);
|
||||
let mut tab_index = self.tab_index;
|
||||
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
.gap_1()
|
||||
.map(|element| {
|
||||
if let Some(on_reset) = self.on_reset {
|
||||
element.child(
|
||||
IconButton::new("reset", IconName::RotateCcw)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.when_some(tab_index.as_mut(), |this, tab_index| {
|
||||
*tab_index += 1;
|
||||
this.tab_index(*tab_index - 1)
|
||||
})
|
||||
.on_click(on_reset),
|
||||
)
|
||||
} else if self.reserve_space_for_reset {
|
||||
element.child(
|
||||
h_flex()
|
||||
.size(icon_size.square(window, cx))
|
||||
.flex_none()
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
element
|
||||
}
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.rounded_sm()
|
||||
.map(|this| {
|
||||
if is_outlined {
|
||||
this.overflow_hidden()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
} else {
|
||||
this.px_1().bg(cx.theme().colors().editor_background)
|
||||
}
|
||||
})
|
||||
.map(|decrement| {
|
||||
if is_outlined {
|
||||
decrement.child(
|
||||
h_flex()
|
||||
.id("decrement_button")
|
||||
.p_1p5()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(Icon::new(IconName::Dash).size(IconSize::Small))
|
||||
.when_some(tab_index.as_mut(), |this, tab_index| {
|
||||
*tab_index += 1;
|
||||
this.tab_index(*tab_index - 1).focus(|style| {
|
||||
style.bg(cx.theme().colors().element_hover)
|
||||
})
|
||||
})
|
||||
.on_click(self.on_decrement),
|
||||
)
|
||||
} else {
|
||||
decrement.child(
|
||||
IconButton::new("decrement", IconName::Dash)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.when_some(tab_index.as_mut(), |this, tab_index| {
|
||||
*tab_index += 1;
|
||||
this.tab_index(*tab_index - 1)
|
||||
})
|
||||
.on_click(self.on_decrement),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(Label::new(self.value).mx_3())
|
||||
.map(|increment| {
|
||||
if is_outlined {
|
||||
increment.child(
|
||||
h_flex()
|
||||
.id("increment_button")
|
||||
.p_1p5()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(Icon::new(IconName::Plus).size(IconSize::Small))
|
||||
.when_some(tab_index.as_mut(), |this, tab_index| {
|
||||
*tab_index += 1;
|
||||
this.tab_index(*tab_index - 1).focus(|style| {
|
||||
style.bg(cx.theme().colors().element_hover)
|
||||
})
|
||||
})
|
||||
.on_click(self.on_increment),
|
||||
)
|
||||
} else {
|
||||
increment.child(
|
||||
IconButton::new("increment", IconName::Dash)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.when_some(tab_index.as_mut(), |this, tab_index| {
|
||||
*tab_index += 1;
|
||||
this.tab_index(*tab_index - 1)
|
||||
})
|
||||
.on_click(self.on_increment),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for NumericStepper {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"Numeric Stepper"
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
Self::name()
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A button used to increment or decrement a numeric value.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-component-preview",
|
||||
"10",
|
||||
move |_, _, _| {},
|
||||
move |_, _, _| {},
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Outlined",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-with-border-component-preview",
|
||||
"10",
|
||||
move |_, _, _| {},
|
||||
move |_, _, _| {},
|
||||
)
|
||||
.style(NumericStepperStyle::Outlined)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user