Skip to main content

gpui_component/setting/fields/
mod.rs

1mod bool;
2mod dropdown;
3mod element;
4mod number;
5mod string;
6
7pub(crate) use bool::*;
8pub(crate) use dropdown::*;
9pub(crate) use element::*;
10pub(crate) use number::*;
11pub(crate) use string::*;
12
13pub use element::SettingFieldElement;
14pub use number::NumberFieldOptions;
15
16use gpui::{AnyElement, App, IntoElement, SharedString, StyleRefinement, Styled, Window};
17use std::{any::Any, rc::Rc};
18
19use crate::setting::RenderOptions;
20
21/// Custom reset behavior for a setting field or item.
22///
23/// The first closure reports whether the target is "dirty" (controls the reset
24/// button visibility), the second performs the reset. Used by `element`/`render`
25/// fields and custom element items whose state is not expressed through the
26/// typed `default_value` mechanism.
27pub(crate) type ResetHandler = (Rc<dyn Fn(&App) -> bool>, Rc<dyn Fn(&mut Window, &mut App)>);
28
29pub(crate) trait SettingFieldRender {
30    #[allow(clippy::too_many_arguments)]
31    fn render(
32        &self,
33        field: Rc<dyn AnySettingField>,
34        options: &RenderOptions,
35        style: &StyleRefinement,
36        window: &mut Window,
37        cx: &mut App,
38    ) -> AnyElement;
39}
40
41pub(crate) fn get_value<T: Clone + 'static>(field: &Rc<dyn AnySettingField>, cx: &mut App) -> T {
42    let setting_field = field
43        .as_any()
44        .downcast_ref::<SettingField<T>>()
45        .expect("Failed to downcast setting field");
46    (setting_field.value)(cx)
47}
48
49pub(crate) fn set_value<T: Clone + 'static>(
50    field: &Rc<dyn AnySettingField>,
51    _cx: &mut App,
52) -> Rc<dyn Fn(T, &mut App)> {
53    let setting_field = field
54        .as_any()
55        .downcast_ref::<SettingField<T>>()
56        .expect("Failed to downcast setting field");
57    setting_field.set_value.clone()
58}
59
60/// The type of setting field to render.
61#[derive(Clone)]
62pub enum SettingFieldType {
63    Switch,
64    Checkbox,
65    NumberInput {
66        options: NumberFieldOptions,
67    },
68    Input,
69    Dropdown {
70        options: Vec<(SharedString, SharedString)>,
71        scrollable: bool,
72    },
73    Element {
74        element: Rc<dyn SettingFieldElement<Element = AnyElement>>,
75    },
76}
77
78impl SettingFieldType {
79    #[inline]
80    pub(crate) fn is_switch(&self) -> bool {
81        matches!(self, SettingFieldType::Switch)
82    }
83
84    #[inline]
85    pub(crate) fn is_number_input(&self) -> bool {
86        matches!(self, SettingFieldType::NumberInput { .. })
87    }
88
89    #[inline]
90    pub(crate) fn is_input(&self) -> bool {
91        matches!(self, SettingFieldType::Input)
92    }
93
94    #[inline]
95    pub(crate) fn is_dropdown(&self) -> bool {
96        matches!(self, SettingFieldType::Dropdown { .. })
97    }
98
99    #[inline]
100    pub(crate) fn is_element(&self) -> bool {
101        matches!(self, SettingFieldType::Element { .. })
102    }
103
104    #[inline]
105    pub(super) fn dropdown_options(&self) -> Option<&Vec<(SharedString, SharedString)>> {
106        match self {
107            SettingFieldType::Dropdown { options, .. } => Some(options),
108            _ => None,
109        }
110    }
111
112    #[inline]
113    pub(super) fn dropdown_scrollable(&self) -> bool {
114        match self {
115            SettingFieldType::Dropdown { scrollable, .. } => *scrollable,
116            _ => false,
117        }
118    }
119
120    #[inline]
121    pub(super) fn number_input_options(&self) -> Option<&NumberFieldOptions> {
122        match self {
123            SettingFieldType::NumberInput { options } => Some(options),
124            _ => None,
125        }
126    }
127
128    #[inline]
129    pub(super) fn element(&self) -> Rc<dyn SettingFieldElement<Element = AnyElement>> {
130        match self {
131            SettingFieldType::Element { element } => element.clone(),
132            _ => unreachable!("element_render called on non-element field"),
133        }
134    }
135}
136
137/// A setting field that can get and set a value of type T in the App.
138pub struct SettingField<T> {
139    pub(crate) field_type: SettingFieldType,
140    pub(crate) style: StyleRefinement,
141    /// Function to get the value for this field.
142    pub(crate) value: Rc<dyn Fn(&App) -> T>,
143    /// Function to set the value for this field.
144    pub(crate) set_value: Rc<dyn Fn(T, &mut App)>,
145    pub(crate) default_value: Option<T>,
146    /// Optional custom reset behavior, used by `element`/`render` fields whose
147    /// state is not expressed through the typed `default_value` mechanism.
148    ///
149    /// The first closure reports whether the field is "dirty" (controls the
150    /// reset button visibility), the second performs the reset.
151    pub(crate) reset_handler: Option<ResetHandler>,
152}
153
154impl SettingField<bool> {
155    /// Create a new Switch field.
156    pub fn switch<V, S>(value: V, set_value: S) -> Self
157    where
158        V: Fn(&App) -> bool + 'static,
159        S: Fn(bool, &mut App) + 'static,
160    {
161        Self::new(SettingFieldType::Switch, value, set_value)
162    }
163
164    /// Create a new Checkbox field.
165    pub fn checkbox<V, S>(value: V, set_value: S) -> Self
166    where
167        V: Fn(&App) -> bool + 'static,
168        S: Fn(bool, &mut App) + 'static,
169    {
170        Self::new(SettingFieldType::Checkbox, value, set_value)
171    }
172}
173
174impl SettingField<SharedString> {
175    /// Create a new Input field.
176    pub fn input<V, S>(value: V, set_value: S) -> Self
177    where
178        V: Fn(&App) -> SharedString + 'static,
179        S: Fn(SharedString, &mut App) + 'static,
180    {
181        Self::new(SettingFieldType::Input, value, set_value)
182    }
183
184    /// Create a new Dropdown field with the given options.
185    ///
186    /// The popup menu does not scroll. For long option lists that may exceed
187    /// the viewport, use [`Self::scrollable_dropdown`] instead.
188    pub fn dropdown<V, S>(
189        options: Vec<(SharedString, SharedString)>,
190        value: V,
191        set_value: S,
192    ) -> Self
193    where
194        V: Fn(&App) -> SharedString + 'static,
195        S: Fn(SharedString, &mut App) + 'static,
196    {
197        Self::new(
198            SettingFieldType::Dropdown {
199                options,
200                scrollable: false,
201            },
202            value,
203            set_value,
204        )
205    }
206
207    /// Create a new Dropdown field whose popup menu scrolls when its content
208    /// exceeds the viewport. Use this for long option lists where the
209    /// non-scrolling [`Self::dropdown`] would push items below the fold.
210    pub fn scrollable_dropdown<V, S>(
211        options: Vec<(SharedString, SharedString)>,
212        value: V,
213        set_value: S,
214    ) -> Self
215    where
216        V: Fn(&App) -> SharedString + 'static,
217        S: Fn(SharedString, &mut App) + 'static,
218    {
219        Self::new(
220            SettingFieldType::Dropdown {
221                options,
222                scrollable: true,
223            },
224            value,
225            set_value,
226        )
227    }
228
229    /// Create a new setting field with the given custom element that implements [`SettingFieldElement`] trait.
230    ///
231    /// See also [`SettingField::render`] for simply building with a render closure.
232    pub fn element<E>(element: E) -> Self
233    where
234        E: SettingFieldElement + 'static,
235    {
236        Self::new(
237            SettingFieldType::Element {
238                element: Rc::new(AnySettingFieldElement(element)),
239            },
240            |_| SharedString::default(),
241            |_, _| {},
242        )
243    }
244
245    /// Create a new setting field with the given element render closure.
246    ///
247    /// See also [`SettingField::element`] for building with a custom field for more complex scenarios.
248    pub fn render<E, R>(element_render: R) -> Self
249    where
250        E: IntoElement + 'static,
251        R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
252    {
253        Self::element(
254            move |options: &RenderOptions, window: &mut Window, cx: &mut App| {
255                (element_render)(options, window, cx).into_any_element()
256            },
257        )
258    }
259}
260
261impl SettingField<f64> {
262    /// Create a new Number Input field with the given options.
263    pub fn number_input<V, S>(options: NumberFieldOptions, value: V, set_value: S) -> Self
264    where
265        V: Fn(&App) -> f64 + 'static,
266        S: Fn(f64, &mut App) + 'static,
267    {
268        Self::new(SettingFieldType::NumberInput { options }, value, set_value)
269    }
270}
271
272impl<T> SettingField<T> {
273    /// Create a new setting field with the given get and set functions.
274    fn new<V, S>(field_type: SettingFieldType, value: V, set_value: S) -> Self
275    where
276        V: Fn(&App) -> T + 'static,
277        S: Fn(T, &mut App) + 'static,
278    {
279        Self {
280            field_type,
281            style: StyleRefinement::default(),
282            value: Rc::new(value),
283            set_value: Rc::new(set_value),
284            default_value: None,
285            reset_handler: None,
286        }
287    }
288
289    /// Set the default value for this setting field, default is None.
290    ///
291    /// If set, this value can be used to reset the setting to its default state.
292    /// If not set, the setting cannot be reset.
293    pub fn default_value(mut self, default_value: impl Into<T>) -> Self {
294        self.default_value = Some(default_value.into());
295        self
296    }
297
298    /// Provide custom reset behavior for this field.
299    ///
300    /// This is intended for [`SettingField::element`] / [`SettingField::render`]
301    /// fields, whose state is managed externally and therefore not covered by
302    /// the typed [`SettingField::default_value`] reset mechanism.
303    ///
304    /// - `is_dirty` reports whether the field differs from its default and thus
305    ///   whether the reset button should appear.
306    /// - `reset` performs the reset.
307    ///
308    /// When set, this takes precedence over the `default_value` based reset.
309    pub fn on_reset<D, R>(mut self, is_dirty: D, reset: R) -> Self
310    where
311        D: Fn(&App) -> bool + 'static,
312        R: Fn(&mut Window, &mut App) + 'static,
313    {
314        self.reset_handler = Some((Rc::new(is_dirty), Rc::new(reset)));
315        self
316    }
317}
318
319impl<T> Styled for SettingField<T> {
320    fn style(&mut self) -> &mut StyleRefinement {
321        &mut self.style
322    }
323}
324
325/// A trait for setting fields that allows for dynamic typing.
326pub trait AnySettingField {
327    fn as_any(&self) -> &dyn std::any::Any;
328    fn type_name(&self) -> &'static str;
329    fn type_id(&self) -> std::any::TypeId;
330    fn field_type(&self) -> &SettingFieldType;
331    fn style(&self) -> &StyleRefinement;
332    fn is_resettable(&self, cx: &App) -> bool;
333    fn reset(&self, window: &mut Window, cx: &mut App);
334}
335
336impl<T: Clone + PartialEq + Send + Sync + 'static> AnySettingField for SettingField<T> {
337    fn as_any(&self) -> &dyn Any {
338        self
339    }
340
341    fn type_name(&self) -> &'static str {
342        std::any::type_name::<T>()
343    }
344
345    fn type_id(&self) -> std::any::TypeId {
346        std::any::TypeId::of::<T>()
347    }
348
349    fn field_type(&self) -> &SettingFieldType {
350        &self.field_type
351    }
352
353    fn style(&self) -> &StyleRefinement {
354        &self.style
355    }
356
357    fn is_resettable(&self, cx: &App) -> bool {
358        if let Some((is_dirty, _)) = self.reset_handler.as_ref() {
359            return is_dirty(cx);
360        }
361
362        // `element`/`render` fields carry no typed value (their `value` always
363        // returns the default), so the `default_value` comparison is meaningless
364        // for them. Without a custom `on_reset`, they are not resettable.
365        if self.field_type.is_element() {
366            return false;
367        }
368
369        let Some(default_value) = self.default_value.as_ref() else {
370            return false;
371        };
372
373        &(self.value)(cx) != default_value
374    }
375
376    fn reset(&self, window: &mut Window, cx: &mut App) {
377        if let Some((_, reset)) = self.reset_handler.as_ref() {
378            reset(window, cx);
379            return;
380        }
381
382        let Some(default_value) = self.default_value.as_ref() else {
383            return;
384        };
385
386        (self.set_value)(default_value.clone(), cx)
387    }
388}