From 7b9ebaacbb971fb4f4098156ecf67e2aaa7c8182 Mon Sep 17 00:00:00 2001 From: RealtaNua Date: Sat, 17 Oct 2026 23:50:48 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20fitting=20Intensity=20translations;?= =?UTF-8?q?=20cut=20values=20end=20in=20"=E2=80=A6",=20cut=20labels=20show?= =?UTF-8?q?=20on=20hover=20(#256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From review: - ru and pt-BR "Intensity" were too long for the glass field and ended in "…"; they are now "Яркость" and "Brilho", which fit. - A value too long for its field ends in "…" instead of being clipped, so "100000" never shows as "10". - A label cut short shows whole on hover. - The every-language test now checks the Intensity label fits whole in each catalog (it fails with the old ru translation); the panel test checks the label isn't cut and that the field is the 98.5 points the language test measures against. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/ui-egui/locales/pt-BR.toml | 2 +- crates/ui-egui/locales/ru.toml | 2 +- crates/ui-egui/src/field_tests.rs | 33 ++++++++++++++----------------- crates/ui-egui/src/widgets.rs | 10 ++++++++-- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/crates/ui-egui/locales/pt-BR.toml b/crates/ui-egui/locales/pt-BR.toml index aabc8806..d970db14 100644 --- a/crates/ui-egui/locales/pt-BR.toml +++ b/crates/ui-egui/locales/pt-BR.toml @@ -614,7 +614,7 @@ status = "draft — needs native review" "Instance swap (no other component)" = "Troca de instância (nenhum outro componente)" "Instances" = "Instâncias" "Instant" = "Instantâneo" -"Intensity" = "Intensidade" +"Intensity" = "Brilho" "Interaction" = "Interação" "Interactions" = "Interações" "Interface design and prototyping. A free, open-source, clean-room design tool written from scratch in Rust, part of the ArtCraft Crafting Apps." = "Design de interfaces e prototipagem. Uma ferramenta de design gratuita e de código aberto, com implementação independente (clean-room), escrita do zero em Rust, parte dos Crafting Apps da ArtCraft." diff --git a/crates/ui-egui/locales/ru.toml b/crates/ui-egui/locales/ru.toml index 7ec18507..dc850023 100644 --- a/crates/ui-egui/locales/ru.toml +++ b/crates/ui-egui/locales/ru.toml @@ -635,7 +635,7 @@ name = "Русский" "Instance swap (no other component)" = "Замена экземпляра (других компонентов нет)" "Instances" = "Экземпляры" "Instant" = "Мгновенно" -"Intensity" = "Интенсивность" +"Intensity" = "Яркость" "Interaction" = "Взаимодействие" "Interactions" = "Взаимодействия" "Interface design and prototyping. A free, open-source, clean-room design tool written from scratch in Rust, part of the ArtCraft Crafting Apps." = "Дизайн интерфейсов и прототипирование. Бесплатный инструмент с открытым исходным кодом, независимо созданный с нуля на Rust, из семейства ArtCraft Crafting Apps." diff --git a/crates/ui-egui/src/field_tests.rs b/crates/ui-egui/src/field_tests.rs index 14158972..9ddc48e5 100644 --- a/crates/ui-egui/src/field_tests.rs +++ b/crates/ui-egui/src/field_tests.rs @@ -502,34 +502,31 @@ fn the_glass_light_intensity_field_has_a_label() { u.frame(vec![]); let shapes = u.frame_output(vec![]).shapes; let lead = u.ctx.read_response(Id::new(("fx_gi", 0usize)).with("lead")).expect("the intensity field is shown").rect; - let labels: Vec = painted_texts(&shapes).into_iter().filter(|(_, _, at)| lead.contains(at.center())).map(|(text, ..)| text).collect(); - assert_eq!(labels, ["Intensity"]); + let labels: Vec<(String, bool)> = + painted_texts(&shapes).into_iter().filter(|(_, _, at)| lead.contains(at.center())).map(|(text, cut, _)| (text, cut)).collect(); + assert_eq!(labels, [("Intensity".to_string(), false)], "labelled, and not cut short"); + let field = u.ctx.read_response(Id::new(("fx_gi", 0usize)).with("field")).expect("its value").rect; + assert_eq!(field.max.x - lead.min.x, GLASS_FIELD_W, "the width the next test measures against"); } /// A glass settings field's width in the Design panel at its default width. -const GLASS_FIELD_W: f32 = 98.0; +const GLASS_FIELD_W: f32 = 98.5; -/// The glass settings' labels leave their values room in every language, and are cut only when -/// they would crowd them (#256). +/// The light intensity label this fix adds fits whole beside its value in every language (#256). +/// Other glass labels may end in "…" in some languages (es "Profundidad"); a longer +/// "Intensity" translation fails here. #[test] -fn glass_labels_fit_beside_their_values_in_every_language() { +fn the_intensity_label_fits_whole_in_every_language() { use crate::i18n::{self, Text}; - let keys = ["Light|glass", "Intensity", "Refraction", "Depth", "Dispersion", "Frost", "Splay"]; let ctx = egui::Context::default(); let mut out = ctx.run_ui(Default::default(), |ui| { let width = |s: &str| ui.painter().layout_no_wrap(s.to_string(), crate::theme::regular(11.0), egui::Color32::WHITE).size().x; for lang in i18n::languages() { - for key in keys { - let label = lang.ui.iter().find(|(k, _)| *k == key).map_or(key, |(_, t)| match t { - Text::Plain(s) | Text::Plural { other: s, .. } => s, - }); - let (lead, value) = (crate::widgets::text_lead_width(ui, label, GLASS_FIELD_W), width("100%")); - assert!(lead + value <= GLASS_FIELD_W, "{}: no room for the value beside {label:?}", lang.code); - // Cut off only when it would crowd the value's 32 points (es "Profundidad"). - if 12.0 + width(label) <= GLASS_FIELD_W - 32.0 { - assert!(lead >= 12.0 + width(label), "{}: {label:?} is cut off", lang.code); - } - } + let label = lang.ui.iter().find(|(k, _)| *k == "Intensity").map_or("Intensity", |(_, t)| match t { + Text::Plain(s) | Text::Plural { other: s, .. } => s, + }); + let lead = crate::widgets::text_lead_width(ui, label, GLASS_FIELD_W); + assert!(lead >= 12.0 + width(label), "{}: {label:?} would end in \"…\"", lang.code); } }); out.textures_delta.clear(); diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 4763b47c..c415842d 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -223,7 +223,12 @@ fn number_field_impl( } match lead { Lead::Text(s) => { - crate::bidi::paint_name(ui.painter(), pos2(lead_rect.min.x + 8.0, rect.center().y), s, regular(11.0), t.text_secondary, lead_w - 12.0); + let galley = crate::bidi::name_galley(ui.painter(), s, regular(11.0), t.text_secondary, lead_w - 12.0); + // A label cut short shows whole on hover. + if galley.elided { + lead_resp.clone().on_hover_text(s); + } + ui.painter().galley(pos2(lead_rect.min.x + 8.0, rect.center().y - galley.size().y / 2.0), galley, t.text_secondary); } Lead::Icon(i, _) => { icons::paint(ui.painter(), Rect::from_center_size(lead_rect.center() + vec2(1.0, 0.0), vec2(14.0, 14.0)), i, t.icon_secondary) @@ -347,7 +352,8 @@ fn number_field_impl( None => empty.to_string(), }; let col = if value.is_some() { t.text } else { t.text_secondary }; - ui.painter().with_clip_rect(edit_rect).text(pos2(edit_rect.min.x, rect.center().y), Align2::LEFT_CENTER, s, regular(11.0), col); + // A value too long for its room ends in "…", so a cut number never reads as another. + crate::bidi::paint_name(ui.painter(), pos2(edit_rect.min.x, rect.center().y), &s, regular(11.0), col, edit_rect.width()); } } // The variable button shows while the pointer is over a bindable field.