settings.rs

  1use crate::{ThemeRegistry, ThemeVariant};
  2use anyhow::Result;
  3use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
  4use schemars::{
  5    gen::SchemaGenerator,
  6    schema::{InstanceType, Schema, SchemaObject},
  7    JsonSchema,
  8};
  9use serde::{Deserialize, Serialize};
 10use serde_json::Value;
 11use settings2::{Settings, SettingsJsonSchemaParams};
 12use std::sync::Arc;
 13use util::ResultExt as _;
 14
 15const MIN_FONT_SIZE: Pixels = px(6.0);
 16const MIN_LINE_HEIGHT: f32 = 1.0;
 17
 18#[derive(Clone)]
 19pub struct ThemeSettings {
 20    pub buffer_font: Font,
 21    pub buffer_font_size: Pixels,
 22    pub buffer_line_height: BufferLineHeight,
 23    pub active_theme: Arc<ThemeVariant>,
 24}
 25
 26#[derive(Default)]
 27pub struct AdjustedBufferFontSize(Option<Pixels>);
 28
 29#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 30pub struct ThemeSettingsContent {
 31    #[serde(default)]
 32    pub buffer_font_family: Option<String>,
 33    #[serde(default)]
 34    pub buffer_font_size: Option<f32>,
 35    #[serde(default)]
 36    pub buffer_line_height: Option<BufferLineHeight>,
 37    #[serde(default)]
 38    pub buffer_font_features: Option<FontFeatures>,
 39    #[serde(default)]
 40    pub theme: Option<String>,
 41}
 42
 43#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
 44#[serde(rename_all = "snake_case")]
 45pub enum BufferLineHeight {
 46    #[default]
 47    Comfortable,
 48    Standard,
 49    Custom(f32),
 50}
 51
 52impl BufferLineHeight {
 53    pub fn value(&self) -> f32 {
 54        match self {
 55            BufferLineHeight::Comfortable => 1.618,
 56            BufferLineHeight::Standard => 1.3,
 57            BufferLineHeight::Custom(line_height) => *line_height,
 58        }
 59    }
 60}
 61
 62impl ThemeSettings {
 63    pub fn buffer_font_size(&self, cx: &mut AppContext) -> Pixels {
 64        let font_size = *cx
 65            .default_global::<AdjustedBufferFontSize>()
 66            .0
 67            .get_or_insert(self.buffer_font_size.into());
 68        font_size.max(MIN_FONT_SIZE)
 69    }
 70
 71    pub fn line_height(&self) -> f32 {
 72        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
 73    }
 74}
 75
 76pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
 77    if let Some(adjusted_size) = cx.default_global::<AdjustedBufferFontSize>().0 {
 78        let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
 79        let delta = adjusted_size - buffer_font_size;
 80        size + delta
 81    } else {
 82        size
 83    }
 84    .max(MIN_FONT_SIZE)
 85}
 86
 87pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
 88    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
 89    let adjusted_size = cx
 90        .default_global::<AdjustedBufferFontSize>()
 91        .0
 92        .get_or_insert(buffer_font_size);
 93    f(adjusted_size);
 94    *adjusted_size = (*adjusted_size).max(MIN_FONT_SIZE - buffer_font_size);
 95    cx.refresh();
 96}
 97
 98pub fn reset_font_size(cx: &mut AppContext) {
 99    if cx.has_global::<AdjustedBufferFontSize>() {
100        cx.global_mut::<AdjustedBufferFontSize>().0 = None;
101        cx.refresh();
102    }
103}
104
105impl settings2::Settings for ThemeSettings {
106    const KEY: Option<&'static str> = None;
107
108    type FileContent = ThemeSettingsContent;
109
110    fn load(
111        defaults: &Self::FileContent,
112        user_values: &[&Self::FileContent],
113        cx: &mut AppContext,
114    ) -> Result<Self> {
115        let themes = cx.default_global::<Arc<ThemeRegistry>>();
116
117        let mut this = Self {
118            buffer_font: Font {
119                family: defaults.buffer_font_family.clone().unwrap().into(),
120                features: defaults.buffer_font_features.clone().unwrap(),
121                weight: FontWeight::default(),
122                style: FontStyle::default(),
123            },
124            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
125            buffer_line_height: defaults.buffer_line_height.unwrap(),
126            active_theme: themes.get("Zed Pro Moonlight").unwrap(),
127            // todo!(Read the theme name from the settings)
128            // active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
129        };
130
131        for value in user_values.into_iter().copied().cloned() {
132            if let Some(value) = value.buffer_font_family {
133                this.buffer_font.family = value.into();
134            }
135            if let Some(value) = value.buffer_font_features {
136                this.buffer_font.features = value;
137            }
138
139            if let Some(value) = &value.theme {
140                if let Some(theme) = themes.get(value).log_err() {
141                    this.active_theme = theme;
142                }
143            }
144
145            merge(
146                &mut this.buffer_font_size,
147                value.buffer_font_size.map(Into::into),
148            );
149            merge(&mut this.buffer_line_height, value.buffer_line_height);
150        }
151
152        Ok(this)
153    }
154
155    fn json_schema(
156        generator: &mut SchemaGenerator,
157        params: &SettingsJsonSchemaParams,
158        cx: &AppContext,
159    ) -> schemars::schema::RootSchema {
160        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
161        let theme_names = cx
162            .global::<Arc<ThemeRegistry>>()
163            .list_names(params.staff_mode)
164            .map(|theme_name| Value::String(theme_name.to_string()))
165            .collect();
166
167        let theme_name_schema = SchemaObject {
168            instance_type: Some(InstanceType::String.into()),
169            enum_values: Some(theme_names),
170            ..Default::default()
171        };
172
173        root_schema
174            .definitions
175            .extend([("ThemeName".into(), theme_name_schema.into())]);
176
177        root_schema
178            .schema
179            .object
180            .as_mut()
181            .unwrap()
182            .properties
183            .extend([(
184                "theme".to_owned(),
185                Schema::new_ref("#/definitions/ThemeName".into()),
186            )]);
187
188        root_schema
189    }
190}
191
192fn merge<T: Copy>(target: &mut T, value: Option<T>) {
193    if let Some(value) = value {
194        *target = value;
195    }
196}