1use std::{
  2    fmt::Display,
  3    num::{NonZero, NonZeroU32, NonZeroU64},
  4    rc::Rc,
  5    str::FromStr,
  6};
  7
  8use editor::{Editor, EditorStyle};
  9use gpui::{ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers};
 10
 11use settings::{CenteredPaddingSettings, CodeFade, DelayMs, InactiveOpacity, MinimumContrast};
 12use ui::prelude::*;
 13
 14#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
 15pub enum NumberFieldMode {
 16    #[default]
 17    Read,
 18    Edit,
 19}
 20
 21pub trait NumberFieldType: Display + Copy + Clone + Sized + PartialOrd + FromStr + 'static {
 22    fn default_format(value: &Self) -> String {
 23        format!("{}", value)
 24    }
 25    fn default_step() -> Self;
 26    fn large_step() -> Self;
 27    fn small_step() -> Self;
 28    fn min_value() -> Self;
 29    fn max_value() -> Self;
 30    fn saturating_add(self, rhs: Self) -> Self;
 31    fn saturating_sub(self, rhs: Self) -> Self;
 32}
 33
 34macro_rules! impl_newtype_numeric_stepper {
 35    ($type:ident, $default:expr, $large:expr, $small:expr, $min:expr, $max:expr) => {
 36        impl NumberFieldType for $type {
 37            fn default_step() -> Self {
 38                $default.into()
 39            }
 40
 41            fn large_step() -> Self {
 42                $large.into()
 43            }
 44
 45            fn small_step() -> Self {
 46                $small.into()
 47            }
 48
 49            fn min_value() -> Self {
 50                $min.into()
 51            }
 52
 53            fn max_value() -> Self {
 54                $max.into()
 55            }
 56
 57            fn saturating_add(self, rhs: Self) -> Self {
 58                $type((self.0 + rhs.0).min(Self::max_value().0))
 59            }
 60
 61            fn saturating_sub(self, rhs: Self) -> Self {
 62                $type((self.0 - rhs.0).max(Self::min_value().0))
 63            }
 64        }
 65    };
 66}
 67
 68#[rustfmt::skip]
 69impl_newtype_numeric_stepper!(FontWeight, 50., 100., 10., FontWeight::THIN, FontWeight::BLACK);
 70impl_newtype_numeric_stepper!(CodeFade, 0.1, 0.2, 0.05, 0.0, 0.9);
 71impl_newtype_numeric_stepper!(InactiveOpacity, 0.1, 0.2, 0.05, 0.0, 1.0);
 72impl_newtype_numeric_stepper!(MinimumContrast, 1., 10., 0.5, 0.0, 106.0);
 73impl_newtype_numeric_stepper!(DelayMs, 100, 500, 10, 0, 2000);
 74impl_newtype_numeric_stepper!(
 75    CenteredPaddingSettings,
 76    0.05,
 77    0.2,
 78    0.1,
 79    CenteredPaddingSettings::MIN_PADDING,
 80    CenteredPaddingSettings::MAX_PADDING
 81);
 82
 83macro_rules! impl_numeric_stepper_int {
 84    ($type:ident) => {
 85        impl NumberFieldType for $type {
 86            fn default_step() -> Self {
 87                1
 88            }
 89
 90            fn large_step() -> Self {
 91                10
 92            }
 93
 94            fn small_step() -> Self {
 95                1
 96            }
 97
 98            fn min_value() -> Self {
 99                <$type>::MIN
100            }
101
102            fn max_value() -> Self {
103                <$type>::MAX
104            }
105
106            fn saturating_add(self, rhs: Self) -> Self {
107                self.saturating_add(rhs)
108            }
109
110            fn saturating_sub(self, rhs: Self) -> Self {
111                self.saturating_sub(rhs)
112            }
113        }
114    };
115}
116
117macro_rules! impl_numeric_stepper_nonzero_int {
118    ($nonzero:ty, $inner:ty) => {
119        impl NumberFieldType for $nonzero {
120            fn default_step() -> Self {
121                <$nonzero>::new(1).unwrap()
122            }
123
124            fn large_step() -> Self {
125                <$nonzero>::new(10).unwrap()
126            }
127
128            fn small_step() -> Self {
129                <$nonzero>::new(1).unwrap()
130            }
131
132            fn min_value() -> Self {
133                <$nonzero>::MIN
134            }
135
136            fn max_value() -> Self {
137                <$nonzero>::MAX
138            }
139
140            fn saturating_add(self, rhs: Self) -> Self {
141                let result = self.get().saturating_add(rhs.get());
142                <$nonzero>::new(result.max(1)).unwrap()
143            }
144
145            fn saturating_sub(self, rhs: Self) -> Self {
146                let result = self.get().saturating_sub(rhs.get()).max(1);
147                <$nonzero>::new(result).unwrap()
148            }
149        }
150    };
151}
152
153macro_rules! impl_numeric_stepper_float {
154    ($type:ident) => {
155        impl NumberFieldType for $type {
156            fn default_format(value: &Self) -> String {
157                format!("{:.2}", value)
158            }
159
160            fn default_step() -> Self {
161                1.0
162            }
163
164            fn large_step() -> Self {
165                10.0
166            }
167
168            fn small_step() -> Self {
169                0.1
170            }
171
172            fn min_value() -> Self {
173                <$type>::MIN
174            }
175
176            fn max_value() -> Self {
177                <$type>::MAX
178            }
179
180            fn saturating_add(self, rhs: Self) -> Self {
181                (self + rhs).clamp(Self::min_value(), Self::max_value())
182            }
183
184            fn saturating_sub(self, rhs: Self) -> Self {
185                (self - rhs).clamp(Self::min_value(), Self::max_value())
186            }
187        }
188    };
189}
190
191impl_numeric_stepper_float!(f32);
192impl_numeric_stepper_float!(f64);
193impl_numeric_stepper_int!(isize);
194impl_numeric_stepper_int!(usize);
195impl_numeric_stepper_int!(i32);
196impl_numeric_stepper_int!(u32);
197impl_numeric_stepper_int!(i64);
198impl_numeric_stepper_int!(u64);
199
200impl_numeric_stepper_nonzero_int!(NonZeroU32, u32);
201impl_numeric_stepper_nonzero_int!(NonZeroU64, u64);
202impl_numeric_stepper_nonzero_int!(NonZero<usize>, usize);
203
204#[derive(RegisterComponent)]
205pub struct NumberField<T = usize> {
206    id: ElementId,
207    value: T,
208    focus_handle: FocusHandle,
209    mode: Entity<NumberFieldMode>,
210    format: Box<dyn FnOnce(&T) -> String>,
211    large_step: T,
212    small_step: T,
213    step: T,
214    min_value: T,
215    max_value: T,
216    on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
217    on_change: Rc<dyn Fn(&T, &mut Window, &mut App) + 'static>,
218    tab_index: Option<isize>,
219}
220
221impl<T: NumberFieldType> NumberField<T> {
222    pub fn new(id: impl Into<ElementId>, value: T, window: &mut Window, cx: &mut App) -> Self {
223        let id = id.into();
224
225        let (mode, focus_handle) = window.with_id(id.clone(), |window| {
226            let mode = window.use_state(cx, |_, _| NumberFieldMode::default());
227            let focus_handle = window.use_state(cx, |_, cx| cx.focus_handle());
228            (mode, focus_handle)
229        });
230
231        Self {
232            id,
233            mode,
234            value,
235            focus_handle: focus_handle.read(cx).clone(),
236            format: Box::new(T::default_format),
237            large_step: T::large_step(),
238            step: T::default_step(),
239            small_step: T::small_step(),
240            min_value: T::min_value(),
241            max_value: T::max_value(),
242            on_reset: None,
243            on_change: Rc::new(|_, _, _| {}),
244            tab_index: None,
245        }
246    }
247
248    pub fn format(mut self, format: impl FnOnce(&T) -> String + 'static) -> Self {
249        self.format = Box::new(format);
250        self
251    }
252
253    pub fn small_step(mut self, step: T) -> Self {
254        self.small_step = step;
255        self
256    }
257
258    pub fn normal_step(mut self, step: T) -> Self {
259        self.step = step;
260        self
261    }
262
263    pub fn large_step(mut self, step: T) -> Self {
264        self.large_step = step;
265        self
266    }
267
268    pub fn min(mut self, min: T) -> Self {
269        self.min_value = min;
270        self
271    }
272
273    pub fn max(mut self, max: T) -> Self {
274        self.max_value = max;
275        self
276    }
277
278    pub fn on_reset(
279        mut self,
280        on_reset: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
281    ) -> Self {
282        self.on_reset = Some(Box::new(on_reset));
283        self
284    }
285
286    pub fn tab_index(mut self, tab_index: isize) -> Self {
287        self.tab_index = Some(tab_index);
288        self
289    }
290
291    pub fn on_change(mut self, on_change: impl Fn(&T, &mut Window, &mut App) + 'static) -> Self {
292        self.on_change = Rc::new(on_change);
293        self
294    }
295}
296
297impl<T: NumberFieldType> IntoElement for NumberField<T> {
298    type Element = gpui::Component<Self>;
299
300    fn into_element(self) -> Self::Element {
301        gpui::Component::new(self)
302    }
303}
304
305impl<T: NumberFieldType> RenderOnce for NumberField<T> {
306    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
307        let mut tab_index = self.tab_index;
308
309        let get_step = {
310            let large_step = self.large_step;
311            let step = self.step;
312            let small_step = self.small_step;
313            move |modifiers: Modifiers| -> T {
314                if modifiers.shift {
315                    large_step
316                } else if modifiers.alt {
317                    small_step
318                } else {
319                    step
320                }
321            }
322        };
323
324        let bg_color = cx.theme().colors().surface_background;
325        let hover_bg_color = cx.theme().colors().element_hover;
326
327        let border_color = cx.theme().colors().border_variant;
328        let focus_border_color = cx.theme().colors().border_focused;
329
330        let base_button = |icon: IconName| {
331            h_flex()
332                .cursor_pointer()
333                .p_1p5()
334                .size_full()
335                .justify_center()
336                .overflow_hidden()
337                .border_1()
338                .border_color(border_color)
339                .bg(bg_color)
340                .hover(|s| s.bg(hover_bg_color))
341                .focus_visible(|s| s.border_color(focus_border_color).bg(hover_bg_color))
342                .child(Icon::new(icon).size(IconSize::Small))
343        };
344
345        h_flex()
346            .id(self.id.clone())
347            .track_focus(&self.focus_handle)
348            .gap_1()
349            .when_some(self.on_reset, |this, on_reset| {
350                this.child(
351                    IconButton::new("reset", IconName::RotateCcw)
352                        .icon_size(IconSize::Small)
353                        .when_some(tab_index.as_mut(), |this, tab_index| {
354                            *tab_index += 1;
355                            this.tab_index(*tab_index - 1)
356                        })
357                        .on_click(on_reset),
358                )
359            })
360            .child(
361                h_flex()
362                    .map(|decrement| {
363                        let decrement_handler = {
364                            let value = self.value;
365                            let on_change = self.on_change.clone();
366                            let min = self.min_value;
367                            move |click: &ClickEvent, window: &mut Window, cx: &mut App| {
368                                let step = get_step(click.modifiers());
369                                let new_value = value.saturating_sub(step);
370                                let new_value = if new_value < min { min } else { new_value };
371                                on_change(&new_value, window, cx);
372                            }
373                        };
374
375                        decrement.child(
376                            base_button(IconName::Dash)
377                                .id("decrement_button")
378                                .rounded_tl_sm()
379                                .rounded_bl_sm()
380                                .tab_index(
381                                    tab_index
382                                        .as_mut()
383                                        .map(|tab_index| {
384                                            *tab_index += 1;
385                                            *tab_index - 1
386                                        })
387                                        .unwrap_or(0),
388                                )
389                                .on_click(decrement_handler),
390                        )
391                    })
392                    .child(
393                        h_flex()
394                            .min_w_16()
395                            .size_full()
396                            .border_y_1()
397                            .border_color(border_color)
398                            .bg(bg_color)
399                            .in_focus(|this| this.border_color(focus_border_color))
400                            .child(match *self.mode.read(cx) {
401                                NumberFieldMode::Read => h_flex()
402                                    .px_1()
403                                    .flex_1()
404                                    .justify_center()
405                                    .child(Label::new((self.format)(&self.value)))
406                                    .into_any_element(),
407                                // Edit mode is disabled until we implement center text alignment for editor
408                                // mode.write(cx, NumberFieldMode::Edit);
409                                //
410                                // When we get to making Edit mode work, we shouldn't even focus the decrement/increment buttons.
411                                // Focus should go instead straight to the editor, avoiding any double-step focus.
412                                // In this world, the buttons become a mouse-only interaction, given users should be able
413                                // to do everything they'd do with the buttons straight in the editor anyway.
414                                NumberFieldMode::Edit => h_flex()
415                                    .flex_1()
416                                    .child(window.use_state(cx, {
417                                        |window, cx| {
418                                            let previous_focus_handle = window.focused(cx);
419                                            let mut editor = Editor::single_line(window, cx);
420                                            let mut style = EditorStyle::default();
421                                            style.text.text_align = gpui::TextAlign::Right;
422                                            editor.set_style(style, window, cx);
423
424                                            editor.set_text(format!("{}", self.value), window, cx);
425                                            cx.on_focus_out(&editor.focus_handle(cx), window, {
426                                                let mode = self.mode.clone();
427                                                let min = self.min_value;
428                                                let max = self.max_value;
429                                                let on_change = self.on_change.clone();
430                                                move |this, _, window, cx| {
431                                                    if let Ok(new_value) =
432                                                        this.text(cx).parse::<T>()
433                                                    {
434                                                        let new_value = if new_value < min {
435                                                            min
436                                                        } else if new_value > max {
437                                                            max
438                                                        } else {
439                                                            new_value
440                                                        };
441
442                                                        if let Some(previous) =
443                                                            previous_focus_handle.as_ref()
444                                                        {
445                                                            window.focus(previous);
446                                                        }
447                                                        on_change(&new_value, window, cx);
448                                                    };
449                                                    mode.write(cx, NumberFieldMode::Read);
450                                                }
451                                            })
452                                            .detach();
453
454                                            window.focus(&editor.focus_handle(cx));
455
456                                            editor
457                                        }
458                                    }))
459                                    .on_action::<menu::Confirm>({
460                                        move |_, window, _| {
461                                            window.blur();
462                                        }
463                                    })
464                                    .into_any_element(),
465                            }),
466                    )
467                    .map(|increment| {
468                        let increment_handler = {
469                            let value = self.value;
470                            let on_change = self.on_change.clone();
471                            let max = self.max_value;
472                            move |click: &ClickEvent, window: &mut Window, cx: &mut App| {
473                                let step = get_step(click.modifiers());
474                                let new_value = value.saturating_add(step);
475                                let new_value = if new_value > max { max } else { new_value };
476                                on_change(&new_value, window, cx);
477                            }
478                        };
479
480                        increment.child(
481                            base_button(IconName::Plus)
482                                .id("increment_button")
483                                .rounded_tr_sm()
484                                .rounded_br_sm()
485                                .tab_index(
486                                    tab_index
487                                        .as_mut()
488                                        .map(|tab_index| {
489                                            *tab_index += 1;
490                                            *tab_index - 1
491                                        })
492                                        .unwrap_or(0),
493                                )
494                                .on_click(increment_handler),
495                        )
496                    }),
497            )
498    }
499}
500
501impl Component for NumberField<usize> {
502    fn scope() -> ComponentScope {
503        ComponentScope::Input
504    }
505
506    fn name() -> &'static str {
507        "Number Field"
508    }
509
510    fn sort_name() -> &'static str {
511        Self::name()
512    }
513
514    fn description() -> Option<&'static str> {
515        Some("A numeric input element with increment and decrement buttons.")
516    }
517
518    fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
519        let stepper_example = window.use_state(cx, |_, _| 100.0);
520
521        Some(
522            v_flex()
523                .gap_6()
524                .children(vec![single_example(
525                    "Default Numeric Stepper",
526                    NumberField::new(
527                        "numeric-stepper-component-preview",
528                        *stepper_example.read(cx),
529                        window,
530                        cx,
531                    )
532                    .on_change({
533                        let stepper_example = stepper_example.clone();
534                        move |value, _, cx| stepper_example.write(cx, *value)
535                    })
536                    .min(1.0)
537                    .max(100.0)
538                    .into_any_element(),
539                )])
540                .into_any_element(),
541        )
542    }
543}