use crate::{Theme, ThemeRegistry};use anyhow::Result;use gpui::{font_cache::FamilyId, fonts, AppContext};use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, JsonSchema,};use serde::{Deserialize, Serialize};use serde_json::Value;use settings::SettingsJsonSchemaParams;use std::sync::Arc;use util::ResultExt as _; const MIN_FONT_SIZE: f32 = 6.0;const MIN_LINE_HEIGHT: f32 = 1.0; #[derive(Clone, JsonSchema)]pub struct ThemeSettings { pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub(crate) buffer_font_size: f32, pub(crate) buffer_line_height: BufferLineHeight, #[serde(skip)] pub theme: Arc<Theme>,} pub struct AdjustedBufferFontSize(pub f32); #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]pub struct ThemeSettingsContent { #[serde(default)] pub buffer_font_family: Option<String>, #[serde(default)] pub buffer_font_size: Option<f32>, #[serde(default)] pub buffer_line_height: Option<BufferLineHeight>, #[serde(default)] pub buffer_font_features: Option<fonts::Features>, #[serde(default)] pub theme: Option<String>,} #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]#[serde(rename_all = "snake_case")]pub enum BufferLineHeight { #[default] Comfortable, Standard, Custom(f32),} impl BufferLineHeight { pub fn value(&self) -> f32 { match self { BufferLineHeight::Comfortable => 1.618, BufferLineHeight::Standard => 1.3, BufferLineHeight::Custom(line_height) => *line_height, } }} impl ThemeSettings { pub fn buffer_font_size(&self, cx: &AppContext) -> f32 { if cx.has_global::<AdjustedBufferFontSize>() { cx.global::<AdjustedBufferFontSize>().0 } else { self.buffer_font_size } .max(MIN_FONT_SIZE) } pub fn line_height(&self) -> f32 { f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT) }} pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 { if cx.has_global::<AdjustedBufferFontSize>() { let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size; let delta = cx.global::<AdjustedBufferFontSize>().0 - buffer_font_size; size + delta } else { size } .max(MIN_FONT_SIZE)} pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) { if !cx.has_global::<AdjustedBufferFontSize>() { let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size; cx.set_global(AdjustedBufferFontSize(buffer_font_size)); } cx.update_global::<AdjustedBufferFontSize, _, _>(|delta, cx| { f(&mut delta.0); delta.0 = delta .0 .max(MIN_FONT_SIZE - settings::get::<ThemeSettings>(cx).buffer_font_size); }); cx.refresh_windows();} pub fn reset_font_size(cx: &mut AppContext) { if cx.has_global::<AdjustedBufferFontSize>() { cx.remove_global::<AdjustedBufferFontSize>(); cx.refresh_windows(); }} impl settings::Setting for ThemeSettings { const KEY: Option<&'static str> = None; type FileContent = ThemeSettingsContent; fn load( defaults: &Self::FileContent, user_values: &[&Self::FileContent], cx: &AppContext, ) -> Result<Self> { let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); let themes = cx.global::<Arc<ThemeRegistry>>(); let mut this = Self { buffer_font_family: cx .font_cache() .load_family( &[defaults.buffer_font_family.as_ref().unwrap()], &buffer_font_features, ) .unwrap(), buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), buffer_line_height: defaults.buffer_line_height.unwrap(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; for value in user_values.into_iter().copied().cloned() { let font_cache = cx.font_cache(); let mut family_changed = false; if let Some(value) = value.buffer_font_family { this.buffer_font_family_name = value; family_changed = true; } if let Some(value) = value.buffer_font_features { this.buffer_font_features = value; family_changed = true; } if family_changed { if let Some(id) = font_cache .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features) .log_err() { this.buffer_font_family = id; } } if let Some(value) = &value.theme { if let Some(theme) = themes.get(value).log_err() { this.theme = theme; } } merge(&mut this.buffer_font_size, value.buffer_font_size); merge(&mut this.buffer_line_height, value.buffer_line_height); } Ok(this) } fn json_schema( generator: &mut SchemaGenerator, params: &SettingsJsonSchemaParams, cx: &AppContext, ) -> schemars::schema::RootSchema { let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>(); let theme_names = cx .global::<Arc<ThemeRegistry>>() .list_names(params.staff_mode) .map(|theme_name| Value::String(theme_name.to_string())) .collect(); let theme_name_schema = SchemaObject { instance_type: Some(InstanceType::String.into()), enum_values: Some(theme_names), ..Default::default() }; root_schema .definitions .extend([("ThemeName".into(), theme_name_schema.into())]); root_schema .schema .object .as_mut() .unwrap() .properties .extend([( "theme".to_owned(), Schema::new_ref("#/definitions/ThemeName".into()), )]); root_schema }} fn merge<T: Copy>(target: &mut T, value: Option<T>) { if let Some(value) = value { *target = value; }}
use crate::{Theme, ThemeRegistry};
use anyhow::Result;
use gpui::{font_cache::FamilyId, fonts, AppContext};
use schemars::{
gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject},
JsonSchema,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use settings::SettingsJsonSchemaParams;
use std::sync::Arc;
use util::ResultExt as _;
const MIN_FONT_SIZE: f32 = 6.0;
const MIN_LINE_HEIGHT: f32 = 1.0;
#[derive(Clone, JsonSchema)]
pub struct ThemeSettings {
pub buffer_font_family_name: String,
pub buffer_font_features: fonts::Features,
pub buffer_font_family: FamilyId,
pub(crate) buffer_font_size: f32,
pub(crate) buffer_line_height: BufferLineHeight,
#[serde(skip)]
pub theme: Arc<Theme>,
}
pub struct AdjustedBufferFontSize(pub f32);
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct ThemeSettingsContent {
#[serde(default)]
pub buffer_font_family: Option<String>,
#[serde(default)]
pub buffer_font_size: Option<f32>,
#[serde(default)]
pub buffer_line_height: Option<BufferLineHeight>,
#[serde(default)]
pub buffer_font_features: Option<fonts::Features>,
#[serde(default)]
pub theme: Option<String>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum BufferLineHeight {
#[default]
Comfortable,
Standard,
Custom(f32),
}
impl BufferLineHeight {
pub fn value(&self) -> f32 {
match self {
BufferLineHeight::Comfortable => 1.618,
BufferLineHeight::Standard => 1.3,
BufferLineHeight::Custom(line_height) => *line_height,
}
}
}
impl ThemeSettings {
pub fn buffer_font_size(&self, cx: &AppContext) -> f32 {
if cx.has_global::<AdjustedBufferFontSize>() {
cx.global::<AdjustedBufferFontSize>().0
} else {
self.buffer_font_size
}
.max(MIN_FONT_SIZE)
}
pub fn line_height(&self) -> f32 {
f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
}
}
pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 {
if cx.has_global::<AdjustedBufferFontSize>() {
let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
let delta = cx.global::<AdjustedBufferFontSize>().0 - buffer_font_size;
size + delta
} else {
size
}
.max(MIN_FONT_SIZE)
}
pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) {
if !cx.has_global::<AdjustedBufferFontSize>() {
let buffer_font_size = settings::get::<ThemeSettings>(cx).buffer_font_size;
cx.set_global(AdjustedBufferFontSize(buffer_font_size));
}
cx.update_global::<AdjustedBufferFontSize, _, _>(|delta, cx| {
f(&mut delta.0);
delta.0 = delta
.0
.max(MIN_FONT_SIZE - settings::get::<ThemeSettings>(cx).buffer_font_size);
});
cx.refresh_windows();
}
pub fn reset_font_size(cx: &mut AppContext) {
if cx.has_global::<AdjustedBufferFontSize>() {
cx.remove_global::<AdjustedBufferFontSize>();
cx.refresh_windows();
}
}
impl settings::Setting for ThemeSettings {
const KEY: Option<&'static str> = None;
type FileContent = ThemeSettingsContent;
fn load(
defaults: &Self::FileContent,
user_values: &[&Self::FileContent],
cx: &AppContext,
) -> Result<Self> {
let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
let themes = cx.global::<Arc<ThemeRegistry>>();
let mut this = Self {
buffer_font_family: cx
.font_cache()
.load_family(
&[defaults.buffer_font_family.as_ref().unwrap()],
&buffer_font_features,
)
.unwrap(),
buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
buffer_font_features,
buffer_font_size: defaults.buffer_font_size.unwrap(),
buffer_line_height: defaults.buffer_line_height.unwrap(),
theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
};
for value in user_values.into_iter().copied().cloned() {
let font_cache = cx.font_cache();
let mut family_changed = false;
if let Some(value) = value.buffer_font_family {
this.buffer_font_family_name = value;
family_changed = true;
}
if let Some(value) = value.buffer_font_features {
this.buffer_font_features = value;
family_changed = true;
}
if family_changed {
if let Some(id) = font_cache
.load_family(&[&this.buffer_font_family_name], &this.buffer_font_features)
.log_err()
{
this.buffer_font_family = id;
}
}
if let Some(value) = &value.theme {
if let Some(theme) = themes.get(value).log_err() {
this.theme = theme;
}
}
merge(&mut this.buffer_font_size, value.buffer_font_size);
merge(&mut this.buffer_line_height, value.buffer_line_height);
}
Ok(this)
}
fn json_schema(
generator: &mut SchemaGenerator,
params: &SettingsJsonSchemaParams,
cx: &AppContext,
) -> schemars::schema::RootSchema {
let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
let theme_names = cx
.global::<Arc<ThemeRegistry>>()
.list_names(params.staff_mode)
.map(|theme_name| Value::String(theme_name.to_string()))
.collect();
let theme_name_schema = SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(theme_names),
..Default::default()
};
root_schema
.definitions
.extend([("ThemeName".into(), theme_name_schema.into())]);
root_schema
.schema
.object
.as_mut()
.unwrap()
.properties
.extend([(
"theme".to_owned(),
Schema::new_ref("#/definitions/ThemeName".into()),
)]);
root_schema
}
}
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}
}