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 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-05 01:50:25 +07:00
1 parent d8b942c43d
commit b36fd67bf4
15 files changed
+343 -5

No files matched your search

+10
View File
@@ -182,6 +182,10 @@ pub struct PatternDef {
pub dim_copies: f32,
#[serde(default = "yes")]
pub show_tile_edge: bool,
/// Show Swatch Bounds: pattern editing mode outlines the part of the tiling the swatch repeats
/// (the tile, or the period of a brick or hex tiling), dashed.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub show_swatch_bounds: bool,
}
impl PatternDef {
@@ -200,6 +204,7 @@ impl PatternDef {
copies: 5,
dim_copies: 70.0,
show_tile_edge: true,
show_swatch_bounds: false,
}
}
pub fn width(&self) -> f64 {
@@ -226,6 +231,11 @@ impl PatternDef {
pub fn period(&self) -> (f64, f64) {
tile_period(self.tile_type, self.width(), self.height())
}
/// The swatch bounds (Show Swatch Bounds): the period of the tiling from the tile's origin.
pub fn swatch_bounds(&self) -> Rect {
let (w, h) = self.period();
Rect::from_origin_size(self.tile.origin(), (w, h))
}
/// Maps the art's coordinates into pattern space for instance offset `o`.
pub fn instance_xf(&self, o: Vec2) -> Affine {
Affine::translate(o - self.tile.origin().to_vec2())
+5 -1
View File
@@ -65,7 +65,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Pattern Options",
["Window", "Pattern Options"],
None,
"{name? (default: the pattern being edited), newName?, tileType?, brickOffset?, width?, height?, hSpacing?, vSpacing?, sizeTileToArt?: bool, overlap?: {h: left|right, v: top|bottom}, copies?: 3|5|7|9, dimCopies?: %, showTileEdge?: bool}",
"{name? (default: the pattern being edited), newName?, tileType?, brickOffset?, width?, height?, hSpacing?, vSpacing?, sizeTileToArt?: bool, overlap?: {h: left|right, v: top|bottom}, copies?: 3|5|7|9, dimCopies?: %, showTileEdge?: bool, showSwatchBounds?: bool (outline the part of the tiling the swatch repeats, dashed)}",
has_doc,
pattern_options
),
@@ -457,6 +457,9 @@ fn pattern_options(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(v) = p.get("showTileEdge").and_then(Value::as_bool) {
def.show_tile_edge = v;
}
if let Some(v) = p.get("showSwatchBounds").and_then(Value::as_bool) {
def.show_swatch_bounds = v;
}
if def.size_tile_to_art {
let saved = std::mem::take(&mut def.art);
def.art = live_art.clone().unwrap_or_else(|| saved.clone());
@@ -574,6 +577,7 @@ fn pattern_list(s: &mut Session, _: &Value) -> Result<Value> {
"copies": p.copies,
"dimCopies": p.dim_copies,
"showTileEdge": p.show_tile_edge,
"showSwatchBounds": p.show_swatch_bounds,
"artCount": p.art.len(),
})
})
+7
View File
@@ -292,6 +292,8 @@ pub const PREF_SPECS: &[PrefSpec] = &[
p!("touchGestures", "Devices", "", "Enable Touch Gestures", bool),
// Graphic Styles panel menu
p!("overrideCharColor", "Type", "Graphic Styles", "Override Character Color", bool),
// Object → Pattern → Tile Edge Color
p!("patternTileEdgeColor", "Guides & Grid", "Pattern Editing", "Tile Edge Color", color),
];
pub fn spec(key: &str) -> Option<&'static PrefSpec> {
@@ -377,6 +379,7 @@ impl Session {
pub fn apply_prefs(&mut self, p: Prefs) {
let grid_changed = p.gridline_every != self.prefs.gridline_every || p.grid_subdivisions != self.prefs.grid_subdivisions;
let history_changed = p.history_states != self.prefs.history_states;
let tile_edge_changed = p.pattern_tile_edge_color != self.prefs.pattern_tile_edge_color;
self.prefs = p;
vectorcraft_render::set_default_threads(u16::try_from(self.prefs.render_threads).ok());
for st in &mut self.docs {
@@ -385,6 +388,10 @@ impl Session {
let over = st.history.undo.len().saturating_sub(st.history.limit);
st.history.undo.drain(..over);
}
// Documents in pattern editing mode redraw their tile edge.
if tile_edge_changed && st.doc.pattern_edit.is_some() {
st.revision += 1;
}
if grid_changed {
let d = std::sync::Arc::make_mut(&mut st.doc);
d.grid.spacing = self.prefs.gridline_every;
+10
View File
@@ -310,6 +310,10 @@ pub struct Prefs {
/// Override Character Color: a graphic style applied to type replaces its characters' fill
/// and stroke with the style's fills and strokes.
pub override_char_color: bool,
// Pattern editing
/// Object → Pattern → Tile Edge Color (`#rrggbb`): the tile edge and swatch bounds in pattern
/// editing mode.
pub pattern_tile_edge_color: String,
}
impl Default for Prefs {
@@ -423,6 +427,10 @@ impl Default for Prefs {
touch_workspace: true,
touch_gestures: true,
override_char_color: true,
pattern_tile_edge_color: {
let [r, g, b] = vectorcraft_doc::LAYER_COLORS[0].1;
Color::rgb8(r, g, b).to_hex()
},
}
}
}
@@ -828,4 +836,6 @@ mod tests_swatchlib;
#[cfg(test)]
mod tests_textedit;
#[cfg(test)]
mod tests_tileedge;
#[cfg(test)]
mod tests_xform;
+63
View File
@@ -0,0 +1,63 @@
//! Pattern editing: the Tile Edge Color preference and Pattern Options' Show Swatch Bounds.
use serde_json::{Value, json};
use vectorcraft_doc::pattern::PatternDef;
use super::*;
fn run(s: &mut Session, id: &str, p: Value) -> Value {
s.execute(id, &p).unwrap_or_else(|e| panic!("{id} {p}: {e}"))
}
/// A session editing pattern "Dots" made from a 10×10 rectangle.
fn editing() -> Session {
let mut s = Session::new();
run(&mut s, "file.new", json!({"width": 200, "height": 200}));
run(&mut s, "shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10}));
run(&mut s, "object.pattern.make", json!({"name": "Dots", "width": 20, "height": 20}));
s
}
fn def(s: &Session) -> PatternDef {
s.doc().unwrap().doc.pattern("Dots").unwrap().clone()
}
#[test]
fn show_swatch_bounds_is_a_pattern_option_that_saves() {
let mut s = editing();
assert!(!def(&s).show_swatch_bounds);
run(&mut s, "pattern.options", json!({"showSwatchBounds": true}));
assert!(def(&s).show_swatch_bounds);
assert_eq!(run(&mut s, "pattern.list", json!({}))["patterns"][0]["showSwatchBounds"], true);
// One undo step.
run(&mut s, "edit.undo", json!({}));
assert!(!def(&s).show_swatch_bounds);
run(&mut s, "edit.redo", json!({}));
// Saved with the document; files from before the option load with it off.
let bytes = vectorcraft_format::save(&s.doc().unwrap().doc, false);
let back = vectorcraft_format::load(&bytes).unwrap();
assert_eq!(back.patterns, s.doc().unwrap().doc.patterns);
assert!(back.pattern("Dots").unwrap().show_swatch_bounds);
let mut old = serde_json::to_value(def(&s)).unwrap();
old.as_object_mut().unwrap().remove("showSwatchBounds");
assert!(!serde_json::from_value::<PatternDef>(old).unwrap().show_swatch_bounds);
}
#[test]
fn tile_edge_color_is_a_preference_that_redraws_pattern_editing() {
let mut s = editing();
let default = run(&mut s, "prefs.get", json!({"key": "patternTileEdgeColor"}));
let [r, g, b] = vectorcraft_doc::LAYER_COLORS[0].1;
assert_eq!(default, vectorcraft_color::Color::rgb8(r, g, b).to_hex());
let rev = s.doc().unwrap().revision;
run(&mut s, "prefs.set", json!({"key": "patternTileEdgeColor", "value": "FF0000"}));
assert_eq!(s.prefs.pattern_tile_edge_color, "#ff0000");
assert!(s.doc().unwrap().revision > rev, "the canvas redraws the tile edge");
assert!(s.execute("prefs.set", &json!({"key": "patternTileEdgeColor", "value": "red"})).is_err());
// Saved with the preferences; older preferences get the default.
let back: Prefs = serde_json::from_value(s.prefs.to_json()).unwrap();
assert_eq!(back.pattern_tile_edge_color, "#ff0000");
let mut old = s.prefs.to_json();
old.as_object_mut().unwrap().remove("patternTileEdgeColor");
assert_eq!(json!(serde_json::from_value::<Prefs>(old).unwrap().pattern_tile_edge_color), default);
}
+6
View File
@@ -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;
+10 -2
View File
@@ -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
}
+69
View File
@@ -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))));
}
+4
View File
@@ -213,6 +213,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
proof: vectorcraft_render::proof::active_proof(),
overprint_preview: vectorcraft_render::proof::overprint_preview_on(),
trim: app.ui.view.trim_view,
tile_edge: vectorcraft_color::Color::from_hex(&app.session.prefs.pattern_tile_edge_color).map_or(opts.tile_edge, |c| {
let [r, g, b, _] = c.to_rgba8(1.0);
[r, g, b]
}),
..opts
};
// Light documents render synchronously (no lag vs overlays); heavy ones go to the worker.
+2
View File
@@ -30,6 +30,7 @@ mod save_changes;
pub mod save_swatch_library;
mod shapes;
pub mod swatch_options;
pub mod tile_edge_color;
mod tools;
mod transform;
@@ -152,6 +153,7 @@ registry! {
ColorBalance: [color_balance::KIND] => color_balance::SPEC,
Saturate: [saturate::KIND] => saturate::SPEC,
SaveSwatchLibrary: [save_swatch_library::KIND] => save_swatch_library::SPEC,
TileEdgeColor: [tile_edge_color::KIND] => tile_edge_color::SPEC,
}
/// The spec for a `Dialog::kind` ([`DialogSpec::FORM`] when unregistered).
@@ -0,0 +1,93 @@
//! Object → Pattern → Tile Edge Color: the colour pattern editing mode draws the tile edge and the
//! swatch bounds in, a preset (the layer colours) or a custom colour. OK sets the preference
//! `patternTileEdgeColor` (`prefs.set`).
//!
//! Fields: `color` (`#rrggbb`, or a preset's name such as "Light Blue").
use serde_json::{Value, json};
use vectorcraft_color::Color;
use vectorcraft_doc::LAYER_COLORS;
use super::swatch_options::{grid, label};
use super::{DialogSpec, run_and_close};
use crate::state::Dialog;
use crate::{VectorcraftApp, widgets};
/// The dialog kind of Tile Edge Color.
pub const KIND: &str = "tileEdgeColor";
pub(super) const SPEC: DialogSpec = DialogSpec { heading: |_| "Tile Edge Color".into(), body, confirm, min_width: 300.0, ..DialogSpec::FORM };
/// Open Tile Edge Color on the current preference.
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
app.ui.dialog = Some(Dialog::new(KIND, json!({ "color": app.session.prefs.pattern_tile_edge_color })));
Ok(Value::Null)
}
/// `color` (a preset's name or a hex colour) as a colour.
fn resolve(color: &str) -> Option<Color> {
match LAYER_COLORS.iter().find(|(name, _)| name.eq_ignore_ascii_case(color.trim())) {
Some((_, [r, g, b])) => Some(Color::rgb8(*r, *g, *b)),
None => Color::from_hex(color),
}
}
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let color = resolve(&d.str("color")).unwrap_or(Color::BLACK);
let [r, g, b, _] = color.to_rgba8(1.0);
let preset = LAYER_COLORS.iter().position(|(_, c)| *c == [r, g, b]);
let names: Vec<&str> = LAYER_COLORS.iter().map(|(n, _)| *n).chain(["Custom"]).collect();
grid(ui, |ui| {
label(ui, "Color:");
ui.horizontal(|ui| {
if let Some((_, [r, g, b])) =
widgets::dropdown(ui, "tile-edge", names[preset.unwrap_or(LAYER_COLORS.len())], &names, 140.0).and_then(|i| LAYER_COLORS.get(i))
{
d.fields.insert("color".into(), json!(Color::rgb8(*r, *g, *b).to_hex()));
}
// Any other colour (the preset list shows Custom then).
let mut rgb = [r, g, b];
if ui.color_edit_button_srgb(&mut rgb).changed() {
d.fields.insert("color".into(), json!(Color::rgb8(rgb[0], rgb[1], rgb[2]).to_hex()));
}
});
ui.end_row();
});
false
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let color = d.str("color");
let hex = resolve(&color).ok_or_else(|| format!("`{color}` is not a colour (#rrggbb or a preset name)"))?.to_hex();
run_and_close(app, "prefs.set", json!({ "key": "patternTileEdgeColor", "value": hex }))
}
#[cfg(test)]
mod tests {
use super::*;
use vectorcraft_engine::Session;
#[test]
fn presets_or_custom_colours_set_the_preference() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("ui.tileEdgeColor", json!({})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("color"), app.session.prefs.pattern_tile_edge_color);
// Drawn headlessly in the shared frame: the preset's name shows.
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut out = ctx.run_ui(Default::default(), |ui| super::super::show(&mut app, ui.ctx()));
out.textures_delta.clear();
let set = |app: &mut VectorcraftApp, v: &str| {
app.ui.dialog.as_mut().unwrap().fields.insert("color".into(), json!(v));
super::super::confirm(app)
};
set(&mut app, "Red").unwrap();
assert_eq!(app.session.prefs.pattern_tile_edge_color, "#ff4f4f");
assert!(app.ui.dialog.is_none());
app.run("ui.tileEdgeColor", json!({})).unwrap();
set(&mut app, "#00FF00").unwrap();
assert_eq!(app.session.prefs.pattern_tile_edge_color, "#00ff00");
app.run("ui.tileEdgeColor", json!({})).unwrap();
assert!(set(&mut app, "nope").is_err());
}
}
+11 -1
View File
@@ -253,6 +253,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{names: [two or more styles]} open Graphic Style Options (dialog `graphicStyleOptions`, field `name`) to name the style OK merges from them (graphicStyle.merge)",
),
(
"ui.tileEdgeColor",
"Tile Edge Color…",
"",
"{} open Tile Edge Color (dialog `tileEdgeColor`, field `color`: #rrggbb or a preset name such as \"Light Blue\"); OK sets the preference patternTileEdgeColor (prefs.set), the colour of the tile edge and swatch bounds in pattern editing mode",
),
];
/// Handle a UI command. `None` = not a UI command (the engine handles it).
@@ -600,6 +606,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
let names = p.get("names").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_str).map(str::to_string).collect());
crate::dialogs::graphic_style_options::open_merge(app, names.unwrap_or_default())
}
"ui.tileEdgeColor" => crate::dialogs::tile_edge_color::open(app),
_ => return None,
};
Some(r)
@@ -996,7 +1003,10 @@ pub fn menu_tree() -> Vec<(&'static str, Vec<Item>)> {
],
),
sub("Shape", vec![c("Convert to Shape", "object.shape.convertToShape"), c("Expand Shape", "object.expandShape")]),
sub("Pattern", vec![c("Make", "object.pattern.make"), c("Edit Pattern", "object.pattern.edit"), todo("Tile Edge Color…")]),
sub(
"Pattern",
vec![c("Make", "object.pattern.make"), c("Edit Pattern", "object.pattern.edit"), c("Tile Edge Color…", "ui.tileEdgeColor")],
),
sub(
"Repeat",
vec![
+39 -1
View File
@@ -1,6 +1,6 @@
//! Pattern Options panel: active in pattern editing mode (Object → Pattern → Make / Edit
//! Pattern). Name, Tile Type, Brick Offset, Width/Height, Size Tile to Art with H/V spacing,
//! Overlap, Copies, Dim Copies, Show Tile Edge; Save a Copy / Done / Cancel. Outside editing
//! Overlap, Copies, Dim Copies, Show Tile Edge, Show Swatch Bounds; Save a Copy / Done / Cancel. Outside editing
//! mode it lists the document's patterns with an Edit button.
use egui::Ui;
@@ -140,6 +140,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if widgets::check(ui, "Show Tile Edge", def.show_tile_edge, true) {
set(app, &name, json!({"showTileEdge": !def.show_tile_edge}));
}
if widgets::check(ui, "Show Swatch Bounds", def.show_swatch_bounds, true) {
set(app, &name, json!({"showSwatchBounds": !def.show_swatch_bounds}));
}
widgets::divider(ui);
ui.horizontal(|ui| {
if widgets::flat_button(ui, "Save a Copy", 90.0).clicked()
@@ -214,3 +217,38 @@ pub fn repeat_fields(app: &VectorcraftApp) -> Option<Value> {
RepeatKind::Mirror { angle, .. } => json!({"angle": angle}),
})
}
#[cfg(test)]
mod tests {
use super::*;
use vectorcraft_engine::Session;
/// One headless frame of the panel with `events`: where each text was drawn.
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<(String, egui::Rect)> {
let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| show(app, ui));
out.textures_delta.clear();
let mut v = vec![];
for c in &out.shapes {
if let egui::Shape::Text(t) = &c.shape {
v.push((t.galley.text().to_string(), t.visual_bounding_rect()));
}
}
v
}
#[test]
fn show_swatch_bounds_toggles_the_pattern_option() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
app.run("object.pattern.make", json!({"name": "Dots"})).unwrap();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let texts = frame(&mut app, &ctx, vec![]);
let at = texts.iter().find(|(t, _)| t == "Show Swatch Bounds").expect("the checkbox").1.center();
let click = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(&mut app, &ctx, vec![egui::Event::PointerMoved(at), click(true)]);
frame(&mut app, &ctx, vec![click(false)]);
assert!(app.session.active().unwrap().doc.pattern("Dots").unwrap().show_swatch_bounds);
}
}
+4
View File
@@ -90,3 +90,7 @@ Add to Swatches.
opening a `.vcswatches` or `.gpl` file with `app.open` does the same. `ui.saveSwatchLibrary {names?}` opens the
`saveSwatchLibrary` dialog (fields `name`, `format`: `vcswatches`/`gpl`/`css`, `user`: save to the user library
folder, `selectedOnly` with `names`); `ui.dialog.confirm` runs `swatch.library.save` (to a file it asks for a path).
Tile Edge Color: Object → Pattern → Tile Edge Color… (`ui.tileEdgeColor`) opens the `tileEdgeColor` dialog (field
`color`: `#rrggbb` or a preset name such as "Light Blue"); `ui.dialog.confirm` sets the preference
`patternTileEdgeColor` (`prefs.set`), the colour pattern editing mode draws the tile edge and the swatch bounds in.
+10
View File
@@ -371,4 +371,14 @@ that split into many regions. The result reports `{ids, vector, rasterized, opti
```json
{"name":"run_command","arguments":{"command":"object.flattenTransparency","params":{"ids":[12,15],"preset":"high"}}}
{"name":"run_command","arguments":{"command":"object.flattenTransparency","params":{"balance":0,"lineArtPpi":150}}}
## Pattern editing display
In pattern editing mode the tile edge (`pattern.options {showTileEdge}`) and the swatch bounds (`pattern.options
{showSwatchBounds}`: the part of the tiling the swatch repeats, dashed) are drawn in the preference
`patternTileEdgeColor`.
```json
{"name":"run_command","arguments":{"command":"pattern.options","params":{"showSwatchBounds":true}}}
{"name":"run_command","arguments":{"command":"prefs.set","params":{"key":"patternTileEdgeColor","value":"#ff4f4f"}}}
```