gpui_component/setting/fields/
mod.rs1mod 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
21pub(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#[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
137pub struct SettingField<T> {
139 pub(crate) field_type: SettingFieldType,
140 pub(crate) style: StyleRefinement,
141 pub(crate) value: Rc<dyn Fn(&App) -> T>,
143 pub(crate) set_value: Rc<dyn Fn(T, &mut App)>,
145 pub(crate) default_value: Option<T>,
146 pub(crate) reset_handler: Option<ResetHandler>,
152}
153
154impl SettingField<bool> {
155 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 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 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 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 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 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 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 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 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 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 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
325pub 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 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}