theme_settings.rs

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