Links: rows of images in stored brush art show their thumbnails

The panel collected the objects for its nested rows from the document
as it is between commands, where the art of the stored brushes is not
set, so rows whose owner is a brush found no object and drew a blank
thumbnail. When an id is not found otherwise, the panel now sets that
art on a copy of the document, as links.list does, and collects the
objects from it. A recheck of the files at the same revision keeps the
objects collected before.
This commit is contained in:
dotCooCoo
2026-11-14 13:56:22 -08:00
parent 107078ad10
commit aaae3fe378
2 changed files with 73 additions and 12 deletions
+51 -12
View File
@@ -92,28 +92,29 @@ fn cached(app: &mut VectorcraftApp, ctx: &egui::Context, o: &Options) -> Option<
let mut c: Cache = pstate(ctx, "links-cache");
let stale = c.key != key || now - c.at > RECHECK;
if stale {
let same_doc = c.key == key;
c.list = app.session.execute("links.list", &json!({ "show": o.show, "sort": o.sort, "dcsTransparency": o.dcs })).ok()?;
(c.key, c.at) = (key, now);
// One pass over the stored art for the rows outside the layer tree and the objects whose
// opacity masks hold them, not one per frame.
let mut want: std::collections::HashSet<u64> = c.list["links"]
// opacity masks hold them, not one per frame. An object in the layer tree is looked up by
// its index when drawn.
let doc = app.session.active().map(|st| &st.doc);
let want: std::collections::HashSet<u64> = c.list["links"]
.as_array()
.into_iter()
.flatten()
.filter(|r| nested_row(r))
.flat_map(|r| [r["id"].as_u64(), r["owner"]["maskOf"].as_u64()])
.flatten()
.filter(|id| doc.is_some_and(|d| d.node(NodeId(*id)).is_none()))
.collect();
c.nested.clear();
if !want.is_empty()
&& let Some(st) = app.session.active()
{
st.doc.visit_stored_arcs(|n, place| {
if !place.in_layers() && want.remove(&n.id.0) {
c.nested.push((n.id.0, std::sync::Arc::clone(n)));
}
});
c.nested.sort_by_key(|(id, _)| *id);
// The same revision keeps the objects collected for it, so their thumbnails are not drawn
// again each time the files are looked at.
if !(same_doc && want.iter().all(|id| c.nested_node(*id).is_some())) {
c.nested = match doc {
Some(d) if !want.is_empty() => stored_objects(d, want),
_ => vec![],
};
}
}
let want = first.filter(|_| o.info);
@@ -126,6 +127,44 @@ fn cached(app: &mut VectorcraftApp, ctx: &egui::Context, o: &Options) -> Option<
Some(c)
}
/// The objects of `doc` outside the layer tree whose ids are in `want`, sorted by id. The art of
/// the stored brushes is set on `doc` only while a command uses it ([`Document::mounted_art`]), so
/// when an id is not found otherwise that art is set on a copy of `doc`, which gives its objects
/// the ids `links.list` reports for them.
///
/// [`Document::mounted_art`]: vectorcraft_doc::Document::mounted_art
pub(super) fn stored_objects(
doc: &vectorcraft_doc::Document,
mut want: std::collections::HashSet<u64>,
) -> Vec<(u64, std::sync::Arc<vectorcraft_doc::Node>)> {
let mut out = vec![];
collect_stored(doc, &mut want, &mut out);
if !want.is_empty() && doc.mounted_art.is_empty() {
let art = vectorcraft_brush::stored_art::StoredArt::read(doc);
if !art.is_empty() {
let mut d = doc.clone();
art.mount(&mut d);
collect_stored(&d, &mut want, &mut out);
}
}
out.sort_by_key(|(id, _)| *id);
out
}
/// Adds the objects of `d` outside the layer tree whose ids are in `want` to `out`, taking their
/// ids out of `want`.
fn collect_stored(
d: &vectorcraft_doc::Document,
want: &mut std::collections::HashSet<u64>,
out: &mut Vec<(u64, std::sync::Arc<vectorcraft_doc::Node>)>,
) {
d.visit_stored_arcs(|n, place| {
if !place.in_layers() && want.remove(&n.id.0) {
out.push((n.id.0, std::sync::Arc::clone(n)));
}
});
}
/// The ids of the selected image objects and placed documents.
fn selected_images(app: &VectorcraftApp) -> Vec<u64> {
let Some(st) = app.session.active() else { return vec![] };
+22
View File
@@ -230,6 +230,28 @@ fn a_mask_rows_tooltip_names_its_object_inside_a_symbol() {
assert!(text.iter().any(|t| t.contains(&want)), "{want} in {text:?}");
}
/// The row of an image in a stored brush's art finds its object for the thumbnail, though that art
/// is set on the document only while a command uses it.
#[test]
fn a_row_in_a_stored_brushs_art_finds_its_object() {
let dir = Folder::new("brush-art");
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
let leaf = dir.file("leaf.png", &png(30, 20));
let id = app.run("file.place", json!({"path": leaf, "at": [100, 100]})).unwrap()["ids"][0].as_u64().unwrap();
app.run("brush.new", json!({"type": "scatter", "name": "Leaf", "ids": [id]})).unwrap();
app.run("edit.clear", json!({"ids": [id]})).unwrap();
let list = app.run("links.list", json!({})).unwrap();
let row = list["links"].as_array().unwrap().iter().find(|r| r["name"] == "leaf.png").unwrap_or_else(|| panic!("{list}")).clone();
assert_eq!(row["owner"]["kind"], "brush", "{list}");
let row = row["id"].as_u64().unwrap();
let doc = &app.session.active().unwrap().doc;
assert!(doc.mounted_art.is_empty(), "the brush's art is not set on the document");
let found = super::links::stored_objects(doc, [row].into());
assert_eq!(found.iter().map(|(id, _)| *id).collect::<Vec<_>>(), [row]);
assert!(matches!(found[0].1.kind, vectorcraft_doc::NodeKind::Image(_)), "{:?}", found[0].1.kind);
}
#[test]
fn an_empty_document_shows_how_to_add_images() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());