From f7426a86f2ccc40a18bc6b21d92fc3f073490de1 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:51:00 +0200 Subject: Elide a long window title instead of cutting it mid-glyph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A title that did not fit was hard-cut at whatever character crossed the button reservation. Nothing marked the cut, so a truncated name read as the whole name - "annual-report-final-v7-reviewed-2026-with-appendix.ods - LibreO" looks like a filename, not like a filename with its tail missing. Titles now end in an ellipsis when they are shortened, which is what Windows, GNOME and KDE all do, and it keeps the informative half: an application or document title almost always begins distinctively and ends in boilerplate. Whole characters are dropped until the ellipsis fits beside what remains, so the result never overruns the buttons. A titlebar with no room even for the ellipsis draws nothing, rather than a lone "..." that says less than an empty titlebar does. The mark itself is "…" where the system font has that glyph and "..." where it does not. A missing glyph rasterizes to nothing at all, which would have quietly reintroduced the invisible-truncation problem on any font without it. Also bounded the work: a client may set a title of any length - a browser tab carrying a whole data URL - and every character cost a rasterization before the layout could decide it did not fit. Measurement stops at 256 characters, well past anything legible in a titlebar, and a title that long is elided many times over regardless. Four tests: an over-long title fits its span and ends in the ellipsis mark; a title that fits is left exactly alone; a titlebar too narrow for the ellipsis draws nothing; a 20,000-character title never lays out more than the cap. Checked on screen too, at 600px and at 220px. --- crates/wayland/src/decoration/tests.rs | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'crates/wayland/src/decoration/tests.rs') diff --git a/crates/wayland/src/decoration/tests.rs b/crates/wayland/src/decoration/tests.rs index 0d6609f..0f1a9ab 100644 --- a/crates/wayland/src/decoration/tests.rs +++ b/crates/wayland/src/decoration/tests.rs @@ -967,3 +967,51 @@ fn snap_flyout_has_an_internal_grid_line_between_columns() { let idx = (30 * width as usize + cell_w as usize) * 4; assert_eq!(buf[idx + 3], 255, "column boundary must be drawn, not just the outer border"); } + +/// A long title used to be hard-cut mid-glyph, which leaves no sign that +/// anything was removed - a truncated name reads as the whole name. +#[test] +fn a_long_title_is_elided_rather_than_cut() { + let Some(font) = super::font::find_system_font() else { return }; + let long = "a-very-long-window-title-that-cannot-possibly-fit-in-a-narrow-titlebar.txt"; + let laid_out = super::titlebar::lay_out_title(&font, long, 120.0); + let width: f32 = laid_out.iter().map(|(m, _)| m.advance_width).sum(); + assert!(width <= 120.0, "elided title still overflows: {width}"); + let full: f32 = long.chars().map(|c| font.metrics(c, super::font::FONT_PIXELS).advance_width).sum(); + assert!(width < full, "nothing was elided at all"); + // The last glyph must be an ellipsis mark, not whatever character the + // cut happened to land on. + let ellipsis = if font.lookup_glyph_index('\u{2026}') != 0 { '\u{2026}' } else { '.' }; + let expected = font.rasterize(ellipsis, super::font::FONT_PIXELS); + let last = laid_out.last().expect("title elided to nothing"); + assert_eq!(last.1, expected.1, "a title was cut without an ellipsis"); +} + +/// A title that fits must be left exactly alone - no ellipsis, nothing +/// dropped. +#[test] +fn a_title_that_fits_is_not_touched() { + let Some(font) = super::font::find_system_font() else { return }; + let short = "Files"; + let laid_out = super::titlebar::lay_out_title(&font, short, 400.0); + assert_eq!(laid_out.len(), short.chars().count()); +} + +/// A titlebar too narrow even for the ellipsis draws nothing, rather than a +/// lone "..." that says less than an empty titlebar. +#[test] +fn a_titlebar_with_no_room_at_all_draws_no_title() { + let Some(font) = super::font::find_system_font() else { return }; + assert!(super::titlebar::lay_out_title(&font, "anything", 1.0).is_empty()); +} + +/// A client may set a title of any length; nothing should rasterize +/// thousands of glyphs to discover that. +#[test] +fn a_pathologically_long_title_is_bounded() { + let Some(font) = super::font::find_system_font() else { return }; + let huge: String = std::iter::repeat('x').take(20_000).collect(); + let laid_out = super::titlebar::lay_out_title(&font, &huge, 400.0); + assert!(laid_out.len() < super::titlebar::MAX_TITLE_CHARS); +} + -- cgit v1.2.3