From 3dbf701557e9bf100ba71ed48498fca0821648bb Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:51:58 -0300 Subject: [PATCH] settings_ui: Make font size settings use editable number fields (#45875) Now that the edit mode in number fields is finally working well, we can make the UX of editing font sizes much nicer because you can now type inside the number field :) https://github.com/user-attachments/assets/8df7c6ee-e82b-4e10-a175-e0ca5f1bab1f Release Notes: - settings UI: Improved the UX of editing font size fields as you can now type the desired value as opposed to just using the decrement/increment buttons. --- .../settings/src/settings_content/terminal.rs | 5 +- crates/settings/src/settings_content/theme.rs | 54 +++++++++++++++---- crates/settings/src/vscode_import.rs | 6 ++- crates/settings_ui/src/settings_ui.rs | 42 ++++++++++++++- crates/terminal/src/terminal_settings.rs | 2 +- crates/ui_input/src/number_field.rs | 5 +- 6 files changed, 96 insertions(+), 18 deletions(-) diff --git a/crates/settings/src/settings_content/terminal.rs b/crates/settings/src/settings_content/terminal.rs index 1a30eecaa1..48cb5bfb64 100644 --- a/crates/settings/src/settings_content/terminal.rs +++ b/crates/settings/src/settings_content/terminal.rs @@ -6,7 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings_macros::{MergeFrom, with_fallible_options}; -use crate::FontFamilyName; +use crate::{FontFamilyName, FontSize}; #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ProjectTerminalSettingsContent { @@ -75,8 +75,7 @@ pub struct TerminalSettingsContent { /// /// If this option is not included, /// the terminal will default to matching the buffer's font size. - #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] - pub font_size: Option, + pub font_size: Option, /// Sets the terminal's font family. /// /// If this option is not included, diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index 94045b75a1..41bd433f28 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/crates/settings/src/settings_content/theme.rs @@ -1,5 +1,5 @@ use collections::{HashMap, IndexMap}; -use gpui::{FontFallbacks, FontFeatures, FontStyle, FontWeight, SharedString}; +use gpui::{FontFallbacks, FontFeatures, FontStyle, FontWeight, Pixels, SharedString}; use schemars::{JsonSchema, JsonSchema_repr}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; @@ -15,8 +15,7 @@ use crate::serialize_f32_with_two_decimal_places; #[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ThemeSettingsContent { /// The default font size for text in the UI. - #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] - pub ui_font_size: Option, + pub ui_font_size: Option, /// The name of a font to use for rendering in the UI. pub ui_font_family: Option, /// The font fallbacks to use for rendering in the UI. @@ -35,8 +34,7 @@ pub struct ThemeSettingsContent { #[schemars(extend("uniqueItems" = true))] pub buffer_font_fallbacks: Option>, /// The default font size for rendering in text buffers. - #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] - pub buffer_font_size: Option, + pub buffer_font_size: Option, /// The weight of the editor font in CSS units from 100 to 900. #[schemars(default = "default_buffer_font_weight")] pub buffer_font_weight: Option, @@ -46,11 +44,9 @@ pub struct ThemeSettingsContent { #[schemars(default = "default_font_features")] pub buffer_font_features: Option, /// The font size for agent responses in the agent panel. Falls back to the UI font size if unset. - #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] - pub agent_ui_font_size: Option, + pub agent_ui_font_size: Option, /// The font size for user messages in the agent panel. - #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] - pub agent_buffer_font_size: Option, + pub agent_buffer_font_size: Option, /// The name of the Zed theme to use. pub theme: Option, /// The name of the icon theme to use. @@ -79,6 +75,46 @@ pub struct ThemeSettingsContent { pub theme_overrides: HashMap, } +/// A font size value in pixels, wrapping around `f32` for custom settings UI rendering. +#[derive( + Clone, + Copy, + Debug, + Serialize, + Deserialize, + JsonSchema, + MergeFrom, + PartialEq, + PartialOrd, + derive_more::FromStr, +)] +#[serde(transparent)] +pub struct FontSize(#[serde(serialize_with = "serialize_f32_with_two_decimal_places")] pub f32); + +impl Display for FontSize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:.2}", self.0) + } +} + +impl From for FontSize { + fn from(value: f32) -> Self { + Self(value) + } +} + +impl From for Pixels { + fn from(value: FontSize) -> Self { + value.0.into() + } +} + +impl From for FontSize { + fn from(value: Pixels) -> Self { + Self(value.into()) + } +} + #[derive( Clone, Copy, diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 64343b05fd..8f32b7baf1 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -736,7 +736,9 @@ impl VsCodeSettings { font_fallbacks, font_family, font_features: None, - font_size: self.read_f32("terminal.integrated.fontSize"), + font_size: self + .read_f32("terminal.integrated.fontSize") + .map(FontSize::from), font_weight: None, keep_selection_on_copy: None, line_height: self @@ -795,7 +797,7 @@ impl VsCodeSettings { ui_font_weight: None, buffer_font_family, buffer_font_fallbacks, - buffer_font_size: self.read_f32("editor.fontSize"), + buffer_font_size: self.read_f32("editor.fontSize").map(FontSize::from), buffer_font_weight: self.read_f32("editor.fontWeight").map(|w| w.into()), buffer_line_height: None, buffer_font_features: None, diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index a735553f78..1196c45e32 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -31,7 +31,7 @@ use ui::{ Banner, ContextMenu, Divider, DropdownMenu, DropdownStyle, IconButtonShape, KeyBinding, KeybindingHint, PopoverMenu, Switch, Tooltip, TreeViewItem, WithScrollbar, prelude::*, }; -use ui_input::{NumberField, NumberFieldType}; +use ui_input::{NumberField, NumberFieldMode, NumberFieldType}; use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath}; use workspace::{AppState, OpenOptions, OpenVisible, Workspace, client_side_decorations}; use zed_actions::{OpenProjectSettings, OpenSettings, OpenSettingsAt}; @@ -513,6 +513,7 @@ fn init_renderers(cx: &mut App) { .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_editable_number_field) // please semicolon stay on next line ; } @@ -3667,7 +3668,44 @@ fn render_number_field( ) -> AnyElement { let (_, value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick); let value = value.copied().unwrap_or_else(T::min_value); - NumberField::new("numeric_stepper", value, window, cx) + + let id = field + .json_path + .map(|p| format!("numeric_stepper_{}", p)) + .unwrap_or_else(|| "numeric_stepper".to_string()); + + NumberField::new(id, value, window, cx) + .tab_index(0_isize) + .on_change({ + move |value, _window, cx| { + let value = *value; + update_settings_file(file.clone(), field.json_path, cx, move |settings, _cx| { + (field.write)(settings, Some(value)); + }) + .log_err(); // todo(settings_ui) don't log err + } + }) + .into_any_element() +} + +fn render_editable_number_field( + field: SettingField, + file: SettingsUiFile, + _metadata: Option<&SettingsFieldMetadata>, + window: &mut Window, + cx: &mut App, +) -> AnyElement { + let (_, value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick); + let value = value.copied().unwrap_or_else(T::min_value); + + let id = field + .json_path + .map(|p| format!("numeric_stepper_{}", p)) + .unwrap_or_else(|| "numeric_stepper".to_string()); + + NumberField::new(id, value, window, cx) + .mode(NumberFieldMode::Edit, cx) + .tab_index(0_isize) .on_change({ move |value, _window, cx| { let value = *value; diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 3d70d85f35..a71800e093 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -84,7 +84,7 @@ impl settings::Settings for TerminalSettings { TerminalSettings { shell: settings_shell_to_task_shell(project_content.shell.unwrap()), working_directory: project_content.working_directory.unwrap(), - font_size: user_content.font_size.map(px), + font_size: user_content.font_size.map(Into::into), font_family: user_content.font_family, font_fallbacks: user_content.font_fallbacks.map(|fallbacks| { FontFallbacks::from_fonts( diff --git a/crates/ui_input/src/number_field.rs b/crates/ui_input/src/number_field.rs index b72ce39d4c..bb5f6e4324 100644 --- a/crates/ui_input/src/number_field.rs +++ b/crates/ui_input/src/number_field.rs @@ -11,7 +11,9 @@ use gpui::{ TextStyleRefinement, WeakEntity, }; -use settings::{CenteredPaddingSettings, CodeFade, DelayMs, InactiveOpacity, MinimumContrast}; +use settings::{ + CenteredPaddingSettings, CodeFade, DelayMs, FontSize, InactiveOpacity, MinimumContrast, +}; use ui::prelude::*; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -105,6 +107,7 @@ macro_rules! impl_newtype_numeric_stepper_int { #[rustfmt::skip] impl_newtype_numeric_stepper_float!(FontWeight, 50., 100., 10., FontWeight::THIN, FontWeight::BLACK); impl_newtype_numeric_stepper_float!(CodeFade, 0.1, 0.2, 0.05, 0.0, 0.9); +impl_newtype_numeric_stepper_float!(FontSize, 1.0, 4.0, 0.5, 6.0, 72.0); impl_newtype_numeric_stepper_float!(InactiveOpacity, 0.1, 0.2, 0.05, 0.0, 1.0); impl_newtype_numeric_stepper_float!(MinimumContrast, 1., 10., 0.5, 0.0, 106.0); impl_newtype_numeric_stepper_int!(DelayMs, 100, 500, 10, 0, 2000);