From 64241978ec9e2814135382782e19bf9192eb1d02 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 22:50:22 -0800 Subject: [PATCH] Links: a nested row's tooltip names the symbol, graph design, pattern or mask holding it Two rows of the same file in different symbols showed the same name, thumbnail and status. The tooltip of a row outside the layer tree now adds the symbol, graph design or pattern whose art holds the image, and the object whose opacity mask holds it. --- crates/ui-egui/src/panels/links.rs | 33 ++++++++++++++++++++++-- crates/ui-egui/src/panels/tests_links.rs | 18 +++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/links.rs b/crates/ui-egui/src/panels/links.rs index af85690e9d..5c95e17bc7 100644 --- a/crates/ui-egui/src/panels/links.rs +++ b/crates/ui-egui/src/panels/links.rs @@ -344,7 +344,31 @@ fn badge(ui: &Ui, r: egui::Rect, status: &str) { } } -fn status_tip(r: &Value) -> String { +/// The tooltip of row `r`. A row outside the layer tree also names where it is stored +/// (`links.list`'s `owner`): the symbol, graph design or pattern whose art holds it, and the object +/// whose opacity mask holds it, by `mask` (that object's name) or else by its id. +pub(super) fn status_tip(r: &Value, mask: Option) -> String { + let tip = file_tip(r); + let owner = &r["owner"]; + let name = owner["name"].as_str().unwrap_or_default(); + let art = match owner["kind"].as_str() { + Some("symbol") => Some(tl!("Symbols")), + Some("graphDesign") => Some(tl!("Graph Design")), + Some("pattern") => Some(tl!("Pattern")), + _ => None, + }; + let tip = match art { + Some(kind) => format!("{tip}\n{kind}: {name}"), + None => tip, + }; + match owner["maskOf"].as_u64() { + Some(id) => format!("{tip}\n{}: {}", tl!("Opacity Mask"), mask.unwrap_or_else(|| id.to_string())), + None => tip, + } +} + +/// The file, status and notes part of [`status_tip`]. +fn file_tip(r: &Value) -> String { let path = r["found"].as_str().or(r["path"].as_str()).unwrap_or_default(); let tip = match r["status"].as_str().unwrap_or_default() { "missing" => crate::i18n::fmt(tl!("Missing: {path}"), &[("path", path)]), @@ -495,7 +519,12 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { egui::FontId::proportional(12.5), t.text, ); - let resp = resp.on_hover_text(status_tip(r)); + let mask = r["owner"]["maskOf"] + .as_u64() + .and_then(|m| doc.stored_node(NodeId(m))) + .map(|(n, _)| n) + .map(|n| super::layers::shown_name(app, n, crate::i18n::current())); + let resp = resp.on_hover_text(status_tip(r, mask)); if resp.double_clicked() { clicked = Some((id, egui::Modifiers::NONE, true)); } else if resp.clicked() { diff --git a/crates/ui-egui/src/panels/tests_links.rs b/crates/ui-egui/src/panels/tests_links.rs index be5e17e374..89dfba5f43 100644 --- a/crates/ui-egui/src/panels/tests_links.rs +++ b/crates/ui-egui/src/panels/tests_links.rs @@ -178,6 +178,24 @@ fn a_link_inside_a_symbol_is_listed_and_picked_in_the_panel() { assert!(selected(&app).is_empty(), "the image in the layers is no longer selected"); } +/// The tooltip of a row outside the layer tree says where the image is stored, so two rows of the +/// same file in different symbols or masks can be told apart. +#[test] +fn a_nested_rows_tooltip_names_what_holds_it() { + let tip = |owner: serde_json::Value, mask: Option<&str>| { + let row = json!({"id": 9, "name": "a.png", "status": "ok", "path": "/x/a.png", "owner": owner}); + super::links::status_tip(&row, mask.map(str::to_string)) + }; + assert_eq!(tip(json!({"kind": "symbol", "name": "Mark"}), None), "/x/a.png\nSymbols: Mark"); + assert_eq!(tip(json!({"kind": "pattern", "name": "Dots"}), None), "/x/a.png\nPattern: Dots"); + assert_eq!(tip(json!({"kind": "graphDesign", "name": "Bar"}), None), "/x/a.png\nGraph Design: Bar"); + assert_eq!(tip(json!({"kind": "layers", "maskOf": 7}), Some("")), "/x/a.png\nOpacity Mask: "); + assert_eq!(tip(json!({"kind": "symbol", "name": "Mark", "maskOf": 7}), None), "/x/a.png\nSymbols: Mark\nOpacity Mask: 7"); + // A row in the layers says only its file. + let row = json!({"id": 9, "name": "a.png", "status": "ok", "path": "/x/a.png"}); + assert_eq!(super::links::status_tip(&row, None), "/x/a.png"); +} + #[test] fn an_empty_document_shows_how_to_add_images() { let mut app = VectorcraftApp::new(Session::new(), Default::default());