numeric_stepper.rs

 1use gpui::ClickEvent;
 2
 3use crate::{IconButtonShape, prelude::*};
 4
 5#[derive(IntoElement)]
 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}
15
16impl NumericStepper {
17    pub fn new(
18        id: impl Into<ElementId>,
19        value: impl Into<SharedString>,
20        on_decrement: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
21        on_increment: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
22    ) -> Self {
23        Self {
24            id: id.into(),
25            value: value.into(),
26            on_decrement: Box::new(on_decrement),
27            on_increment: Box::new(on_increment),
28            reserve_space_for_reset: false,
29            on_reset: None,
30        }
31    }
32
33    pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
34        self.reserve_space_for_reset = reserve_space_for_reset;
35        self
36    }
37
38    pub fn on_reset(
39        mut self,
40        on_reset: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
41    ) -> Self {
42        self.on_reset = Some(Box::new(on_reset));
43        self
44    }
45}
46
47impl RenderOnce for NumericStepper {
48    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
49        let shape = IconButtonShape::Square;
50        let icon_size = IconSize::Small;
51
52        h_flex()
53            .id(self.id)
54            .gap_1()
55            .map(|element| {
56                if let Some(on_reset) = self.on_reset {
57                    element.child(
58                        IconButton::new("reset", IconName::RotateCcw)
59                            .shape(shape)
60                            .icon_size(icon_size)
61                            .on_click(on_reset),
62                    )
63                } else if self.reserve_space_for_reset {
64                    element.child(
65                        h_flex()
66                            .size(icon_size.square(window, cx))
67                            .flex_none()
68                            .into_any_element(),
69                    )
70                } else {
71                    element
72                }
73            })
74            .child(
75                h_flex()
76                    .gap_1()
77                    .px_1()
78                    .rounded_xs()
79                    .bg(cx.theme().colors().editor_background)
80                    .child(
81                        IconButton::new("decrement", IconName::Dash)
82                            .shape(shape)
83                            .icon_size(icon_size)
84                            .on_click(self.on_decrement),
85                    )
86                    .child(Label::new(self.value))
87                    .child(
88                        IconButton::new("increment", IconName::Plus)
89                            .shape(shape)
90                            .icon_size(icon_size)
91                            .on_click(self.on_increment),
92                    ),
93            )
94    }
95}