mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 11:47:21 -06:00
M3.72: Tile Edge Color dialog and Show Swatch Bounds in pattern editing
Object > Pattern > Tile Edge Color... picks the colour of the tile edge
(a layer-colour preset or a custom colour), stored in the preference
patternTileEdgeColor and passed to the renderer. Pattern Options gains
Show Swatch Bounds: a dashed outline of the part of the tiling the swatch
repeats (the period of brick and hex tilings).
Commands: prefs.set {patternTileEdgeColor}, pattern.options
{showSwatchBounds}, ui.tileEdgeColor. Model: PatternDef.show_swatch_bounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d8b942c43d
commit
b36fd67bf4
@@ -79,6 +79,9 @@ pub struct RenderOptions {
|
||||
pub trim: bool,
|
||||
/// Leave template layers out (exports and thumbnails: templates are guides, not artwork).
|
||||
pub skip_templates: bool,
|
||||
/// Pattern editing mode's tile edge and swatch bounds colour (Object → Pattern → Tile Edge
|
||||
/// Color), RGB.
|
||||
pub tile_edge: [u8; 3],
|
||||
}
|
||||
|
||||
impl Default for RenderOptions {
|
||||
@@ -93,6 +96,7 @@ impl Default for RenderOptions {
|
||||
overprint_preview: false,
|
||||
trim: false,
|
||||
skip_templates: false,
|
||||
tile_edge: vectorcraft_doc::LAYER_COLORS[0].1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1170,3 +1174,5 @@ mod tests_isolation;
|
||||
mod tests_knockout;
|
||||
#[cfg(test)]
|
||||
mod tests_objectfx;
|
||||
#[cfg(test)]
|
||||
mod tests_tileedge;
|
||||
|
||||
@@ -194,9 +194,17 @@ impl Renderer {
|
||||
});
|
||||
}
|
||||
self.draw_arc(ctx, f, layer);
|
||||
let [r, g, b] = f.opts.tile_edge;
|
||||
if def.show_tile_edge {
|
||||
let bp = def.tile.to_path(0.1);
|
||||
self.hairline(ctx, f, &bp, [0x2d, 0x8c, 0xeb, 255]);
|
||||
self.hairline(ctx, f, &def.tile.to_path(0.1), [r, g, b, 255]);
|
||||
}
|
||||
if def.show_swatch_bounds {
|
||||
let mut bounds = def.swatch_bounds().to_path(0.1);
|
||||
bounds.apply_affine(f.view);
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.set_stroke(vello_cpu::kurbo::Stroke::new(1.0).with_dashes(0.0, [4.0, 3.0]));
|
||||
ctx.set_paint(peniko::Color::from_rgba8(r, g, b, 255));
|
||||
ctx.stroke_path(&bounds);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
//! Pattern editing mode: the tile edge in the Tile Edge Color, and Show Swatch Bounds.
|
||||
|
||||
use super::*;
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::pattern::{PatternDef, PatternEdit, TileType};
|
||||
use vectorcraft_doc::{Appearance, Node};
|
||||
use vectorcraft_geom::shapes;
|
||||
|
||||
/// A document editing a 20×20 pattern tile of type `tt` (a red 10×10 square in its corner, no
|
||||
/// preview copies), its tile edge and swatch bounds shown as asked.
|
||||
fn editing(tt: TileType, edge: bool, bounds: bool) -> Document {
|
||||
let mut d = Document::new(200.0, 200.0);
|
||||
let id = d.alloc_id();
|
||||
let red = Node::path(
|
||||
id,
|
||||
shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)),
|
||||
Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0),
|
||||
);
|
||||
let mut def = PatternDef::new("Checks", vec![Arc::new(red.clone())]);
|
||||
def.tile = Rect::new(0.0, 0.0, 20.0, 20.0);
|
||||
(def.tile_type, def.copies, def.show_tile_edge, def.show_swatch_bounds) = (tt, 1, edge, bounds);
|
||||
d.patterns.push(def);
|
||||
let layer = d.add_layer(Some("Pattern Editing Mode"));
|
||||
d.insert(Some(layer), 0, red).unwrap();
|
||||
d.pattern_edit = Some(PatternEdit { pattern: "Checks".into(), layer, original: None });
|
||||
d
|
||||
}
|
||||
|
||||
/// `d` at 1:1 with the tile's corner at the centre of pixel (50, 50), on white.
|
||||
fn render(d: &Document, tile_edge: Option<[u8; 3]>) -> Rendered {
|
||||
let opts = RenderOptions { background: Some([255; 4]), tile_edge: tile_edge.unwrap_or(RenderOptions::default().tile_edge), ..Default::default() };
|
||||
Renderer::new().render(d, 120, 120, Affine::translate((50.5, 50.5)), &opts)
|
||||
}
|
||||
|
||||
fn is_white(p: [u8; 4]) -> bool {
|
||||
p[0] > 245 && p[1] > 245 && p[2] > 245
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_tile_edge_takes_the_tile_edge_color() {
|
||||
let green = render(&editing(TileType::Grid, true, false), Some([0, 200, 0]));
|
||||
// The left edge below the red square, and the bottom edge.
|
||||
for (x, y) in [(50, 65), (60, 70), (70, 55)] {
|
||||
let p = green.pixel(x, y);
|
||||
assert!(p[1] > 150 && p[0] < 100 && p[2] < 100, "({x},{y}) {p:?}");
|
||||
}
|
||||
assert!(is_white(green.pixel(65, 65)), "inside the tile");
|
||||
// The default is the first layer colour (light blue).
|
||||
let [r, g, b] = RenderOptions::default().tile_edge;
|
||||
let p = render(&editing(TileType::Grid, true, false), None).pixel(50, 65);
|
||||
assert!(p[0].abs_diff(r) < 30 && p[1].abs_diff(g) < 30 && p[2].abs_diff(b) < 30, "{p:?}");
|
||||
// Hidden: nothing there.
|
||||
assert!(is_white(render(&editing(TileType::Grid, false, false), Some([0, 200, 0])).pixel(50, 65)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swatch_bounds_outline_the_period_dashed() {
|
||||
// Brick by row repeats every two rows: the swatch is 20×40 from the tile's corner.
|
||||
let tt = TileType::BrickByRow { offset: 0.5 };
|
||||
let on = render(&editing(tt, false, true), Some([0, 200, 0]));
|
||||
let green = |p: [u8; 4]| p[1] > 150 && p[0] < 100 && p[2] < 100;
|
||||
let bottom: Vec<bool> = (51..70).map(|x| green(on.pixel(x, 90))).collect();
|
||||
assert!(bottom.iter().any(|g| *g) && bottom.iter().any(|g| !*g), "dashed: {bottom:?}");
|
||||
assert!((51..90).any(|y| green(on.pixel(70, y))), "right side");
|
||||
assert!(!(51..70).any(|x| green(on.pixel(x, 70))), "the tile edge itself stays hidden");
|
||||
// Off: no outline.
|
||||
let off = render(&editing(tt, false, false), Some([0, 200, 0]));
|
||||
assert!((51..70).all(|x| is_white(off.pixel(x, 90))));
|
||||
}
|
||||
Reference in New Issue
Block a user