settings_ui_core.rs

  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);