settings.rs

  1use crate::{Theme, ThemeRegistry};
  2use anyhow::Result;
  3use gpui2::{FontFeatures, SharedString, Font, AppContext, Pixels, px, FontWeight, FontStyle};
  4use schemars::{
  5    gen::SchemaGenerator,
  6    schema::{InstanceType, Schema, SchemaObject},
  7    JsonSchema,
  8};
  9use serde::{Deserialize, Serialize};
 10use serde_json::Value;
 11use settings2::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, JsonSchema)]
 19pub struct ThemeSettings {
 20    pub buffer_font: Font,
 21    pub buffer_font_size: Pixels,
 22    pub buffer_line_height: BufferLineHeight,
 23    #[serde(skip)]
 24    pub theme: Arc<Theme>,
 25}
 26
 27pub struct AdjustedBufferFontSize(pub f32);
 28
 29#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 30pub struct ThemeSettingsContent {
 31    #[serde(default)]
 32    pub buffer_font_family: Option<SharedString>,
 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<SharedString>,
 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: &AppContext) -> f32 {
 64        if cx.has_global::<AdjustedBufferFontSize>() {
 65            cx.global::<AdjustedBufferFontSize>().0
 66        } else {
 67            self.buffer_font_size
 68        }
 69        .max(MIN_FONT_SIZE)
 70    }
 71
 72    pub fn line_height(&self) -> f32 {
 73        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
 74    }
 75}
 76
 77pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 {
 78    if let Some(adjusted_size) = cx.try_global::<AdjustedBufferFontSize>() {
 79        let buffer_font_size = settings2::get::<ThemeSettings>(cx).buffer_font_size;
 80        let delta = adjusted_size - buffer_font_size;
 81        size + delta
 82    } else {
 83        size
 84    }.max(MIN_FONT_SIZE)
 85}
 86
 87pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
 88    if !cx.has_global::<AdjustedBufferFontSize>() {
 89        let buffer_font_size = settings2::get::<ThemeSettings>(cx).buffer_font_size;
 90        cx.set_global(AdjustedBufferFontSize(buffer_font_size));
 91    }
 92    let mut delta = cx.global_mut::<AdjustedBufferFontSize>();
 93    f(&mut delta.0);
 94    delta.0 = delta
 95        .0
 96        .max(MIN_FONT_SIZE - settings2::get::<ThemeSettings>(cx).buffer_font_size);
 97    cx.refresh_windows();
 98}
 99
100pub fn reset_font_size(cx: &mut AppContext) {
101    if cx.has_global::<AdjustedBufferFontSize>() {
102        cx.remove_global::<AdjustedBufferFontSize>();
103        cx.refresh_windows();
104    }
105}
106
107impl settings2::Setting for ThemeSettings {
108    const KEY: Option<&'static str> = None;
109
110    type FileContent = ThemeSettingsContent;
111
112    fn load(
113        defaults: &Self::FileContent,
114        user_values: &[&Self::FileContent],
115        cx: &AppContext,
116    ) -> Result<Self> {
117        let themes = cx.global::<Arc<ThemeRegistry>>();
118
119        let mut this = Self {
120            buffer_font: Font {
121                family: defaults.buffer_font_family.clone().unwrap(),
122                features: defaults.buffer_font_features.clone().unwrap(),
123                weight: FontWeight::default(),
124                style: FontStyle::default(),
125            },
126            buffer_font_size: defaults.buffer_font_size.unwrap(),
127            buffer_line_height: defaults.buffer_line_height.unwrap(),
128            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
129        };
130
131        for value in user_values.into_iter().copied().cloned() {
132            let font_cache = cx.font_cache();
133            let mut family_changed = false;
134            if let Some(value) = value.buffer_font_family {
135                this.buffer_font_family_name = value;
136                family_changed = true;
137            }
138            if let Some(value) = value.buffer_font_features {
139                this.buffer_font_features = value;
140                family_changed = true;
141            }
142            if family_changed {
143                if let Some(id) = font_cache
144                    .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features)
145                    .log_err()
146                {
147                    this.buffer_font_family = id;
148                }
149            }
150
151            if let Some(value) = &value.theme {
152                if let Some(theme) = themes.get(value).log_err() {
153                    this.theme = theme;
154                }
155            }
156
157            merge(&mut this.buffer_font_size, value.buffer_font_size);
158            merge(&mut this.buffer_line_height, value.buffer_line_height);
159        }
160
161        Ok(this)
162    }
163
164    fn json_schema(
165        generator: &mut SchemaGenerator,
166        params: &SettingsJsonSchemaParams,
167        cx: &AppContext,
168    ) -> schemars::schema::RootSchema {
169        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
170        let theme_names = cx
171            .global::<Arc<ThemeRegistry>>()
172            .list_names(params.staff_mode)
173            .map(|theme_name| Value::String(theme_name.to_string()))
174            .collect();
175
176        let theme_name_schema = SchemaObject {
177            instance_type: Some(InstanceType::String.into()),
178            enum_values: Some(theme_names),
179            ..Default::default()
180        };
181
182        root_schema
183            .definitions
184            .extend([("ThemeName".into(), theme_name_schema.into())]);
185
186        root_schema
187            .schema
188            .object
189            .as_mut()
190            .unwrap()
191            .properties
192            .extend([(
193                "theme".to_owned(),
194                Schema::new_ref("#/definitions/ThemeName".into()),
195            )]);
196
197        root_schema
198    }
199}
200
201fn merge<T: Copy>(target: &mut T, value: Option<T>) {
202    if let Some(value) = value {
203        *target = value;
204    }
205}