row, button, widget: distribute a row's width shortfall instead of overflowing, elide button labels into the rect they are granted, icon_size takes a Length
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

Row deficit distribution (layout/row.rs). `Row` knew how to hand out leftover width but had no notion of a shortfall: `leftover` was floored at zero and every child was laid out at its preferred width, so a cluster wider than its rect simply overflowed — and symmetrically, because the no-spacer branch centres the block, which is why both end labels of a segmented control were clipped at once rather than only the trailing one. The shortfall now comes off the widest children first, by water filling: `width_cap` sorts the flexible widths and returns the largest per-child cap `c` for which `sum( min( w, c ) ) <= available`, so a long title absorbs the whole deficit while an icon button beside it keeps its size, and equal siblings — a segmented control — share it evenly. A uniform scale-down, which was the first attempt, was wrong precisely there: it thinned a header's back arrow along with the title it sat next to. Spacers and flex children stay out of the flexible set, so a pinned gap keeps the width it was given — an explicit spacer is a decision, not slack — and `Row::no_shrink()` opts a row out entirely, for strips deliberately wider than their viewport such as a carousel rail meant to be scrolled. `align_right` and the centring branch now measure the laid width rather than the preferred one, so a shrunken row is positioned against what it actually occupies.
Button label elision (widget/button/mod.rs, widget/mod.rs, widget/list_item/mod.rs). Shrinking a row is only half the fix: a leaf handed less width than it asked for still painted its full string, so the deficit came out clipped instead of truncated. `draw_text_button` now elides the label against the rect the layout granted, less the horizontal padding, for all three variants. The truncation rule moves out of `ListItem`, where it was a private helper, into a single crate-internal `widget::elide` the two share. `Text` keeps its own inline copy for now: it measures through an optional font override that this signature does not carry, and folding the two together is a separate change.
icon_size as a Length (widget/button/mod.rs). `Button::icon_size` was the one geometry setter that ignored the widget-scaling mode — it took a bare `f32`, pinned it, and used `0.0` as the "unset" sentinel. It now takes `impl Into<Length>` behind an `Option` and resolves through `Canvas::resolve_geom`, the way `height`, `width` and `font_size` already do. A bare number still means `Length::px`, so every existing call keeps its exact size; what it adds is `Length::widget( n )`, which follows the active mode the way stock icons do. Without it a back arrow pinned at 21 px sat next to a `list_item` chevron that fluid sizing had grown well past 21, and read as visibly smaller on the same row.
This commit is contained in:
2026-08-05 12:37:03 +02:00
parent 530a5696b9
commit 1290f9400e
6 changed files with 228 additions and 50 deletions

View File

@@ -108,3 +108,43 @@ pub use factory::{ button, icon_button, text_edit, image, text, container, exter
/// — the same closure is invoked once per emitted message, regardless
/// of how many leaves the sub-tree has.
pub( crate ) type MapFn<Msg, U> = std::sync::Arc<dyn Fn( Msg ) -> U>;
/// Shorten `text` with a trailing ellipsis so it fits `max_w` at `size`.
///
/// The single truncation rule for widgets that paint a line of text into a
/// rect they do not control: a leaf must stay inside what the layout gave
/// it, or a long label runs under its neighbour. Widths accumulate per
/// character, which is what [`text::Text`] does with its own inline copy of
/// this — folding that one in is pending, as it measures through a font
/// override this signature does not carry.
pub( crate ) fn elide(
canvas: &crate::render::Canvas,
text: &str,
size: f32,
max_w: f32,
) -> String
{
if max_w <= 0.0
{
return String::new();
}
if canvas.measure_text( text, size ) <= max_w
{
return text.to_string();
}
let ellipsis = "...";
let budget = max_w - canvas.measure_text( ellipsis, size );
if budget <= 0.0
{
return ellipsis.to_string();
}
let mut accum = 0.0_f32;
let kept: String = text.chars().take_while( |ch|
{
accum += canvas.measure_text( &ch.to_string(), size );
accum <= budget
} ).collect();
format!( "{kept}{ellipsis}" )
}