Appologies if this PR is off base, I'm still not super familiar with the Zed codebase. I was trying to integrate with https://github.com/zed-industries/zed/pull/12940 and found it awkward to hook up global bindings to adjust the UI font size due to the fact it takes a WindowContext. Looking at the API, it seemed odd that it took a WindowContext, yet the editor font methods take an AppContext. I couldn't find a compelling reason for this to be tied to a WindowContext, so I personally think it makes sense to switch it. This does have a behavior change, which hopefully is actually desirable: Currently, if you have two open and visible Zed windows, and trigger a UI font adjustment in one, the non-active windows won't update. However, once you switch focus to the second one it will snap to the new UI font size. This is inconsistent with adjusting the editor font size, which applies to all open windows immediately. Release Notes: - N/A
673 lines
22 KiB
Rust
673 lines
22 KiB
Rust
use crate::one_themes::one_dark;
|
|
use crate::{Appearance, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent};
|
|
use anyhow::Result;
|
|
use derive_more::{Deref, DerefMut};
|
|
use gpui::{
|
|
px, AppContext, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
|
|
Subscription, ViewContext, WindowContext,
|
|
};
|
|
use refineable::Refineable;
|
|
use schemars::{
|
|
gen::SchemaGenerator,
|
|
schema::{InstanceType, Schema, SchemaObject},
|
|
JsonSchema,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use settings::{add_references_to_properties, Settings, SettingsJsonSchemaParams, SettingsSources};
|
|
use std::sync::Arc;
|
|
use util::ResultExt as _;
|
|
|
|
const MIN_FONT_SIZE: Pixels = px(6.0);
|
|
const MIN_LINE_HEIGHT: f32 = 1.0;
|
|
|
|
#[derive(
|
|
Debug,
|
|
Default,
|
|
PartialEq,
|
|
Eq,
|
|
PartialOrd,
|
|
Ord,
|
|
Hash,
|
|
Clone,
|
|
Copy,
|
|
Serialize,
|
|
Deserialize,
|
|
JsonSchema,
|
|
)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum UiDensity {
|
|
/// A denser UI with tighter spacing and smaller elements.
|
|
#[serde(alias = "compact")]
|
|
Compact,
|
|
#[default]
|
|
#[serde(alias = "default")]
|
|
/// The default UI density.
|
|
Default,
|
|
#[serde(alias = "comfortable")]
|
|
/// A looser UI with more spacing and larger elements.
|
|
Comfortable,
|
|
}
|
|
|
|
impl UiDensity {
|
|
pub fn spacing_ratio(self) -> f32 {
|
|
match self {
|
|
UiDensity::Compact => 0.75,
|
|
UiDensity::Default => 1.0,
|
|
UiDensity::Comfortable => 1.25,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<String> for UiDensity {
|
|
fn from(s: String) -> Self {
|
|
match s.as_str() {
|
|
"compact" => Self::Compact,
|
|
"default" => Self::Default,
|
|
"comfortable" => Self::Comfortable,
|
|
_ => Self::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<UiDensity> for String {
|
|
fn from(val: UiDensity) -> Self {
|
|
match val {
|
|
UiDensity::Compact => "compact".to_string(),
|
|
UiDensity::Default => "default".to_string(),
|
|
UiDensity::Comfortable => "comfortable".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct ThemeSettings {
|
|
pub ui_font_size: Pixels,
|
|
pub ui_font: Font,
|
|
pub buffer_font: Font,
|
|
pub buffer_font_size: Pixels,
|
|
pub buffer_line_height: BufferLineHeight,
|
|
pub theme_selection: Option<ThemeSelection>,
|
|
pub active_theme: Arc<Theme>,
|
|
pub theme_overrides: Option<ThemeStyleContent>,
|
|
pub ui_density: UiDensity,
|
|
pub unnecessary_code_fade: f32,
|
|
}
|
|
|
|
impl ThemeSettings {
|
|
const DEFAULT_LIGHT_THEME: &'static str = "One Light";
|
|
const DEFAULT_DARK_THEME: &'static str = "One Dark";
|
|
|
|
/// Returns the name of the default theme for the given [`Appearance`].
|
|
pub fn default_theme(appearance: Appearance) -> &'static str {
|
|
match appearance {
|
|
Appearance::Light => Self::DEFAULT_LIGHT_THEME,
|
|
Appearance::Dark => Self::DEFAULT_DARK_THEME,
|
|
}
|
|
}
|
|
|
|
/// Reloads the current theme.
|
|
///
|
|
/// Reads the [`ThemeSettings`] to know which theme should be loaded,
|
|
/// taking into account the current [`SystemAppearance`].
|
|
pub fn reload_current_theme(cx: &mut AppContext) {
|
|
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
|
let system_appearance = SystemAppearance::global(cx);
|
|
|
|
if let Some(theme_selection) = theme_settings.theme_selection.clone() {
|
|
let mut theme_name = theme_selection.theme(*system_appearance);
|
|
|
|
// If the selected theme doesn't exist, fall back to a default theme
|
|
// based on the system appearance.
|
|
let theme_registry = ThemeRegistry::global(cx);
|
|
if theme_registry.get(theme_name).ok().is_none() {
|
|
theme_name = Self::default_theme(*system_appearance);
|
|
};
|
|
|
|
if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
|
|
ThemeSettings::override_global(theme_settings, cx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The appearance of the system.
|
|
#[derive(Debug, Clone, Copy, Deref)]
|
|
pub struct SystemAppearance(pub Appearance);
|
|
|
|
impl Default for SystemAppearance {
|
|
fn default() -> Self {
|
|
Self(Appearance::Dark)
|
|
}
|
|
}
|
|
|
|
#[derive(Deref, DerefMut, Default)]
|
|
struct GlobalSystemAppearance(SystemAppearance);
|
|
|
|
impl Global for GlobalSystemAppearance {}
|
|
|
|
impl SystemAppearance {
|
|
/// Initializes the [`SystemAppearance`] for the application.
|
|
pub fn init(cx: &mut AppContext) {
|
|
*cx.default_global::<GlobalSystemAppearance>() =
|
|
GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
|
|
}
|
|
|
|
/// Returns the global [`SystemAppearance`].
|
|
///
|
|
/// Inserts a default [`SystemAppearance`] if one does not yet exist.
|
|
pub(crate) fn default_global(cx: &mut AppContext) -> Self {
|
|
cx.default_global::<GlobalSystemAppearance>().0
|
|
}
|
|
|
|
/// Returns the global [`SystemAppearance`].
|
|
pub fn global(cx: &AppContext) -> Self {
|
|
cx.global::<GlobalSystemAppearance>().0
|
|
}
|
|
|
|
/// Returns a mutable reference to the global [`SystemAppearance`].
|
|
pub fn global_mut(cx: &mut AppContext) -> &mut Self {
|
|
cx.global_mut::<GlobalSystemAppearance>()
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct AdjustedBufferFontSize(Pixels);
|
|
|
|
impl Global for AdjustedBufferFontSize {}
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct AdjustedUiFontSize(Pixels);
|
|
|
|
impl Global for AdjustedUiFontSize {}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
|
#[serde(untagged)]
|
|
pub enum ThemeSelection {
|
|
Static(#[schemars(schema_with = "theme_name_ref")] String),
|
|
Dynamic {
|
|
#[serde(default)]
|
|
mode: ThemeMode,
|
|
#[schemars(schema_with = "theme_name_ref")]
|
|
light: String,
|
|
#[schemars(schema_with = "theme_name_ref")]
|
|
dark: String,
|
|
},
|
|
}
|
|
|
|
fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
|
|
Schema::new_ref("#/definitions/ThemeName".into())
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ThemeMode {
|
|
/// Use the specified `light` theme.
|
|
Light,
|
|
|
|
/// Use the specified `dark` theme.
|
|
Dark,
|
|
|
|
/// Use the theme based on the system's appearance.
|
|
#[default]
|
|
System,
|
|
}
|
|
|
|
impl ThemeSelection {
|
|
pub fn theme(&self, system_appearance: Appearance) -> &str {
|
|
match self {
|
|
Self::Static(theme) => theme,
|
|
Self::Dynamic { mode, light, dark } => match mode {
|
|
ThemeMode::Light => light,
|
|
ThemeMode::Dark => dark,
|
|
ThemeMode::System => match system_appearance {
|
|
Appearance::Light => light,
|
|
Appearance::Dark => dark,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn mode(&self) -> Option<ThemeMode> {
|
|
match self {
|
|
ThemeSelection::Static(_) => None,
|
|
ThemeSelection::Dynamic { mode, .. } => Some(*mode),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Settings for rendering text in UI and text buffers.
|
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
|
pub struct ThemeSettingsContent {
|
|
/// The default font size for text in the UI.
|
|
#[serde(default)]
|
|
pub ui_font_size: Option<f32>,
|
|
/// The name of a font to use for rendering in the UI.
|
|
#[serde(default)]
|
|
pub ui_font_family: Option<String>,
|
|
/// The font fallbacks to use for rendering in the UI.
|
|
#[serde(default)]
|
|
#[schemars(default = "default_font_fallbacks")]
|
|
pub ui_font_fallbacks: Option<Vec<String>>,
|
|
/// The OpenType features to enable for text in the UI.
|
|
#[serde(default)]
|
|
#[schemars(default = "default_font_features")]
|
|
pub ui_font_features: Option<FontFeatures>,
|
|
/// The weight of the UI font in CSS units from 100 to 900.
|
|
#[serde(default)]
|
|
pub ui_font_weight: Option<f32>,
|
|
/// The name of a font to use for rendering in text buffers.
|
|
#[serde(default)]
|
|
pub buffer_font_family: Option<String>,
|
|
/// The font fallbacks to use for rendering in text buffers.
|
|
#[serde(default)]
|
|
#[schemars(default = "default_font_fallbacks")]
|
|
pub buffer_font_fallbacks: Option<Vec<String>>,
|
|
/// The default font size for rendering in text buffers.
|
|
#[serde(default)]
|
|
pub buffer_font_size: Option<f32>,
|
|
/// The weight of the editor font in CSS units from 100 to 900.
|
|
#[serde(default)]
|
|
pub buffer_font_weight: Option<f32>,
|
|
/// The buffer's line height.
|
|
#[serde(default)]
|
|
pub buffer_line_height: Option<BufferLineHeight>,
|
|
/// The OpenType features to enable for rendering in text buffers.
|
|
#[serde(default)]
|
|
#[schemars(default = "default_font_features")]
|
|
pub buffer_font_features: Option<FontFeatures>,
|
|
/// The name of the Zed theme to use.
|
|
#[serde(default)]
|
|
pub theme: Option<ThemeSelection>,
|
|
|
|
/// UNSTABLE: Expect many elements to be broken.
|
|
///
|
|
// Controls the density of the UI.
|
|
#[serde(rename = "unstable.ui_density", default)]
|
|
pub ui_density: Option<UiDensity>,
|
|
|
|
/// How much to fade out unused code.
|
|
#[serde(default)]
|
|
pub unnecessary_code_fade: Option<f32>,
|
|
|
|
/// EXPERIMENTAL: Overrides for the current theme.
|
|
///
|
|
/// These values will override the ones on the current theme specified in `theme`.
|
|
#[serde(rename = "experimental.theme_overrides", default)]
|
|
pub theme_overrides: Option<ThemeStyleContent>,
|
|
}
|
|
|
|
fn default_font_features() -> Option<FontFeatures> {
|
|
Some(FontFeatures::default())
|
|
}
|
|
|
|
fn default_font_fallbacks() -> Option<FontFallbacks> {
|
|
Some(FontFallbacks::default())
|
|
}
|
|
|
|
impl ThemeSettingsContent {
|
|
/// Sets the theme for the given appearance to the theme with the specified name.
|
|
pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
|
|
if let Some(selection) = self.theme.as_mut() {
|
|
let theme_to_update = match selection {
|
|
ThemeSelection::Static(theme) => theme,
|
|
ThemeSelection::Dynamic { mode, light, dark } => match mode {
|
|
ThemeMode::Light => light,
|
|
ThemeMode::Dark => dark,
|
|
ThemeMode::System => match appearance {
|
|
Appearance::Light => light,
|
|
Appearance::Dark => dark,
|
|
},
|
|
},
|
|
};
|
|
|
|
*theme_to_update = theme_name.to_string();
|
|
} else {
|
|
self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
|
|
}
|
|
}
|
|
|
|
pub fn set_mode(&mut self, mode: ThemeMode) {
|
|
if let Some(selection) = self.theme.as_mut() {
|
|
match selection {
|
|
ThemeSelection::Static(theme) => {
|
|
// If the theme was previously set to a single static theme,
|
|
// we don't know whether it was a light or dark theme, so we
|
|
// just use it for both.
|
|
self.theme = Some(ThemeSelection::Dynamic {
|
|
mode,
|
|
light: theme.clone(),
|
|
dark: theme.clone(),
|
|
});
|
|
}
|
|
ThemeSelection::Dynamic {
|
|
mode: mode_to_update,
|
|
..
|
|
} => *mode_to_update = mode,
|
|
}
|
|
} else {
|
|
self.theme = Some(ThemeSelection::Dynamic {
|
|
mode,
|
|
light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
|
|
dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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) -> Pixels {
|
|
cx.try_global::<AdjustedBufferFontSize>()
|
|
.map_or(self.buffer_font_size, |size| size.0)
|
|
.max(MIN_FONT_SIZE)
|
|
}
|
|
|
|
pub fn line_height(&self) -> f32 {
|
|
f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
|
|
}
|
|
|
|
/// Switches to the theme with the given name, if it exists.
|
|
///
|
|
/// Returns a `Some` containing the new theme if it was successful.
|
|
/// Returns `None` otherwise.
|
|
pub fn switch_theme(&mut self, theme: &str, cx: &mut AppContext) -> Option<Arc<Theme>> {
|
|
let themes = ThemeRegistry::default_global(cx);
|
|
|
|
let mut new_theme = None;
|
|
|
|
if let Some(theme) = themes.get(theme).log_err() {
|
|
self.active_theme = theme.clone();
|
|
new_theme = Some(theme);
|
|
}
|
|
|
|
self.apply_theme_overrides();
|
|
|
|
new_theme
|
|
}
|
|
|
|
/// Applies the theme overrides, if there are any, to the current theme.
|
|
pub fn apply_theme_overrides(&mut self) {
|
|
if let Some(theme_overrides) = &self.theme_overrides {
|
|
let mut base_theme = (*self.active_theme).clone();
|
|
|
|
if let Some(window_background_appearance) = theme_overrides.window_background_appearance
|
|
{
|
|
base_theme.styles.window_background_appearance =
|
|
window_background_appearance.into();
|
|
}
|
|
|
|
base_theme
|
|
.styles
|
|
.colors
|
|
.refine(&theme_overrides.theme_colors_refinement());
|
|
base_theme
|
|
.styles
|
|
.status
|
|
.refine(&theme_overrides.status_colors_refinement());
|
|
base_theme.styles.player.merge(&theme_overrides.players);
|
|
base_theme.styles.accents.merge(&theme_overrides.accents);
|
|
base_theme.styles.syntax =
|
|
SyntaxTheme::merge(base_theme.styles.syntax, theme_overrides.syntax_overrides());
|
|
|
|
self.active_theme = Arc::new(base_theme);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn observe_buffer_font_size_adjustment<V: 'static>(
|
|
cx: &mut ViewContext<V>,
|
|
f: impl 'static + Fn(&mut V, &mut ViewContext<V>),
|
|
) -> Subscription {
|
|
cx.observe_global::<AdjustedBufferFontSize>(f)
|
|
}
|
|
|
|
pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
|
|
if let Some(AdjustedBufferFontSize(adjusted_size)) = cx.try_global::<AdjustedBufferFontSize>() {
|
|
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
|
|
let delta = *adjusted_size - buffer_font_size;
|
|
size + delta
|
|
} else {
|
|
size
|
|
}
|
|
.max(MIN_FONT_SIZE)
|
|
}
|
|
|
|
pub fn get_buffer_font_size(cx: &AppContext) -> Pixels {
|
|
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
|
|
cx.try_global::<AdjustedBufferFontSize>()
|
|
.map_or(buffer_font_size, |adjusted_size| adjusted_size.0)
|
|
}
|
|
|
|
pub fn adjust_buffer_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
|
|
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
|
|
let mut adjusted_size = cx
|
|
.try_global::<AdjustedBufferFontSize>()
|
|
.map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
|
|
|
|
f(&mut adjusted_size);
|
|
adjusted_size = adjusted_size.max(MIN_FONT_SIZE);
|
|
cx.set_global(AdjustedBufferFontSize(adjusted_size));
|
|
cx.refresh();
|
|
}
|
|
|
|
pub fn has_adjusted_buffer_font_size(cx: &mut AppContext) -> bool {
|
|
cx.has_global::<AdjustedBufferFontSize>()
|
|
}
|
|
|
|
pub fn reset_buffer_font_size(cx: &mut AppContext) {
|
|
if cx.has_global::<AdjustedBufferFontSize>() {
|
|
cx.remove_global::<AdjustedBufferFontSize>();
|
|
cx.refresh();
|
|
}
|
|
}
|
|
|
|
pub fn setup_ui_font(cx: &mut WindowContext) -> gpui::Font {
|
|
let (ui_font, ui_font_size) = {
|
|
let theme_settings = ThemeSettings::get_global(cx);
|
|
let font = theme_settings.ui_font.clone();
|
|
(font, get_ui_font_size(cx))
|
|
};
|
|
|
|
cx.set_rem_size(ui_font_size);
|
|
ui_font
|
|
}
|
|
|
|
pub fn get_ui_font_size(cx: &AppContext) -> Pixels {
|
|
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
|
cx.try_global::<AdjustedUiFontSize>()
|
|
.map_or(ui_font_size, |adjusted_size| adjusted_size.0)
|
|
}
|
|
|
|
pub fn adjust_ui_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
|
|
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
|
let mut adjusted_size = cx
|
|
.try_global::<AdjustedUiFontSize>()
|
|
.map_or(ui_font_size, |adjusted_size| adjusted_size.0);
|
|
|
|
f(&mut adjusted_size);
|
|
adjusted_size = adjusted_size.max(MIN_FONT_SIZE);
|
|
cx.set_global(AdjustedUiFontSize(adjusted_size));
|
|
cx.refresh();
|
|
}
|
|
|
|
pub fn has_adjusted_ui_font_size(cx: &mut AppContext) -> bool {
|
|
cx.has_global::<AdjustedUiFontSize>()
|
|
}
|
|
|
|
pub fn reset_ui_font_size(cx: &mut AppContext) {
|
|
if cx.has_global::<AdjustedUiFontSize>() {
|
|
cx.remove_global::<AdjustedUiFontSize>();
|
|
cx.refresh();
|
|
}
|
|
}
|
|
|
|
impl settings::Settings for ThemeSettings {
|
|
const KEY: Option<&'static str> = None;
|
|
|
|
type FileContent = ThemeSettingsContent;
|
|
|
|
fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self> {
|
|
let themes = ThemeRegistry::default_global(cx);
|
|
let system_appearance = SystemAppearance::default_global(cx);
|
|
|
|
let defaults = sources.default;
|
|
let mut this = Self {
|
|
ui_font_size: defaults.ui_font_size.unwrap().into(),
|
|
ui_font: Font {
|
|
family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
|
|
features: defaults.ui_font_features.clone().unwrap(),
|
|
fallbacks: defaults
|
|
.ui_font_fallbacks
|
|
.as_ref()
|
|
.map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
|
|
weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
|
|
style: Default::default(),
|
|
},
|
|
buffer_font: Font {
|
|
family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
|
|
features: defaults.buffer_font_features.clone().unwrap(),
|
|
fallbacks: defaults
|
|
.buffer_font_fallbacks
|
|
.as_ref()
|
|
.map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
|
|
weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
|
|
style: FontStyle::default(),
|
|
},
|
|
buffer_font_size: defaults.buffer_font_size.unwrap().into(),
|
|
buffer_line_height: defaults.buffer_line_height.unwrap(),
|
|
theme_selection: defaults.theme.clone(),
|
|
active_theme: themes
|
|
.get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
|
|
.or(themes.get(&one_dark().name))
|
|
.unwrap(),
|
|
theme_overrides: None,
|
|
ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
|
|
unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
|
|
};
|
|
|
|
for value in sources.user.into_iter().chain(sources.release_channel) {
|
|
if let Some(value) = value.ui_density {
|
|
this.ui_density = value;
|
|
}
|
|
|
|
if let Some(value) = value.buffer_font_family.clone() {
|
|
this.buffer_font.family = value.into();
|
|
}
|
|
if let Some(value) = value.buffer_font_features.clone() {
|
|
this.buffer_font.features = value;
|
|
}
|
|
if let Some(value) = value.buffer_font_fallbacks.clone() {
|
|
this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
|
|
}
|
|
if let Some(value) = value.buffer_font_weight {
|
|
this.buffer_font.weight = FontWeight(value);
|
|
}
|
|
|
|
if let Some(value) = value.ui_font_family.clone() {
|
|
this.ui_font.family = value.into();
|
|
}
|
|
if let Some(value) = value.ui_font_features.clone() {
|
|
this.ui_font.features = value;
|
|
}
|
|
if let Some(value) = value.ui_font_fallbacks.clone() {
|
|
this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
|
|
}
|
|
if let Some(value) = value.ui_font_weight {
|
|
this.ui_font.weight = FontWeight(value);
|
|
}
|
|
|
|
if let Some(value) = &value.theme {
|
|
this.theme_selection = Some(value.clone());
|
|
|
|
let theme_name = value.theme(*system_appearance);
|
|
|
|
if let Some(theme) = themes.get(theme_name).log_err() {
|
|
this.active_theme = theme;
|
|
}
|
|
}
|
|
|
|
this.theme_overrides.clone_from(&value.theme_overrides);
|
|
this.apply_theme_overrides();
|
|
|
|
merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
|
|
this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
|
|
|
|
merge(
|
|
&mut this.buffer_font_size,
|
|
value.buffer_font_size.map(Into::into),
|
|
);
|
|
this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
|
|
|
|
merge(&mut this.buffer_line_height, value.buffer_line_height);
|
|
|
|
// Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
|
|
merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
|
|
this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
|
|
}
|
|
|
|
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 = ThemeRegistry::global(cx)
|
|
.list_names(params.staff_mode)
|
|
.into_iter()
|
|
.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()),
|
|
("FontFamilies".into(), params.font_family_schema()),
|
|
("FontFallbacks".into(), params.font_fallback_schema()),
|
|
]);
|
|
|
|
add_references_to_properties(
|
|
&mut root_schema,
|
|
&[
|
|
("buffer_font_family", "#/definitions/FontFamilies"),
|
|
("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
|
|
("ui_font_family", "#/definitions/FontFamilies"),
|
|
("ui_font_fallbacks", "#/definitions/FontFallbacks"),
|
|
],
|
|
);
|
|
|
|
root_schema
|
|
}
|
|
}
|
|
|
|
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
|
|
if let Some(value) = value {
|
|
*target = value;
|
|
}
|
|
}
|