From 963cb2c20098df8c88e5c2f2ba5544028e8f8190 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 1a30eecaa12e1e4a2a9799b2ec752bae2998a257..48cb5bfb644f2704c84f80c4936295358333e35d 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 94045b75a1112af64ed56de318d4e27c392a230e..41bd433f28dc6d85955c56d1379aa6fb210db512 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 64343b05fd57c33eb9cfb0d8cb8674971266b464..8f32b7baf119be88717876e3d103b5033b141e86 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 a735553f78d487ad4def08a98886f422b113dba4..1196c45e3280e6c9e0e909f1880017cc1a2eea32 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 3d70d85f35239778bee61113ebc51eea7d87adcb..a71800e0935b66ce1d4fad2fb3ff91031258d248 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 b72ce39d4c619600ddc104223087a759081449cf..bb5f6e432474b8353c5555a94f7014b52c518b27 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);