mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 15:54:53 +00:00
Links: a mask row's tooltip finds its object without walking the stored art
The panel collects the objects whose opacity masks hold a listed image in the same pass over the stored art that collects the rows' objects, once per list rebuild. Each frame then finds a row's mask object by the layer tree's index or in that cache, instead of walking every stored root for objects inside symbols, graph designs and patterns.
This commit is contained in:
@@ -60,10 +60,18 @@ struct Cache {
|
||||
at: f64,
|
||||
list: Value,
|
||||
info: Option<(u64, Value)>,
|
||||
/// The objects of the rows outside the layer tree, by id, for their thumbnails.
|
||||
/// The objects outside the layer tree, by id: those of the rows, for their thumbnails, and
|
||||
/// those whose opacity masks hold a row's image, for the tooltip.
|
||||
nested: Vec<(u64, std::sync::Arc<vectorcraft_doc::Node>)>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
/// Object `id` outside the layer tree, as [`cached`] collected it.
|
||||
fn nested_node(&self, id: u64) -> Option<&std::sync::Arc<vectorcraft_doc::Node>> {
|
||||
self.nested.binary_search_by_key(&id, |(n, _)| *n).ok().and_then(|i| self.nested.get(i)).map(|(_, n)| n)
|
||||
}
|
||||
}
|
||||
|
||||
fn options(ctx: &egui::Context) -> Options {
|
||||
pstate(ctx, "links-options")
|
||||
}
|
||||
@@ -86,9 +94,16 @@ fn cached(app: &mut VectorcraftApp, ctx: &egui::Context, o: &Options) -> Option<
|
||||
if stale {
|
||||
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, not one per frame.
|
||||
let mut want: std::collections::HashSet<u64> =
|
||||
c.list["links"].as_array().into_iter().flatten().filter(|r| nested_row(r)).filter_map(|r| r["id"].as_u64()).collect();
|
||||
// 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"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|r| nested_row(r))
|
||||
.flat_map(|r| [r["id"].as_u64(), r["owner"]["maskOf"].as_u64()])
|
||||
.flatten()
|
||||
.collect();
|
||||
c.nested.clear();
|
||||
if !want.is_empty()
|
||||
&& let Some(st) = app.session.active()
|
||||
@@ -477,16 +492,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if o.thumb > 0.0 {
|
||||
let tr = egui::Rect::from_min_size(egui::pos2(x, rect.center().y - o.thumb / 2.0), vec2(o.thumb, o.thumb));
|
||||
ui.painter().rect_filled(tr, 0.0, egui::Color32::WHITE);
|
||||
let node = if nested_row(r) {
|
||||
cache
|
||||
.nested
|
||||
.binary_search_by_key(&id, |(n, _)| *n)
|
||||
.ok()
|
||||
.and_then(|i| cache.nested.get(i))
|
||||
.map(|(_, n)| std::sync::Arc::clone(n))
|
||||
} else {
|
||||
super::layers::node_arc(&doc, NodeId(id))
|
||||
};
|
||||
let node =
|
||||
if nested_row(r) { cache.nested_node(id).map(std::sync::Arc::clone) } else { super::layers::node_arc(&doc, NodeId(id)) };
|
||||
if let Some(n) = node {
|
||||
super::layers::real_thumb(ui, &thumbs, &n, tr);
|
||||
}
|
||||
@@ -519,10 +526,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
egui::FontId::proportional(12.5),
|
||||
t.text,
|
||||
);
|
||||
// The mask's object by the layer tree's index or from the cache: no walk of the
|
||||
// stored art each frame.
|
||||
let mask = r["owner"]["maskOf"]
|
||||
.as_u64()
|
||||
.and_then(|m| doc.stored_node(NodeId(m)))
|
||||
.map(|(n, _)| n)
|
||||
.and_then(|m| doc.node(NodeId(m)).or_else(|| cache.nested_node(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() {
|
||||
|
||||
@@ -196,6 +196,39 @@ fn a_nested_rows_tooltip_names_what_holds_it() {
|
||||
assert_eq!(super::links::status_tip(&row, None), "/x/a.png");
|
||||
}
|
||||
|
||||
/// The tooltip of an image in the opacity mask of an object inside a symbol names that object,
|
||||
/// which the panel finds among the objects it collected with the list.
|
||||
#[test]
|
||||
fn a_mask_rows_tooltip_names_its_object_inside_a_symbol() {
|
||||
let dir = Folder::new("mask-owner");
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
let rect = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 60, "height": 60})).unwrap()["id"].as_u64().unwrap();
|
||||
let mask = dir.file("mask.png", &png(20, 20));
|
||||
let mask = app.run("file.place", json!({"path": mask, "at": [40, 40]})).unwrap()["ids"][0].as_u64().unwrap();
|
||||
app.run("transparency.makeOpacityMask", json!({"ids": [rect, mask]})).unwrap();
|
||||
app.run("symbol.new", json!({"name": "Mark", "ids": [rect]})).unwrap();
|
||||
app.run("edit.clear", json!({"ids": selected(&app)})).unwrap();
|
||||
let list = app.run("links.list", json!({})).unwrap();
|
||||
let row = list["links"].as_array().unwrap().iter().find(|r| r["name"] == "mask.png").unwrap_or_else(|| panic!("{list}")).clone();
|
||||
assert_eq!(row["owner"]["kind"], "symbol", "{list}");
|
||||
let owner = vectorcraft_doc::NodeId(row["owner"]["maskOf"].as_u64().unwrap_or_else(|| panic!("{list}")));
|
||||
let doc = app.session.active().unwrap().doc.clone();
|
||||
assert!(doc.node(owner).is_none(), "the masked object is outside the layer tree");
|
||||
let name = super::layers::shown_name(&app, doc.stored_node(owner).unwrap().0, crate::i18n::current());
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let frame = |app: &mut VectorcraftApp, events: Vec<egui::Event>, time: f64| {
|
||||
super::tests_appearance::frame_raw(&ctx, app, egui::RawInput { events, time: Some(time), ..Default::default() }, super::links::show)
|
||||
};
|
||||
let at = super::tests_appearance::text_rect(&frame(&mut app, vec![], 1.0), "mask.png").center();
|
||||
frame(&mut app, vec![egui::Event::PointerMoved(at)], 2.0);
|
||||
frame(&mut app, vec![], 4.0);
|
||||
let text: Vec<String> = frame(&mut app, vec![], 4.1).into_iter().map(|(t, _)| t).collect();
|
||||
let want = format!("Opacity Mask: {name}");
|
||||
assert!(text.iter().any(|t| t.contains(&want)), "{want} in {text:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_document_shows_how_to_add_images() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
|
||||
Reference in New Issue
Block a user