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.
This commit is contained in:
Danilo Leal
2025-12-30 14:51:58 -03:00
committed by GitHub
parent 0c7d639cbd
commit 963cb2c200
6 changed files with 96 additions and 18 deletions

View File

@@ -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<f32>,
pub font_size: Option<FontSize>,
/// Sets the terminal's font family.
///
/// If this option is not included,

View File

@@ -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<f32>,
pub ui_font_size: Option<FontSize>,
/// The name of a font to use for rendering in the UI.
pub ui_font_family: Option<FontFamilyName>,
/// 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<Vec<FontFamilyName>>,
/// 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<f32>,
pub buffer_font_size: Option<FontSize>,
/// The weight of the editor font in CSS units from 100 to 900.
#[schemars(default = "default_buffer_font_weight")]
pub buffer_font_weight: Option<FontWeight>,
@@ -46,11 +44,9 @@ pub struct ThemeSettingsContent {
#[schemars(default = "default_font_features")]
pub buffer_font_features: Option<FontFeatures>,
/// 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<f32>,
pub agent_ui_font_size: Option<FontSize>,
/// 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<f32>,
pub agent_buffer_font_size: Option<FontSize>,
/// The name of the Zed theme to use.
pub theme: Option<ThemeSelection>,
/// The name of the icon theme to use.
@@ -79,6 +75,46 @@ pub struct ThemeSettingsContent {
pub theme_overrides: HashMap<String, ThemeStyleContent>,
}
/// 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<f32> for FontSize {
fn from(value: f32) -> Self {
Self(value)
}
}
impl From<FontSize> for Pixels {
fn from(value: FontSize) -> Self {
value.0.into()
}
}
impl From<Pixels> for FontSize {
fn from(value: Pixels) -> Self {
Self(value.into())
}
}
#[derive(
Clone,
Copy,

View File

@@ -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,

View File

@@ -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::<settings::EditPredictionsMode>(render_dropdown)
.add_basic_renderer::<settings::RelativeLineNumbers>(render_dropdown)
.add_basic_renderer::<settings::WindowDecorations>(render_dropdown)
.add_basic_renderer::<settings::FontSize>(render_editable_number_field)
// please semicolon stay on next line
;
}
@@ -3667,7 +3668,44 @@ fn render_number_field<T: NumberFieldType + Send + Sync>(
) -> 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<T: NumberFieldType + Send + Sync>(
field: SettingField<T>,
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;

View File

@@ -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(

View File

@@ -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);