gpui_component/setting/fields/
number.rs1use 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 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 _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}