From 411f96b7c87e33043ede548d39a42642f48472c4 Mon Sep 17 00:00:00 2001 From: ham9zah Date: Tue, 27 Oct 2026 13:01:20 +0300 Subject: [PATCH] Appearance: the object chip of a selection whose fills differ shows "?" (#4174) The object row drew the None swatch when the selected objects' fills differ, which reads as "no fill". It now draws the "?" chip the Control bar and Properties panel use for differing paints. --- crates/ui-egui/src/panels/appearance.rs | 17 +++++++---------- crates/ui-egui/src/panels/appearance/tests.rs | 7 +++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/ui-egui/src/panels/appearance.rs b/crates/ui-egui/src/panels/appearance.rs index b1337be67..19a578137 100644 --- a/crates/ui-egui/src/panels/appearance.rs +++ b/crates/ui-egui/src/panels/appearance.rs @@ -219,12 +219,10 @@ fn text(ui: &Ui, pos: egui::Pos2, s: &str, strong: bool) { ui.painter().text(pos, egui::Align2::LEFT_CENTER, s, egui::FontId::proportional(12.5), if strong { t.text_strong } else { t.text }); } -fn chip(ui: &Ui, r: Rect, p: &Paint) { - let t = Tokens::get(ui.ctx()); +fn chip(ui: &Ui, r: Rect, p: &Paint, mixed: bool) { ui.painter().rect_filled(r.expand(1.0), 0.0, egui::Color32::BLACK); - widgets::paint_chip(ui, r, p); + widgets::proxy_chip(ui, r, p, mixed, None); ui.painter().rect_stroke(r, 0.0, Stroke::new(1.0, egui::Color32::WHITE), StrokeKind::Inside); - let _ = t; } /// A dotted-underline link drawn at `pos`; returns clicked. @@ -271,13 +269,12 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { }; if !hide_thumb { let th = Rect::from_min_size(r.left_center() + vec2(6.0, -12.0), vec2(24.0, 24.0)); - // Objects whose fills differ show no one of them. + // Objects whose fills differ show "?", like the Properties panel's Fill chip. let fill = match &node { - Some(_) if mix.as_ref().is_some_and(|m| m.fills_differ) => Paint::None, Some(n) => n.appearance.fill_paint(), None => app.session.paint.fill.clone(), }; - chip(ui, th, &fill); + chip(ui, th, &fill, mix.as_ref().is_some_and(|m| m.fills_differ)); if let Some(n) = node.as_ref().filter(|_| !mixed) { thumbnail_drag(ui, th, n.id); } @@ -394,7 +391,7 @@ fn default_stack(app: &mut VectorcraftApp, ui: &mut Ui) { } else if link(ui, pos2(lx, r.center().y), ("ap-def-link", i), tl!("Stroke:")) { app.ui.open_panel = Some("stroke".into()); } - chip(ui, Rect::from_min_size(pos2(r.left() + EYE_W + 76.0, r.center().y - 9.0), vec2(18.0, 18.0)), it.paint()); + chip(ui, Rect::from_min_size(pos2(r.left() + EYE_W + 76.0, r.center().y - 9.0), vec2(18.0, 18.0)), it.paint(), false); if let AppearanceItem::Stroke(st) = it { let w = app.session.stroke_unit().format(st.width); text(ui, pos2(r.left() + EYE_W + 106.0, r.center().y), &w, false); @@ -496,7 +493,7 @@ fn stack(app: &mut VectorcraftApp, ui: &mut Ui, n: &Node, sel: Sel, object_row: // Swatch with a swatches popup that sets this item's paint; Shift-click opens the Color // panel's mixer on this row instead. let cr = Rect::from_min_size(pos2(r.left() + EYE_W + 76.0, r.center().y - 9.0), vec2(18.0, 18.0)); - chip(ui, cr, paint); + chip(ui, cr, paint, false); let cresp = ui .interact(cr.expand(2.0), ui.scope_id().with(("ap-chip", i)), Sense::click()) .on_hover_text(tl!("Click to choose a swatch, Shift-click to mix a colour")); @@ -683,7 +680,7 @@ fn contents_row(app: &mut VectorcraftApp, ui: &mut Ui, n: &Node, label: &str, se if characters { for (k, stroke) in [false, true].into_iter().enumerate() { let cr = Rect::from_min_size(pos2(r.left() + EYE_W + 100.0 + 24.0 * k as f32, r.center().y - 9.0), vec2(18.0, 18.0)); - chip(ui, cr, n.proxy_paint(stroke, None).map_or(&Paint::None, |(p, ..)| p)); + chip(ui, cr, n.proxy_paint(stroke, None).map_or(&Paint::None, |(p, ..)| p), false); } } // An image's row has no members to target. diff --git a/crates/ui-egui/src/panels/appearance/tests.rs b/crates/ui-egui/src/panels/appearance/tests.rs index 73f0eacf9..16bc55039 100644 --- a/crates/ui-egui/src/panels/appearance/tests.rs +++ b/crates/ui-egui/src/panels/appearance/tests.rs @@ -554,17 +554,20 @@ fn a_mixed_selection_lists_what_its_objects_share() { run(&mut app, "select.set", json!({"ids": ids})); let shown = frame(&mut app); let kind = app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(ids[0].as_u64().unwrap())).unwrap().kind_label(); - assert_eq!(shown.first().map(String::as_str), Some(kind), "the object row names their kind: {shown:?}"); + assert_eq!(shown.iter().find(|t| *t != "?").map(String::as_str), Some(kind), "the object row names their kind: {shown:?}"); assert!(shown.iter().any(|t| t == "Mixed Appearances"), "{shown:?}"); assert!(shown.iter().any(|t| t == "Stroke:") && !shown.iter().any(|t| t == "Fill:"), "the stroke, not the fills: {shown:?}"); assert!(shown.iter().any(|t| t.contains("Feather")), "the shared effect: {shown:?}"); assert!(shown.iter().any(|t| t == "Opacity:"), "{shown:?}"); + // The object row's chip says the fills differ, like the Properties panel's "?" chip. + assert!(shown.iter().any(|t| t == "?"), "{shown:?}"); // Alike objects list their whole stack, without the note. run(&mut app, "paint.setFill", json!({"ids": ids, "color": "#ffff00"})); let shown = frame(&mut app); assert!(!shown.iter().any(|t| t == "Mixed Appearances") && shown.iter().any(|t| t == "Fill:"), "{shown:?}"); + assert!(!shown.iter().any(|t| t == "?"), "{shown:?}"); // A path and type. let t = run(&mut app, "text.create", json!({"x": 0, "y": 180, "text": "Hi"}))["id"].clone(); run(&mut app, "select.set", json!({"ids": [ids[0].clone(), t]})); - assert_eq!(frame(&mut app).first().map(String::as_str), Some("Mixed Objects")); + assert_eq!(frame(&mut app).iter().find(|t| *t != "?").map(String::as_str), Some("Mixed Objects")); }