Merge pull request #7689 from dotCooCoo/fix/links-brush-art-thumbnails

Links: rows of images in stored brush art show their thumbnails
This commit is contained in:
BFlat
2026-11-15 06:17:31 +07:00
committed by GitHub
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
@@ -238,6 +238,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());