mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 02:07:09 -06:00
Illustrator's View menu has Hide/Show Template (Shift+Ctrl+W) to leave template layers off the screen while tracing. VectorCraft drew them (dimmed) whatever the view, with no way to hide them short of disabling the template option on each layer and losing the dimming. - `view.template` toggles `ViewFlags::templates` (default on, kept across settings files); the View menu lists Hide Template after Lock Slices, and the label flips to Show Template. - The canvas renders with the new `RenderOptions::hide_templates`, which leaves template layers out and nothing else; `skip_templates` (exports and thumbnails) also drops guide paths, so it is not reused. - The fifteen menu catalogs translate both labels; `DYNAMIC_LABELS` lists them. Tests: render `hide_template_leaves_only_the_template_layers_out` (the template layer's square and image go, a guide path on another layer stays); canvas `hide_template_leaves_template_layers_off_the_canvas` (real egui frames: painted pixels, none after Hide Template, the same after Show Template, each re-rendering once); the dynamic-label and catalog tests cover the new pair. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
169 lines
7.6 KiB
Rust
169 lines
7.6 KiB
Rust
//! Layer Options on screen: Preview off draws a layer in outline, Dim Images fades its images;
|
||
//! exports (which leave templates out) draw both as they are.
|
||
|
||
use vectorcraft_color::{Color, Paint};
|
||
use vectorcraft_doc::{Appearance, ImageBlob, ImageObject, Node, NodeId, NodeKind};
|
||
use vectorcraft_geom::shapes;
|
||
|
||
use super::*;
|
||
|
||
/// A red square (10..40) and a 30×30 red image at (50, 10), on one layer set up by `layer`.
|
||
fn doc(layer: impl FnOnce(&mut NodeKind)) -> Document {
|
||
let mut d = Document::new(100.0, 100.0);
|
||
let img = image::RgbaImage::from_pixel(30, 30, image::Rgba([255, 0, 0, 255]));
|
||
let mut png = vec![];
|
||
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
|
||
d.images.insert("red".into(), ImageBlob::new("image/png", png));
|
||
let l = d.layers[0].id;
|
||
let red = Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0);
|
||
let sq = Node::path(d.alloc_id(), shapes::rectangle(Rect::new(10.0, 10.0, 40.0, 40.0)), red);
|
||
d.insert(Some(l), 0, sq).unwrap();
|
||
let mut im = Node::new(
|
||
NodeId(0),
|
||
NodeKind::Image(Box::new(ImageObject {
|
||
key: "red".into(),
|
||
width: 30,
|
||
height: 30,
|
||
xf: Affine::translate((50.0, 10.0)),
|
||
link: None,
|
||
placement: Default::default(),
|
||
})),
|
||
);
|
||
im.id = d.alloc_id();
|
||
d.insert(Some(l), 9, im).unwrap();
|
||
layer(&mut d.node_mut(l).unwrap().kind);
|
||
d
|
||
}
|
||
|
||
fn render(d: &Document, export: bool) -> Rendered {
|
||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), skip_templates: export, ..Default::default() };
|
||
Renderer::new().render(d, 100, 100, Affine::IDENTITY, &opts)
|
||
}
|
||
|
||
#[test]
|
||
fn preview_off_draws_the_layer_in_outline_on_screen_only() {
|
||
let d = doc(|k| {
|
||
if let NodeKind::Layer { preview, .. } = k {
|
||
*preview = false;
|
||
}
|
||
});
|
||
assert_eq!(render(&d, false).pixel(25, 25), [255, 255, 255, 255], "no fill in outline");
|
||
assert_eq!(render(&d, true).pixel(25, 25), [255, 0, 0, 255], "exports draw it as it is");
|
||
}
|
||
|
||
#[test]
|
||
fn dim_images_fades_only_the_images() {
|
||
let plain = render(&doc(|_| {}), false);
|
||
assert_eq!(plain.pixel(65, 25), [255, 0, 0, 255]);
|
||
let d = doc(|k| {
|
||
if let NodeKind::Layer { dim_images, .. } = k {
|
||
*dim_images = Some(50);
|
||
}
|
||
});
|
||
let img = render(&d, false);
|
||
let [r, g, b, _] = img.pixel(65, 25);
|
||
assert!(r == 255 && (120..=135).contains(&g) && g == b, "half way to white: {:?}", img.pixel(65, 25));
|
||
assert_eq!(img.pixel(25, 25), [255, 0, 0, 255], "vector art stays");
|
||
assert_eq!(render(&d, true).pixel(65, 25), [255, 0, 0, 255], "exports are not dimmed");
|
||
}
|
||
|
||
/// #1683: a template layer dims its images by its Dim Images percentage and nothing else: with
|
||
/// the option off they show at full strength, and the layer's paths are never faded.
|
||
#[test]
|
||
fn a_template_layer_dims_its_images_by_dim_images() {
|
||
let template = |dim: Option<u8>| {
|
||
let d = doc(|k| {
|
||
if let NodeKind::Layer { template, dim_images, .. } = k {
|
||
(*template, *dim_images) = (true, dim);
|
||
}
|
||
});
|
||
let img = render(&d, false);
|
||
(img.pixel(65, 25), img.pixel(25, 25))
|
||
};
|
||
let red = [255, 0, 0, 255];
|
||
assert_eq!(template(None), (red, red), "Dim Images off: as it is");
|
||
let (image, path) = template(Some(50));
|
||
assert!(image[0] == 255 && (120..=135).contains(&image[1]) && image[1] == image[2], "half way to white: {image:?}");
|
||
assert_eq!(path, red, "vector art stays");
|
||
let (image, _) = template(Some(20));
|
||
assert!((198..=210).contains(&image[1]) && image[1] == image[2], "a fifth of its strength: {image:?}");
|
||
assert_eq!(template(Some(100)), (red, red));
|
||
}
|
||
|
||
/// In Outline view a template layer still draws in Preview, dimmed, so its picture can be traced;
|
||
/// the layer above it draws in outline (#1486).
|
||
#[test]
|
||
fn a_template_layer_stays_in_preview_in_outline_view() {
|
||
let mut d = doc(|k| {
|
||
if let NodeKind::Layer { template, preview, dim_images, .. } = k {
|
||
// As Layer Options makes a template: its images dimmed to 50 %.
|
||
(*template, *dim_images) = (true, Some(50));
|
||
// Preview off doesn't put a template in outline either.
|
||
*preview = false;
|
||
}
|
||
});
|
||
let top = Node::layer(d.alloc_id(), "Drawing", vectorcraft_doc::LayerColor::Preset(0));
|
||
let top_id = d.insert(None, 9, top).unwrap();
|
||
let red = Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0);
|
||
let sq = Node::path(d.alloc_id(), shapes::rectangle(Rect::new(10.0, 60.0, 40.0, 90.0)), red);
|
||
d.insert(Some(top_id), 0, sq).unwrap();
|
||
let draw = |outline: bool| {
|
||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), outline, ..Default::default() };
|
||
Renderer::new().render(&d, 100, 100, Affine::IDENTITY, &opts)
|
||
};
|
||
let (preview, outline) = (draw(false), draw(true));
|
||
let [r, g, b, _] = outline.pixel(65, 25);
|
||
assert!(r == 255 && (120..=135).contains(&g) && g == b, "the template's image, dimmed: {:?}", outline.pixel(65, 25));
|
||
assert_eq!(outline.pixel(65, 25), preview.pixel(65, 25), "as in Preview");
|
||
assert_eq!(outline.pixel(25, 75), [255, 255, 255, 255], "the drawing layer has no fill in outline");
|
||
assert_eq!(preview.pixel(25, 75), [255, 0, 0, 255]);
|
||
}
|
||
|
||
/// View › Hide Template (#2000) leaves template layers out of the screen view and nothing else:
|
||
/// a guide path on another layer still draws, unlike in an export.
|
||
#[test]
|
||
fn hide_template_leaves_only_the_template_layers_out() {
|
||
let mut d = doc(|k| {
|
||
if let NodeKind::Layer { template, .. } = k {
|
||
*template = true;
|
||
}
|
||
});
|
||
let top = d.add_layer(Some("Guides"));
|
||
let mut g = Node::path(d.alloc_id(), shapes::rectangle(Rect::new(70.0, 70.0, 90.0, 90.0)), Appearance::default());
|
||
if let NodeKind::Path { guide, .. } = &mut g.kind {
|
||
*guide = true;
|
||
}
|
||
d.insert(Some(top), 0, g).unwrap();
|
||
let render = |hide: bool| {
|
||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), hide_templates: hide, ..Default::default() };
|
||
Renderer::new().render(&d, 100, 100, Affine::IDENTITY, &opts)
|
||
};
|
||
let guide_drawn = |r: &Rendered| (60..100).flat_map(|y| (60..100).map(move |x| (x, y))).any(|(x, y)| r.pixel(x, y) != [255, 255, 255, 255]);
|
||
let shown = render(false);
|
||
assert_ne!(shown.pixel(25, 25), [255, 255, 255, 255], "the template layer's square shows (dimmed)");
|
||
assert!(guide_drawn(&shown));
|
||
let hidden = render(true);
|
||
assert_eq!(hidden.pixel(25, 25), [255, 255, 255, 255], "Hide Template: the template layer is left out");
|
||
assert_eq!(hidden.pixel(65, 25), [255, 255, 255, 255], "its image too");
|
||
assert!(guide_drawn(&hidden), "the guide on the other layer still draws");
|
||
}
|
||
|
||
/// A guide path draws on screen only: exports leave it out, as they leave templates out (and as
|
||
/// the SVG, PSD and PDF exports and printing already do).
|
||
#[test]
|
||
fn guides_draw_on_screen_only() {
|
||
let mut d = Document::new(100.0, 100.0);
|
||
let l = d.layers[0].id;
|
||
let mut g = Node::path(d.alloc_id(), shapes::rectangle(Rect::new(70.0, 70.0, 90.0, 90.0)), Appearance::default());
|
||
if let NodeKind::Path { guide, .. } = &mut g.kind {
|
||
*guide = true;
|
||
}
|
||
d.insert(Some(l), 0, g).unwrap();
|
||
let drawn = |export: bool| {
|
||
let r = render(&d, export);
|
||
(60..100).flat_map(|y| (60..100).map(move |x| (x, y))).filter(|&(x, y)| r.pixel(x, y) != [255, 255, 255, 255]).count()
|
||
};
|
||
assert!(drawn(false) > 0, "on screen the guide shows");
|
||
assert_eq!(drawn(true), 0, "exports leave it out");
|
||
}
|