M7: a slice's export preview shows the page content under it

A slice paints nothing itself, so thumbnail() rendered an empty pixmap of the slice's own box: expanding Preview for a slice showed only the transparency checkerboard while exporting the same slice wrote the artwork correctly.

Give the preview the export's slice rule (drawn_layers + export_area in cmd/file.rs): draw every top-level layer of the slice's page over its world bounds. Regression tests cover a slice over artwork, a slice with nothing under it, and a plain layer.

Signed-off-by: Ahmed Mahmoud <ahmedmhmouad41@gmail.com>
This commit is contained in:
ahmed-226
2026-10-14 10:37:23 +03:00
committed by Ahmed Mahmoud
parent bcedfeba10
commit dde193cbf4
3 changed files with 70 additions and 4 deletions
+16 -4
View File
@@ -190,15 +190,27 @@ fn same_layers(a: &Document, b: &Document, id: NodeId) -> bool {
}
pub(crate) fn thumbnail(ctx: &egui::Context, doc: &Document, id: NodeId, px: f32) -> Option<egui::TextureHandle> {
let b = figcraft_render::export_bounds(doc, id)?;
let pix = thumbnail_pixmap(doc, id, px)?;
let img = crate::render_worker::screen_image(&pix, doc.color_profile);
Some(ctx.load_texture(format!("asset-thumb-{}", id.0), img, egui::TextureOptions::LINEAR))
}
/// The pixels a thumbnail shows. A slice paints nothing itself, so it shows the page content
/// under it, exactly as exporting one does (`figcraft_engine::cmd::file::drawn_layers`): without
/// this a slice's preview is a blank checkerboard next to a correct export.
pub(crate) fn thumbnail_pixmap(doc: &Document, id: NodeId, px: f32) -> Option<figcraft_render::Pixmap> {
let slice = doc.node(id).is_some_and(|n| matches!(n.kind, NodeKind::Slice));
let b = if slice { doc.world_bounds(id)? } else { figcraft_render::export_bounds(doc, id)? };
let side = b.width().max(b.height());
if !(side.is_finite() && side > 0.0) {
return None;
}
let scale = (f64::from(px) / side).min(2.0);
let pix = figcraft_render::render_node(doc, id, scale)?;
let img = crate::render_worker::screen_image(&pix, doc.color_profile);
Some(ctx.load_texture(format!("asset-thumb-{}", id.0), img, egui::TextureOptions::LINEAR))
if slice {
let layers = doc.page_of(id).and_then(|p| doc.node(p)).map(|p| p.children.clone()).unwrap_or_default();
return figcraft_render::render_area(doc, &layers, b, scale);
}
figcraft_render::render_node(doc, id, scale)
}
fn matches(name: &str, q: &str) -> bool {
@@ -0,0 +1,52 @@
//! The Export section's Preview row: a slice paints nothing itself, so its preview must show the
//! page content under it — the same thing exporting the slice writes — and not a blank
//! transparency checkerboard next to a correct export.
use figcraft_doc::{Document, NodeId};
use figcraft_engine::Session;
use serde_json::{Value, json};
use crate::assets_panel::thumbnail_pixmap;
/// Run `commands`, returning the document and the id each one made.
fn doc_after(commands: &[(&str, Value)]) -> (Document, Vec<NodeId>) {
let mut s = Session::new();
let ids = commands.iter().map(|(cmd, params)| NodeId(s.execute(cmd, params).unwrap()["id"].as_u64().unwrap())).collect();
(s.doc().unwrap().clone(), ids)
}
/// Whether the pixmap paints anything (any pixel not fully transparent).
fn paints(pix: &figcraft_render::Pixmap) -> bool {
figcraft_render::to_rgba8(pix).as_chunks::<4>().0.iter().any(|p| p.get(3).copied().unwrap_or(0) > 0)
}
#[test]
fn a_slice_previews_the_content_under_it() {
let (doc, ids) = doc_after(&[
("shape.rectangle", json!({"x": 0, "y": 0, "width": 200, "height": 100})),
("node.create", json!({"type": "slice", "x": 0, "y": 0, "width": 200, "height": 100})),
]);
let slice = ids[1];
let pix = thumbnail_pixmap(&doc, slice, 200.0).expect("the slice previews");
assert_eq!([pix.width(), pix.height()], [200, 100], "the slice's area, not an empty node");
assert!(paints(&pix), "the rectangle under the slice shows");
}
#[test]
fn a_slice_with_nothing_under_it_is_blank() {
let (doc, ids) = doc_after(&[("node.create", json!({"type": "slice", "x": 0, "y": 0, "width": 200, "height": 100}))]);
let pix = thumbnail_pixmap(&doc, ids[0], 200.0).expect("the preview still has the slice's area");
assert_eq!([pix.width(), pix.height()], [200, 100]);
assert!(!paints(&pix), "nothing under it stays transparent");
}
#[test]
fn other_layers_preview_themselves() {
let (doc, ids) = doc_after(&[
("shape.rectangle", json!({"x": 0, "y": 0, "width": 200, "height": 100})),
("node.create", json!({"type": "slice", "x": 0, "y": 0, "width": 200, "height": 100})),
]);
let pix = thumbnail_pixmap(&doc, ids[0], 200.0).expect("the rectangle previews");
assert_eq!([pix.width(), pix.height()], [200, 100], "the layer's own area");
assert!(paints(&pix));
}
+2
View File
@@ -78,6 +78,8 @@ mod cursors_tests;
#[cfg(test)]
mod dev_mode_tests;
#[cfg(test)]
mod export_preview_tests;
#[cfg(test)]
mod field_tests;
#[cfg(test)]
mod frame_label_tests;