From b219decd755d7fb3c8ddafbb4bd2ea118d99fc7c Mon Sep 17 00:00:00 2001 From: HassanKhan Date: Fri, 13 Nov 2026 14:19:58 +0500 Subject: [PATCH] Character panel: the highlighted font height icon keeps its contrast (#6304) On the open list's row of the current reference, drawn on the accent-tinted selection fill, the icon draws its measure in the name's colour instead of the accent, which lost its contrast there. The other rows and the closed field keep the accent. --- crates/ui-egui/src/panels/character.rs | 7 ++- crates/ui-egui/src/panels/character/tests.rs | 65 ++++++++++++++++++++ crates/ui-egui/src/widgets/dropdowns.rs | 19 +++--- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/src/panels/character.rs b/crates/ui-egui/src/panels/character.rs index d1712c42d7..b761e6fd66 100644 --- a/crates/ui-egui/src/panels/character.rs +++ b/crates/ui-egui/src/panels/character.rs @@ -951,8 +951,11 @@ fn font_height_reference(app: &mut VectorcraftApp, ui: &mut Ui, width: f32) { let labels = [tl!("Em Box"), tl!("Cap Height"), tl!("x-Height"), tl!("ICF Box")]; let current = shared(app, |s| s.font_height_reference); let shown = FONT_HEIGHT_REFERENCES.iter().position(|(r, _)| Some(*r) == current); - let mark = Tokens::get(ui.ctx()).accent; - let icon = |ui: &Ui, rect: egui::Rect, i: usize, ink: egui::Color32| { + let accent = Tokens::get(ui.ctx()).accent; + let icon = |ui: &Ui, rect: egui::Rect, i: usize, ink: egui::Color32, highlighted: bool| { + // On the highlighted row the selection fill is accent-tinted, so the measure takes the + // name's colour there to keep its contrast. + let mark = if highlighted { ink } else { accent }; if let Some((r, _)) = FONT_HEIGHT_REFERENCES.get(i) { paint_font_height_icon(ui.painter(), rect, *r, ink, mark); } diff --git a/crates/ui-egui/src/panels/character/tests.rs b/crates/ui-egui/src/panels/character/tests.rs index 4e403f0903..dd4b12adf5 100644 --- a/crates/ui-egui/src/panels/character/tests.rs +++ b/crates/ui-egui/src/panels/character/tests.rs @@ -886,6 +886,71 @@ fn font_height_references_show_their_icons() { } } +/// In the open Set Font Height Reference list, the current reference's row is drawn on the +/// accent-tinted selection fill, so its icon draws the measure in the name's colour rather than +/// the accent, which would lose its contrast there; the other rows keep the accent (#6304). +#[test] +fn the_highlighted_font_height_icon_keeps_its_contrast() { + use super::super::tests_appearance::click; + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 300, "height": 200})).unwrap(); + let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Hx", "size": 12})).unwrap()["id"].clone(); + app.run("select.set", json!({"ids": [id]})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + set_pstate(&ctx, FONT_HEIGHT_OPTIONS, true); + let accent = Tokens::get(&ctx).accent; + // Every text drawn with its rect, and every stroke drawn with its rect and colour. + let frame = |ctx: &egui::Context, app: &mut VectorcraftApp| { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(app, ui)); + out.textures_delta.clear(); + fn collect(s: &egui::Shape, texts: &mut Vec<(String, egui::Rect)>, strokes: &mut Vec<(egui::Rect, egui::Color32)>) { + match s { + egui::Shape::Text(t) => texts.push((t.galley.text().to_string(), t.visual_bounding_rect())), + egui::Shape::Vec(v) => v.iter().for_each(|s| collect(s, texts, strokes)), + egui::Shape::LineSegment { stroke, .. } => strokes.push((s.visual_bounding_rect(), stroke.color)), + egui::Shape::Path(p) => { + if let egui::epaint::ColorMode::Solid(c) = p.stroke.color { + strokes.push((s.visual_bounding_rect(), c)); + } + } + egui::Shape::Rect(r) => strokes.push((s.visual_bounding_rect(), r.stroke.color)), + _ => {} + } + } + let (mut texts, mut strokes) = (vec![], vec![]); + for c in &out.shapes { + collect(&c.shape, &mut texts, &mut strokes); + } + (texts, strokes) + }; + // The stroke colours in the icon's place before the text `rect`. + let colours = |strokes: &[(egui::Rect, egui::Color32)], rect: egui::Rect| -> Vec { + let place = egui::Rect::from_min_max( + egui::pos2(rect.min.x - FONT_HEIGHT_ICON - 8.0, rect.center().y - 10.0), + egui::pos2(rect.min.x, rect.center().y + 10.0), + ); + strokes.iter().filter(|(r, _)| r.is_finite() && place.contains_rect(*r)).map(|(_, c)| *c).collect() + }; + frame(&ctx, &mut app); + let (texts, _) = frame(&ctx, &mut app); + let field = texts.iter().find(|(t, _)| t == "Em Box").map(|(_, r)| *r).expect("the closed field shows Em Box"); + click(&ctx, &mut app, field.center(), show); + frame(&ctx, &mut app); + let (texts, strokes) = frame(&ctx, &mut app); + let row = |name: &str| texts.iter().rev().find(|(t, _)| t == name).map(|(_, r)| *r).unwrap_or_else(|| panic!("the open list shows {name}")); + // The list fades in, so colours are compared without their alpha. + let rgb = |c: egui::Color32| { + let [r, g, b, _] = c.to_srgba_unmultiplied(); + [r, g, b] + }; + let is_accent = |c: &egui::Color32| rgb(*c).iter().zip(rgb(accent)).all(|(a, b)| a.abs_diff(b) <= 4); + let current = colours(&strokes, row("Em Box")); + assert!(!current.is_empty() && !current.iter().any(is_accent), "the highlighted Em Box icon draws in the accent: {current:?}"); + let other = colours(&strokes, row("Cap Height")); + assert!(other.iter().any(is_accent), "the other rows keep the accent measure: {other:?}"); +} + /// Tate-chu-yoko is in the panel menu with the East Asian options, and choosing it marks the /// selected characters. #[test] diff --git a/crates/ui-egui/src/widgets/dropdowns.rs b/crates/ui-egui/src/widgets/dropdowns.rs index c8fba54aed..a2741ff1cb 100644 --- a/crates/ui-egui/src/widgets/dropdowns.rs +++ b/crates/ui-egui/src/widgets/dropdowns.rs @@ -37,9 +37,11 @@ pub fn dropdown_names_named(ui: &mut Ui, id: impl std::hash::Hash, name: Option< /// [`dropdown_names`] whose options each show an icon before their name, in the open list and in /// the closed field, so that a name that says little on its own is shown with a picture of what it -/// means (#6304). `icon(ui, rect, index, ink)` paints option `index`'s icon into `rect`, an -/// `icon_size` square, with `ink`, the colour the row's name is drawn in. `current` is the option -/// shown; `None` shows a blank field (characters that differ). +/// means (#6304). `icon(ui, rect, index, ink, highlighted)` paints option `index`'s icon into +/// `rect`, an `icon_size` square, with `ink`, the colour the row's name is drawn in; +/// `highlighted` is true on the open list's row of the current option, drawn on the selection +/// fill, where an accent-coloured mark would lose its contrast. `current` is the option shown; +/// `None` shows a blank field (characters that differ). pub fn dropdown_icon_names( ui: &mut Ui, id: impl std::hash::Hash, @@ -47,18 +49,21 @@ pub fn dropdown_icon_names( options: &[&str], width: f32, icon_size: f32, - icon: impl Fn(&Ui, Rect, usize, egui::Color32), + icon: impl Fn(&Ui, Rect, usize, egui::Color32, bool), ) -> Option { let icon = &icon; - let atom = move |i: usize| egui::Atom::paint(Vec2::splat(icon_size), move |ui, a| icon(ui, a.rect, i, a.fallback_text_color)); + let atom = move |i: usize, highlighted: bool| { + egui::Atom::paint(Vec2::splat(icon_size), move |ui, a| icon(ui, a.rect, i, a.fallback_text_color, highlighted)) + }; let shown = match current.and_then(|i| options.get(i).map(|name| (i, *name))) { - Some((i, name)) => (atom(i), egui::RichText::new(name).size(12.0)).into_atoms(), + Some((i, name)) => (atom(i, false), egui::RichText::new(name).size(12.0)).into_atoms(), None => egui::RichText::new("").size(12.0).into_atoms(), }; combo_atoms_named(ui, id, None, shown, width, |ui| { let mut chosen = None; for (i, o) in options.iter().enumerate() { - if ui.add(egui::Button::selectable(current == Some(i), (atom(i), *o))).clicked() { + let on = current == Some(i); + if ui.add(egui::Button::selectable(on, (atom(i, on), *o))).clicked() { chosen = Some(i); } }