gpui_component/setting/fields/
number.rs

1use std::rc::Rc;
2
3use gpui::{
4    prelude::FluentBuilder as _, AnyElement, App, AppContext as _, Entity, IntoElement,
5    SharedString, StyleRefinement, Styled, Window,
6};
7
8use crate::{
9    input::{InputState, NumberInput, NumberInputEvent},
10    setting::{
11        fields::{get_value, set_value, SettingFieldRender},
12        AnySettingField, RenderOptions,
13    },
14    AxisExt, Sizable, StyledExt,
15};
16
17#[derive(Clone, Debug)]
18pub struct NumberFieldOptions {
19    /// The minimum value for the number input, default is `f64::MIN`.
20    pub min: f64,
21    /// The maximum value for the number input, default is `f64::MAX`.
22    pub max: f64,
23    /// The step value for the number input, default is `1.0`.
24    pub step: f64,
25}
26
27impl Default for NumberFieldOptions {
28    fn default() -> Self {
29        Self {
30            min: f64::MIN,
31            max: f64::MAX,
32            step: 1.0,
33        }
34    }
35}
36
37pub(crate) struct NumberField {
38    options: NumberFieldOptions,
39}
40
41impl NumberField {
42    pub(crate) fn new(options: Option<&NumberFieldOptions>) -> Self {
43        Self {
44            options: options.cloned().unwrap_or_default(),
45        }
46    }
47}
48
49struct State {
50    input: Entity<InputState>,
51    _subscription: gpui::Subscription,
52}
53
54impl SettingFieldRender for NumberField {
55    fn render(
56        &self,
57        field: Rc<dyn AnySettingField>,
58        options: &RenderOptions,
59        style: &StyleRefinement,
60        window: &mut Window,
61        cx: &mut App,
62    ) -> AnyElement {
63        let value = get_value::<f64>(&field, cx);
64        let set_value = set_value::<f64>(&field, cx);
65        let num_options = self.options.clone();
66
67        let state = window
68            .use_keyed_state("number-state", cx, |window, cx| {
69                let input =
70                    cx.new(|cx| InputState::new(window, cx).default_value(value.to_string()));
71                let _subscription = cx.subscribe_in(&input, window, {
72                    move |_, input, event: &NumberInputEvent, window, cx| match event {
73                        NumberInputEvent::Step(action) => input.update(cx, |input, cx| {
74                            let value = input.value();
75                            if let Ok(value) = value.parse::<f64>() {
76                                let new_value = if *action == crate::input::StepAction::Increment {
77                                    (value + num_options.step).min(num_options.max)
78                                } else {
79                                    (value - num_options.step).max(num_options.min)
80                                };
81                                set_value(new_value, cx);
82                                input.set_value(
83                                    SharedString::from(new_value.to_string()),
84                                    window,
85                                    cx,
86                                );
87                            }
88                        }),
89                    }
90                });
91
92                State {
93                    input,
94                    _subscription,
95                }
96            })
97            .read(cx);
98
99        NumberInput::new(&state.input)
100            .with_size(options.size)
101            .map(|this| {
102                if options.layout.is_horizontal() {
103                    this.w_32()
104                } else {
105                    this.w_full()
106                }
107            })
108            .refine_style(style)
109            .into_any_element()
110    }
111}