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}