numeric_stepper.rs

  1use gpui::ClickEvent;
  2
  3use crate::{Divider, IconButtonShape, prelude::*};
  4
  5#[derive(IntoElement, RegisterComponent)]
  6pub struct NumericStepper {
  7    id: ElementId,
  8    value: SharedString,
  9    on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 10    on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 11    /// Whether to reserve space for the reset button.
 12    reserve_space_for_reset: bool,
 13    on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
 14    border: bool,
 15}
 16
 17impl NumericStepper {
 18    pub fn new(
 19        id: impl Into<ElementId>,
 20        value: impl Into<SharedString>,
 21        on_decrement: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 22        on_increment: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 23    ) -> Self {
 24        Self {
 25            id: id.into(),
 26            value: value.into(),
 27            on_decrement: Box::new(on_decrement),
 28            on_increment: Box::new(on_increment),
 29            border: false,
 30            reserve_space_for_reset: false,
 31            on_reset: None,
 32        }
 33    }
 34
 35    pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
 36        self.reserve_space_for_reset = reserve_space_for_reset;
 37        self
 38    }
 39
 40    pub fn on_reset(
 41        mut self,
 42        on_reset: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 43    ) -> Self {
 44        self.on_reset = Some(Box::new(on_reset));
 45        self
 46    }
 47
 48    pub fn border(mut self) -> Self {
 49        self.border = true;
 50        self
 51    }
 52}
 53
 54impl RenderOnce for NumericStepper {
 55    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 56        let shape = IconButtonShape::Square;
 57        let icon_size = IconSize::Small;
 58
 59        h_flex()
 60            .id(self.id)
 61            .gap_1()
 62            .map(|element| {
 63                if let Some(on_reset) = self.on_reset {
 64                    element.child(
 65                        IconButton::new("reset", IconName::RotateCcw)
 66                            .shape(shape)
 67                            .icon_size(icon_size)
 68                            .on_click(on_reset),
 69                    )
 70                } else if self.reserve_space_for_reset {
 71                    element.child(
 72                        h_flex()
 73                            .size(icon_size.square(window, cx))
 74                            .flex_none()
 75                            .into_any_element(),
 76                    )
 77                } else {
 78                    element
 79                }
 80            })
 81            .child(
 82                h_flex()
 83                    .gap_1()
 84                    .when(self.border, |this| {
 85                        this.border_1().border_color(cx.theme().colors().border)
 86                    })
 87                    .px_1()
 88                    .rounded_sm()
 89                    .bg(cx.theme().colors().editor_background)
 90                    .child(
 91                        IconButton::new("decrement", IconName::Dash)
 92                            .shape(shape)
 93                            .icon_size(icon_size)
 94                            .on_click(self.on_decrement),
 95                    )
 96                    .when(self.border, |this| {
 97                        this.child(Divider::vertical().color(super::DividerColor::Border))
 98                    })
 99                    .child(Label::new(self.value))
100                    .when(self.border, |this| {
101                        this.child(Divider::vertical().color(super::DividerColor::Border))
102                    })
103                    .child(
104                        IconButton::new("increment", IconName::Plus)
105                            .shape(shape)
106                            .icon_size(icon_size)
107                            .on_click(self.on_increment),
108                    ),
109            )
110    }
111}
112
113impl Component for NumericStepper {
114    fn scope() -> ComponentScope {
115        ComponentScope::Input
116    }
117
118    fn name() -> &'static str {
119        "NumericStepper"
120    }
121
122    fn sort_name() -> &'static str {
123        Self::name()
124    }
125
126    fn description() -> Option<&'static str> {
127        Some("A button used to increment or decrement a numeric value. ")
128    }
129
130    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
131        Some(
132            v_flex()
133                .child(single_example(
134                    "Borderless",
135                    NumericStepper::new(
136                        "numeric-stepper-component-preview",
137                        "10",
138                        move |_, _, _| {},
139                        move |_, _, _| {},
140                    )
141                    .into_any_element(),
142                ))
143                .child(single_example(
144                    "Border",
145                    NumericStepper::new(
146                        "numeric-stepper-with-border-component-preview",
147                        "10",
148                        move |_, _, _| {},
149                        move |_, _, _| {},
150                    )
151                    .border()
152                    .into_any_element(),
153                ))
154                .into_any_element(),
155        )
156    }
157}