1use std::any::TypeId;
2
3use anyhow::Context as _;
4use fs::Fs;
5use gpui::{AnyElement, App, AppContext as _, ReadGlobal as _, Window};
6use smallvec::SmallVec;
7
8use crate::SettingsStore;
9
10pub trait SettingsUi {
11 fn settings_ui_item() -> SettingsUiItem {
12 // todo(settings_ui): remove this default impl, only entry should have a default impl
13 // because it's expected that the macro or custom impl use the item and the known paths to create the entry
14 SettingsUiItem::None
15 }
16
17 fn settings_ui_entry() -> SettingsUiEntry {
18 SettingsUiEntry {
19 path: None,
20 title: "None entry",
21 item: SettingsUiItem::None,
22 }
23 }
24}
25
26pub struct SettingsUiEntry {
27 // todo(settings_ui): move this back here once there isn't a None variant
28 /// The path in the settings JSON file for this setting. Relative to parent
29 /// None implies `#[serde(flatten)]` or `Settings::KEY.is_none()` for top level settings
30 pub path: Option<&'static str>,
31 pub title: &'static str,
32 pub item: SettingsUiItem,
33}
34
35pub enum SettingsUiItemSingle {
36 SwitchField,
37 /// A numeric stepper for a specific type of number
38 NumericStepper(NumType),
39 ToggleGroup(&'static [&'static str]),
40 /// This should be used when toggle group size > 6
41 DropDown(&'static [&'static str]),
42 Custom(Box<dyn Fn(SettingsValue<serde_json::Value>, &mut Window, &mut App) -> AnyElement>),
43}
44
45pub struct SettingsValue<T> {
46 pub title: &'static str,
47 pub path: SmallVec<[&'static str; 1]>,
48 pub value: Option<T>,
49 pub default_value: T,
50}
51
52impl<T> SettingsValue<T> {
53 pub fn read(&self) -> &T {
54 match &self.value {
55 Some(value) => value,
56 None => &self.default_value,
57 }
58 }
59}
60
61impl SettingsValue<serde_json::Value> {
62 pub fn write_value(path: &SmallVec<[&'static str; 1]>, value: serde_json::Value, cx: &mut App) {
63 let settings_store = SettingsStore::global(cx);
64 let fs = <dyn Fs>::global(cx);
65
66 let rx = settings_store.update_settings_file_at_path(fs.clone(), path.as_slice(), value);
67 let path = path.clone();
68 cx.background_spawn(async move {
69 rx.await?
70 .with_context(|| format!("Failed to update setting at path `{:?}`", path.join(".")))
71 })
72 .detach_and_log_err(cx);
73 }
74}
75
76impl<T: serde::Serialize> SettingsValue<T> {
77 pub fn write(
78 path: &SmallVec<[&'static str; 1]>,
79 value: T,
80 cx: &mut App,
81 ) -> Result<(), serde_json::Error> {
82 SettingsValue::write_value(path, serde_json::to_value(value)?, cx);
83 Ok(())
84 }
85}
86
87pub struct SettingsUiItemDynamic {
88 pub options: Vec<SettingsUiEntry>,
89 pub determine_option: fn(&serde_json::Value, &mut App) -> usize,
90}
91
92pub struct SettingsUiItemGroup {
93 pub items: Vec<SettingsUiEntry>,
94}
95
96pub enum SettingsUiItem {
97 Group(SettingsUiItemGroup),
98 Single(SettingsUiItemSingle),
99 Dynamic(SettingsUiItemDynamic),
100 None,
101}
102
103impl SettingsUi for bool {
104 fn settings_ui_item() -> SettingsUiItem {
105 SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
106 }
107}
108
109impl SettingsUi for Option<bool> {
110 fn settings_ui_item() -> SettingsUiItem {
111 SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
112 }
113}
114
115#[repr(u8)]
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
117pub enum NumType {
118 U64 = 0,
119 U32 = 1,
120 F32 = 2,
121}
122pub static NUM_TYPE_NAMES: std::sync::LazyLock<[&'static str; NumType::COUNT]> =
123 std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_name));
124pub static NUM_TYPE_IDS: std::sync::LazyLock<[TypeId; NumType::COUNT]> =
125 std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_id));
126
127impl NumType {
128 const COUNT: usize = 3;
129 const ALL: [NumType; Self::COUNT] = [NumType::U64, NumType::U32, NumType::F32];
130
131 pub fn type_id(self) -> TypeId {
132 match self {
133 NumType::U64 => TypeId::of::<u64>(),
134 NumType::U32 => TypeId::of::<u32>(),
135 NumType::F32 => TypeId::of::<f32>(),
136 }
137 }
138
139 pub fn type_name(self) -> &'static str {
140 match self {
141 NumType::U64 => std::any::type_name::<u64>(),
142 NumType::U32 => std::any::type_name::<u32>(),
143 NumType::F32 => std::any::type_name::<f32>(),
144 }
145 }
146}
147
148macro_rules! numeric_stepper_for_num_type {
149 ($type:ty, $num_type:ident) => {
150 impl SettingsUi for $type {
151 fn settings_ui_item() -> SettingsUiItem {
152 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type))
153 }
154 }
155
156 impl SettingsUi for Option<$type> {
157 fn settings_ui_item() -> SettingsUiItem {
158 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type))
159 }
160 }
161 };
162}
163
164numeric_stepper_for_num_type!(u64, U64);
165numeric_stepper_for_num_type!(u32, U32);
166// todo(settings_ui) is there a better ui for f32?
167numeric_stepper_for_num_type!(f32, F32);