Browse / github.com/storytold/vectorcraft / patch
Appearance: a mixed selection's object chip shows "?" when the fills differ
Appearance: a mixed selection's object chip shows "?" when the fills differ Follow-up to #4301 (#4174), from the review there. When the selected objects' fills differ, the Appearance panel's object row drew the red-slash None swatch, which reads as "no fill". It now draws the same "?" chip the Control bar and Properties panel use for differing paints (`widgets::proxy_chip`). Test: `a_mixed_selection_lists_what_its_objects_share` checks the "?" appears for differing fills and disappears once the fills match. `cargo xtask ci`: fmt, clippy, pdf-deps, assets, brands, cursors, layers and wasm pass; tests pass except `constrain_angle_turns_the_base_orientation_of_polygon
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- ham9zah (imported from pull request #4316, open)
- Base
443b31b13df3a7ab611c2fe1b95e73898ed74fdd- Size
- +12 −12 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
5253d6a395f7d09217f899e0
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/panels/appearance.rs crates/ui-egui/src/panels/appearance/tests.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.@@ -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")); }