text_edit font sizing on Length; add Button::width and Text::line_height
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

Unify TextEdit's font size with the button label. The button resolved its `font_size` Length in font space (`viewport_logical`, so the `× dpi_scale` at raster lands the right physical size), while `TextEdit::font_size` took a raw `f32` that the draw / hit-test paths treated as a logical size. A caller that resolved a Length against the physical surface and passed the result as that `f32` therefore double-counted `dpi_scale` and got a font that rendered too large. `TextEdit::font_size` now takes `impl Into<Length>` and is resolved against `viewport_logical` exactly like the button, so the two paths agree. The field stores `Option<Length>` (`None` follows the widget-scaling mode at the theme default), retiring the old `f32` sentinel (`0.0` = mode, negative = fluid design px). `font_size_fluid` becomes shorthand for `Length::fluid( n )`. The `Option<Length>` flows through the `WidgetHandlers::TextEdit` snapshot and `text_input_geometry` and is resolved at draw / hit-test time, both of which carry a canvas; the inner measure helpers (`wrapping`, `hit_test`) keep `f32` because they receive the already-resolved value. Backward compatible: `f32` call sites still compile via `f32: Into<Length>` (→ `Length::px`), with the same result as before.
Add `Button::width( impl Into<Length> )`. Text buttons size to their label plus padding; some layouts need a pinned width instead — a full-width or surface-proportional button. The new builder mirrors `height`: resolved in physical layout space, clamped to the available `max_width`, propagated through `map_msg`, and a no-op for icon buttons.
Add `Text::line_height( mult )`. Wrapped multi-line text used the font's declared leading (`new_line_size`), which is tight for some labels; the multiplier scales the gap between wrapped lines (`1.0`, the default, keeps the natural leading — every other `text` is unchanged — and a `0.5` floor keeps lines from overlapping). Applied uniformly in `preferred_size` and `draw` so the reported height and the drawn baselines stay consistent.
Tests: `button` gains a pinned-width and a size-to-content case; `text` gains a line-height default / clamp test and a check that doubling the line height doubles a wrapped block's reported height. Docs: `docs/widgets.md` `text` / `button` / `separator` sections updated for the new builders, and a `CHANGELOG.md` "Unreleased" section covering this batch alongside the responsive work already landed.
This commit is contained in:
2026-07-10 10:38:30 +02:00
parent ce893ac776
commit 8762ab9ce0
11 changed files with 176 additions and 62 deletions
+28 -32
View File
@@ -26,21 +26,16 @@ pub use draw::password_toggle_hit_zone;
pub( crate ) use hit_test::byte_offset_at;
pub( crate ) use cursor::{ cursor_visual_down, cursor_visual_end, cursor_visual_home, cursor_visual_up };
/// Decode a single-line font-size field into a concrete px against the
/// widget-scaling mode. A positive value is an explicit fixed size; `0.0`
/// follows the mode at the theme default; a negative value follows the mode
/// at the design px `-value` (see [`TextEdit::font_size_fluid`]). Shared by
/// the draw and hit-test paths, which all carry a canvas.
pub( crate ) fn resolve_font_size( canvas: &Canvas, fs: f32 ) -> f32
/// Resolve the font size into a concrete logical px. `Some( len )` resolves
/// against [`Canvas::viewport_logical`] — the font space, so `× dpi_scale` at
/// raster yields the right physical size — exactly like the button label;
/// `None` follows the widget-scaling mode at the theme default. Shared by the
/// draw and hit-test paths, which all carry a canvas.
pub( crate ) fn resolve_font_size( canvas: &Canvas, fs: Option<Length> ) -> f32
{
if fs > 0.0
{
fs
} else if fs == 0.0 {
canvas.font_px( theme::FONT_SIZE )
} else {
canvas.font_px( -fs )
}
fs
.map( |l| l.resolve( canvas.viewport_logical(), Length::EM_BASE_DEFAULT ) )
.unwrap_or_else( || canvas.font_px( theme::FONT_SIZE ) )
}
/// A text input field.
@@ -150,13 +145,14 @@ pub struct TextEdit<Msg: Clone>
/// numeric inputs).
pub( crate ) fixed_width: Option<f32>,
/// Font size in pixels for the single-line draw path. `0.0` (the
/// default) follows the process [`crate::WidgetScaling`] mode (fluid by
/// default, via [`crate::Canvas::font_px`]); any positive value pins an
/// explicit size. The sentinel avoids threading a canvas through the
/// handler snapshot; every consumer that reads it has a canvas and
/// resolves it via [`Self::effective_font_size`]. Multiline mode ignores
/// this and always uses the theme constant.
pub( crate ) font_size: f32,
/// Label font size. `None` follows the process [`crate::WidgetScaling`]
/// mode at the theme default (via [`crate::Canvas::font_px`]); a
/// [`Length`] pins it and is resolved in font space
/// ([`crate::Canvas::viewport_logical`]), exactly like the button label.
/// Resolved via [`Self::effective_font_size`] by every consumer, all of
/// which carry a canvas. Multiline mode ignores this and always uses the
/// theme constant.
pub( crate ) font_size: Option<Length>,
/// Optional single-line field height. `None` uses the theme default
/// (`theme::HEIGHT`); a [`Length`] scales the box with the surface, so
/// a field can match a fluid button or grow with a fluid form. Resolved
@@ -206,7 +202,7 @@ impl<Msg: Clone> TextEdit<Msg>
align: super::text::TextAlign::Left,
borderless: false,
fixed_width: None,
font_size: 0.0,
font_size: None,
height: None,
select_on_focus: false,
password_toggle: None,
@@ -240,24 +236,24 @@ impl<Msg: Clone> TextEdit<Msg>
}
}
/// Pin the single-line font size to an explicit fixed px. Without any
/// font-size call the size follows the process [`crate::WidgetScaling`]
/// mode at the theme default; see [`Self::font_size_fluid`] to follow
/// the mode at a custom design px. Ignored in multiline mode.
pub fn font_size( mut self, px: f32 ) -> Self
/// Set the label font size. Accepts logical `f32` pixels or any
/// [`Length`] (e.g. `Length::vmin( 4.5 ).clamp( 16.0, 32.0 )` to scale
/// with the surface), resolved in font space exactly like the button
/// label. Without any font-size call the size follows the process
/// [`crate::WidgetScaling`] mode at the theme default. Ignored in
/// multiline mode.
pub fn font_size( mut self, size: impl Into<Length> ) -> Self
{
self.font_size = px.max( 1.0 );
self.font_size = Some( size.into() );
self
}
/// Set a font size that follows the process [`crate::WidgetScaling`]
/// mode at the given design px — the fluid counterpart of
/// [`Self::font_size`]. Encoded as a negative sentinel so it flows
/// through the handler snapshot as a plain `f32` without threading a
/// canvas; every consumer resolves it via `resolve_font_size`.
/// [`Self::font_size`]. Shorthand for `.font_size( Length::fluid( n ) )`.
pub fn font_size_fluid( mut self, design_px: f32 ) -> Self
{
self.font_size = -design_px.max( 1.0 );
self.font_size = Some( Length::fluid( design_px.max( 1.0 ) ) );
self
}