From 0126f1b036f5383dffbd4089b913be17371b2bf2 Mon Sep 17 00:00:00 2001 From: Andreas Johansson Date: Mon, 1 Dec 2025 10:05:34 +0100 Subject: [PATCH] input: Fix wrong font being used in Input. (#1706) Closes #1665 ## Description When `Input::new().font_family("font")` is used, the font hasn't been applied yet in `Input::render`, therefore the `text_wrapper` will calculate using the wrong font. This is why it's working when the font is added to the parent element. The fix moves the state update to `element` prepaint, where `window.text_style()` has the correct styles. The only downside is that there are now one additional state update. ## Screenshot | Before | After | | ---------------------------- | --------------------------- | | Screenshot From 2025-11-30
20-03-11 | Screenshot From 2025-11-30
20-00-23 | ## How to Test Use some wide font, like `IBM Plex Mono`. Apply it to `Input::new(&some_state).font_family("IBM Plex Mono")` where the input state has `soft_wrap`. Here is a diff for `textarea_story.rs`. ```diff diff --git a/crates/story/src/textarea_story.rs b/crates/story/src/textarea_story.rs index f2c63d44..bd5cbb31 100644 --- a/crates/story/src/textarea_story.rs +++ b/crates/story/src/textarea_story.rs @@ -77,9 +77,9 @@ impl TextareaStory { let textarea_no_wrap = cx.new(|cx| { InputState::new(window, cx) - .multi_line(true) .rows(6) - .soft_wrap(false) + .multi_line(true) + .soft_wrap(true) .default_value("This is a very long line of text to test if the horizontal scrolling function is working properly, and it should not wrap automatically but display a horizontal scrollbar.\nThe second line is also very long text, used to test the horizontal scrolling effect under multiple lines, and you can input more content to test.\nThe third line: Here you can input other long text content that requires horizontal scrolling.\n") }); @@ -195,7 +185,7 @@ impl Render for TextareaStory { .child( section("Auto Grow") .max_w_md() - .child(Input::new(&self.textarea_auto_grow)), + .child(Input::new(&self.textarea_auto_grow).font_family("IBM Plex Mono")), ) .child( section("Auto Grow with No Wrap") ``` ## Checklist - [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines. - [x] Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate. - [x] Passed `cargo run` for story tests related to the changes. - [ ] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) --------- Co-authored-by: Jason Lee --- crates/story/src/input_story.rs | 8 +++++--- crates/ui/src/input/element.rs | 20 ++++++++++++++------ crates/ui/src/input/input.rs | 14 +++----------- crates/ui/src/input/state.rs | 6 +----- crates/ui/src/input/text_wrapper.rs | 4 ++-- crates/ui/src/styled.rs | 7 +++---- 6 files changed, 28 insertions(+), 31 deletions(-) diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index a645fba4..6e48cbf2 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -85,8 +85,9 @@ impl InputStory { fraction: Some(3), }) }); - let custom_input = - cx.new(|cx| InputState::new(window, cx).placeholder("here is a custom input")); + let custom_input = cx.new(|cx| { + InputState::new(window, cx).placeholder("Custom Input use monospace, 0123456789.") + }); let code_input = cx.new(|cx| { InputState::new(window, cx) @@ -247,11 +248,12 @@ impl Render for InputStory { ))), ) .child( - section("Appearance false").max_w_md().child( + section("Custom Appearance").max_w_md().child( div() .border_b_2() .px_6() .py_3() + .font_family(cx.theme().mono_font_family.clone()) .border_color(cx.theme().border) .bg(cx.theme().secondary) .text_color(cx.theme().secondary_foreground) diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 05c3b18f..9f57f020 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -818,6 +818,15 @@ impl Element for TextElement { window: &mut Window, cx: &mut App, ) -> Self::PrepaintState { + let style = window.text_style(); + let font = style.font(); + let text_size = style.font_size.to_pixels(window.rem_size()); + + self.state.update(cx, |state, cx| { + state.text_wrapper.set_font(font, text_size, cx); + state.text_wrapper.prepare_if_need(&state.text, cx); + }); + let state = self.state.read(cx); let line_height = window.line_height(); @@ -840,8 +849,7 @@ impl Element for TextElement { let text = state.text.clone(); let is_empty = text.len() == 0; let placeholder = self.placeholder.clone(); - let style = window.text_style(); - let font_size = style.font_size.to_pixels(window.rem_size()); + let mut bounds = bounds; let (display_text, text_color) = if is_empty { @@ -862,7 +870,7 @@ impl Element for TextElement { // Calculate the width of the line numbers let (line_number_width, line_number_len) = - Self::layout_line_numbers(&state, &text, font_size, &text_style, window); + Self::layout_line_numbers(&state, &text, text_size, &text_style, window); let wrap_width = if multi_line && state.soft_wrap { Some(bounds.size.width - line_number_width - RIGHT_MARGIN) @@ -956,7 +964,7 @@ impl Element for TextElement { &state, &display_text, &last_layout, - font_size, + text_size, &runs, &document_colors, window, @@ -972,7 +980,7 @@ impl Element for TextElement { .text_system() .shape_line( longest_line.clone(), - font_size, + text_size, &[TextRun { len: longest_line.len(), font: style.font(), @@ -1084,7 +1092,7 @@ impl Element for TextElement { sub_lines.push( window .text_system() - .shape_line(line_no, font_size, &runs, None), + .shape_line(line_no, text_size, &runs, None), ); for _ in 0..line.wrapped_lines.len().saturating_sub(1) { sub_lines.push(ShapedLine::default()); diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 28d1ae99..bad7a2be 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -2,7 +2,7 @@ use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, InteractiveElement as _, IntoElement, IsZero, MouseButton, ParentElement as _, Rems, RenderOnce, StyleRefinement, - Styled, Window, div, px, relative, rems, + Styled, Window, div, px, relative, }; use crate::button::{Button, ButtonVariants as _}; @@ -241,16 +241,8 @@ impl Styled for Input { impl RenderOnce for Input { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { const LINE_HEIGHT: Rems = Rems(1.25); - let font = window.text_style().font(); - let font_size = match self.size { - Size::Large => rems(1.), - _ => rems(0.875), - } - .to_pixels(window.rem_size()); - self.state.update(cx, |state, cx| { - state.text_wrapper.set_font(font, font_size, cx); - state.text_wrapper.prepare_if_need(&state.text, cx); + self.state.update(cx, |state, _| { state.disabled = self.disabled; state.size = self.size; }); @@ -365,8 +357,8 @@ impl RenderOnce for Input { .input_px(self.size) .input_py(self.size) .input_h(self.size) + .input_text_size(self.size) .cursor_text() - .text_size(font_size) .items_center() .when(state.mode.is_multi_line(), |this| { this.h_auto() diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index 239b28a7..f777eb2f 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -368,11 +368,7 @@ impl InputState { Self { focus_handle: focus_handle.clone(), text: "".into(), - text_wrapper: TextWrapper::new( - text_style.font(), - text_style.font_size.to_pixels(window.rem_size()), - None, - ), + text_wrapper: TextWrapper::new(text_style.font(), window.rem_size(), None), blink_cursor, history, selected_range: Selection::default(), diff --git a/crates/ui/src/input/text_wrapper.rs b/crates/ui/src/input/text_wrapper.rs index ae7d9ccb..e081b0b0 100644 --- a/crates/ui/src/input/text_wrapper.rs +++ b/crates/ui/src/input/text_wrapper.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use gpui::{point, px, size, App, Font, LineFragment, Pixels, Point, ShapedLine, Size, Window}; +use gpui::{App, Font, LineFragment, Pixels, Point, ShapedLine, Size, Window, point, px, size}; use ropey::Rope; use smallvec::SmallVec; @@ -500,7 +500,7 @@ impl LineLayout { #[cfg(test)] mod tests { use super::*; - use gpui::{px, Boundary, FontFeatures, FontStyle, FontWeight}; + use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px}; #[test] fn test_update() { diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index 3de2f733..93a4b0da 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -437,9 +437,9 @@ impl StyleSized for T { match size { Size::XSmall => self.text_xs(), Size::Small => self.text_sm(), - Size::Medium => self.text_base(), - Size::Large => self.text_lg(), - Size::Size(size) => self.text_size(size), + Size::Medium => self.text_sm(), + Size::Large => self.text_base(), + Size::Size(size) => self.text_size(size * 0.875), } } @@ -477,7 +477,6 @@ impl StyleSized for T { Size::XSmall => self.h_5(), _ => self.h_6(), } - .input_text_size(size) } #[inline]