gpui_component/setting/fields/
number.rs1use std::rc::Rc;
2
3use gpui::{
4 AnyElement, App, AppContext as _, Entity, IntoElement, SharedString, StyleRefinement, Styled,
5 Subscription, Window, prelude::FluentBuilder as _,
6};
7
8use crate::{
9 AxisExt, Sizable, StyledExt,
10 input::{InputEvent, InputState, NumberInput, NumberInputEvent, StepAction},
11 setting::{
12 AnySettingField, RenderOptions,
13 fields::{SettingFieldRender, get_value, set_value},
14 },
15};
16
17#[derive(Clone, Debug)]
18pub struct NumberFieldOptions {
19 pub min: f64,
21 pub max: f64,
23 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 initial_value: f64,
52 _subscriptions: Vec<Subscription>,
53}
54
55impl SettingFieldRender for NumberField {
56 fn render(
57 &self,
58 field: Rc<dyn AnySettingField>,
59 options: &RenderOptions,
60 style: &StyleRefinement,
61 window: &mut Window,
62 cx: &mut App,
63 ) -> AnyElement {
64 let value = get_value::<f64>(&field, cx);
65 let set_value = set_value::<f64>(&field, cx);
66 let num_options = self.options.clone();
67
68 let state = window
69 .use_keyed_state(
70 SharedString::from(format!(
71 "number-state-{}-{}-{}",
72 options.page_ix, options.group_ix, options.item_ix
73 )),
74 cx,
75 |window, cx| {
76 let input =
77 cx.new(|cx| InputState::new(window, cx).default_value(value.to_string()));
78 let _subscriptions = vec![
79 cx.subscribe_in(&input, window, {
80 move |_, input, event: &NumberInputEvent, window, cx| match event {
81 NumberInputEvent::Step(action) => input.update(cx, |input, cx| {
82 let value = input.value();
83 if let Ok(value) = value.parse::<f64>() {
84 let new_value = if *action == StepAction::Increment {
85 value + num_options.step
86 } else {
87 value - num_options.step
88 };
89 input.set_value(
90 SharedString::from(new_value.to_string()),
91 window,
92 cx,
93 );
94 }
95 }),
96 }
97 }),
98 cx.subscribe_in(&input, window, {
99 move |state: &mut State, input, event: &InputEvent, window, cx| {
100 match event {
101 InputEvent::Change => {
102 input.update(cx, |input, cx| {
103 let value = input.value();
104 if value == state.initial_value.to_string() {
105 return;
106 }
107
108 if let Ok(value) = value.parse::<f64>() {
109 let clamp_value =
110 value.clamp(num_options.min, num_options.max);
111
112 set_value(clamp_value, cx);
113 state.initial_value = clamp_value;
114 if clamp_value != value {
115 input.set_value(
116 SharedString::from(clamp_value.to_string()),
117 window,
118 cx,
119 );
120 }
121 }
122 });
123 }
124 _ => {}
125 }
126 }
127 }),
128 ];
129
130 State {
131 input,
132 initial_value: value,
133 _subscriptions,
134 }
135 },
136 )
137 .read(cx);
138
139 NumberInput::new(&state.input)
140 .with_size(options.size)
141 .map(|this| {
142 if options.layout.is_horizontal() {
143 this.w_32()
144 } else {
145 this.w_full()
146 }
147 })
148 .refine_style(style)
149 .into_any_element()
150 }
151}