mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3052 from dotCooCoo/feat/live-paint-selection-options
Live Paint: Live Paint Selection Tool Options set what a click selects and the highlight
This commit is contained in:
+145
-16
@@ -628,16 +628,14 @@ fn lp_hover(cx: &ToolContext, cache: &mut RegionCache, p: Point, edge: bool) ->
|
||||
LpHover::None
|
||||
}
|
||||
|
||||
fn lp_overlays(h: &LpHover, cache: &RegionCache) -> Vec<Overlay> {
|
||||
/// The highlight of `h`, `width` screen pixels wide in `color`.
|
||||
fn lp_overlays(h: &LpHover, cache: &RegionCache, color: [u8; 3], width: f32) -> Vec<Overlay> {
|
||||
match h {
|
||||
LpHover::None => vec![],
|
||||
LpHover::Part(bp) => vec![Overlay::Path { path: bp.clone(), color: HIGHLIGHT_RED, width: 4.0, dashed: false }],
|
||||
LpHover::Region(i) => cache
|
||||
.map
|
||||
.regions
|
||||
.get(*i)
|
||||
.map(|(_, bp, _)| vec![Overlay::Path { path: bp.clone(), color: HIGHLIGHT_RED, width: 4.0, dashed: false }])
|
||||
.unwrap_or_default(),
|
||||
LpHover::Part(bp) => vec![Overlay::Path { path: bp.clone(), color, width, dashed: false }],
|
||||
LpHover::Region(i) => {
|
||||
cache.map.regions.get(*i).map(|(_, bp, _)| vec![Overlay::Path { path: bp.clone(), color, width, dashed: false }]).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,7 +699,7 @@ impl Tool for LivePaintBucketTool {
|
||||
}
|
||||
|
||||
fn overlays(&self, _cx: &ToolContext) -> Vec<Overlay> {
|
||||
lp_overlays(&self.hover, &self.cache)
|
||||
lp_overlays(&self.hover, &self.cache, HIGHLIGHT_RED, 4.0)
|
||||
}
|
||||
|
||||
fn cursor(&self, _cx: &ToolContext, _p: Point, _m: Mods) -> Cursor {
|
||||
@@ -711,18 +709,54 @@ impl Tool for LivePaintBucketTool {
|
||||
|
||||
// ---------- Live Paint Selection ----------
|
||||
|
||||
/// Live Paint Selection: click selects a face (Shift adds); Alt-click selects an edge.
|
||||
/// The Live Paint Selection tool's highlight width (screen pixels) by default.
|
||||
pub const LP_SELECT_WIDTH: f64 = 4.0;
|
||||
/// The least and greatest highlight width (screen pixels) the Live Paint Selection tool takes.
|
||||
pub const LP_SELECT_WIDTH_RANGE: (f64, f64) = (1.0, 20.0);
|
||||
|
||||
/// Live Paint Selection: click selects a face (Shift adds); Alt-click selects an edge. Its Tool
|
||||
/// Options (double-click the tool) are `selectFills` and `selectStrokes` (both on), the highlight
|
||||
/// `color` (`#rrggbb`, red by default) and its `width` ([`LP_SELECT_WIDTH`] pixels). With Select
|
||||
/// Strokes off, Alt-click selects the face under the pointer as a click does; with Select Fills
|
||||
/// off, a click selects the edge nearest the pointer as Alt-click does; with both off, a click
|
||||
/// selects no face or edge.
|
||||
pub struct LivePaintSelectionTool {
|
||||
cache: RegionCache,
|
||||
hover: LpHover,
|
||||
fills: bool,
|
||||
strokes: bool,
|
||||
color: [u8; 3],
|
||||
width: f64,
|
||||
}
|
||||
|
||||
impl Default for LivePaintSelectionTool {
|
||||
fn default() -> Self {
|
||||
Self { cache: RegionCache::live_paint(), hover: LpHover::None }
|
||||
Self { cache: RegionCache::live_paint(), hover: LpHover::None, fills: true, strokes: true, color: HIGHLIGHT_RED, width: LP_SELECT_WIDTH }
|
||||
}
|
||||
}
|
||||
|
||||
impl LivePaintSelectionTool {
|
||||
/// What a click picks, Alt held or not: Some(true) an edge, Some(false) a face, None nothing.
|
||||
fn picks_edge(&self, alt: bool) -> Option<bool> {
|
||||
match (self.fills, self.strokes) {
|
||||
(true, true) => Some(alt),
|
||||
(true, false) => Some(false),
|
||||
(false, true) => Some(true),
|
||||
(false, false) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A highlight color option: `#rrggbb` or a layer color's name (any case).
|
||||
fn lp_color(v: &Value) -> Option<[u8; 3]> {
|
||||
let s = v.as_str()?.trim();
|
||||
if let Some((_, c)) = vectorcraft_doc::LAYER_COLORS.iter().find(|(name, _)| name.eq_ignore_ascii_case(s)) {
|
||||
return Some(*c);
|
||||
}
|
||||
let [r, g, b, _] = vectorcraft_color::Color::from_hex(s)?.to_rgba8(1.0);
|
||||
Some([r, g, b])
|
||||
}
|
||||
|
||||
impl Tool for LivePaintSelectionTool {
|
||||
fn id(&self) -> &'static str {
|
||||
"livePaintSelection"
|
||||
@@ -730,18 +764,21 @@ impl Tool for LivePaintSelectionTool {
|
||||
|
||||
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
|
||||
let p = ev.pos;
|
||||
let edge = self.picks_edge(ev.mods.alt);
|
||||
match ev.kind {
|
||||
PointerKind::Move => {
|
||||
self.hover = match lp_hover(cx, &mut self.cache, p, ev.mods.alt) {
|
||||
h @ LpHover::Part(_) => h,
|
||||
self.hover = match edge.map(|e| lp_hover(cx, &mut self.cache, p, e)) {
|
||||
Some(h @ LpHover::Part(_)) => h,
|
||||
_ => LpHover::None,
|
||||
};
|
||||
vec![]
|
||||
}
|
||||
PointerKind::Down => {
|
||||
let mut hit = None;
|
||||
if let Some(g) = live_paint_at(cx.doc, p).and_then(|id| cx.doc.node(id)) {
|
||||
let part = if ev.mods.alt { edge_near(g, p, cx.tol(4.0)) } else { face_at(g, p) };
|
||||
if let Some(edge) = edge
|
||||
&& let Some(g) = live_paint_at(cx.doc, p).and_then(|id| cx.doc.node(id))
|
||||
{
|
||||
let part = if edge { edge_near(g, p, cx.tol(4.0)) } else { face_at(g, p) };
|
||||
hit = part.map(|n| n.id);
|
||||
}
|
||||
match (hit, ev.mods.shift) {
|
||||
@@ -756,12 +793,36 @@ impl Tool for LivePaintSelectionTool {
|
||||
}
|
||||
|
||||
fn overlays(&self, _cx: &ToolContext) -> Vec<Overlay> {
|
||||
lp_overlays(&self.hover, &self.cache)
|
||||
lp_overlays(&self.hover, &self.cache, self.color, self.width as f32)
|
||||
}
|
||||
|
||||
fn cursor(&self, _cx: &ToolContext, _p: Point, _m: Mods) -> Cursor {
|
||||
Cursor::Arrow
|
||||
}
|
||||
|
||||
fn options(&self) -> Value {
|
||||
let [r, g, b] = self.color;
|
||||
json!({
|
||||
"selectFills": self.fills,
|
||||
"selectStrokes": self.strokes,
|
||||
"color": format!("#{r:02x}{g:02x}{b:02x}"),
|
||||
"width": self.width,
|
||||
})
|
||||
}
|
||||
|
||||
fn set_option(&mut self, key: &str, v: &Value) {
|
||||
match key {
|
||||
"selectFills" => self.fills = v.as_bool().unwrap_or(self.fills),
|
||||
"selectStrokes" => self.strokes = v.as_bool().unwrap_or(self.strokes),
|
||||
"color" => self.color = lp_color(v).unwrap_or(self.color),
|
||||
"width" => {
|
||||
let (min, max) = LP_SELECT_WIDTH_RANGE;
|
||||
self.width = v.as_f64().filter(|w| w.is_finite()).map_or(self.width, |w| w.clamp(min, max));
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
self.hover = LpHover::None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a builder-family tool by id (None = not one of ours).
|
||||
@@ -1014,6 +1075,74 @@ mod tests {
|
||||
assert_eq!(acts, vec![Action::Exec("select.none".into(), json!({}))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_paint_selection_options_choose_what_a_click_selects() {
|
||||
let (d, _gid, f2, e) = live_group_doc();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let cx = cx(&d, &s, &p);
|
||||
let alt = Mods { alt: true, ..Default::default() };
|
||||
let set = |ids: &[NodeId]| vec![Action::Exec("select.set".into(), json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>() }))];
|
||||
let none = vec![Action::Exec("select.none".into(), json!({}))];
|
||||
let mut t = LivePaintSelectionTool::default();
|
||||
assert_eq!(t.options(), json!({"selectFills": true, "selectStrokes": true, "color": "#ff2a2a", "width": 4.0}));
|
||||
// Select Fills off: a click inside a face selects nothing, and a click by an edge selects it.
|
||||
t.set_option("selectFills", &json!(false));
|
||||
assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, 75.0, 25.0)), none);
|
||||
t.pointer(&cx, &ev(PointerKind::Move, 75.0, 25.0));
|
||||
assert!(t.overlays(&cx).is_empty());
|
||||
assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, 51.0, 25.0)), set(&[e]));
|
||||
assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, 51.0, 25.0).with_mods(alt)), set(&[e]));
|
||||
// Select Strokes off: Alt-click by an edge selects the face under the pointer.
|
||||
t.set_option("selectFills", &json!(true));
|
||||
t.set_option("selectStrokes", &json!(false));
|
||||
assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, 51.0, 25.0).with_mods(alt)), set(&[f2]));
|
||||
t.pointer(&cx, &ev(PointerKind::Move, 51.0, 25.0).with_mods(alt));
|
||||
let ov = t.overlays(&cx);
|
||||
let [Overlay::Path { path, .. }] = ov.as_slice() else { panic!("{ov:?}") };
|
||||
assert_eq!(path.bounding_box(), Rect::new(50.0, 0.0, 100.0, 50.0), "the face, not the edge");
|
||||
// Both off: nothing is selected or highlighted.
|
||||
t.set_option("selectFills", &json!(false));
|
||||
for m in [Mods::default(), alt] {
|
||||
assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, 51.0, 25.0).with_mods(m)), none);
|
||||
t.pointer(&cx, &ev(PointerKind::Move, 51.0, 25.0).with_mods(m));
|
||||
assert!(t.overlays(&cx).is_empty());
|
||||
}
|
||||
let shift = Mods { shift: true, ..Default::default() };
|
||||
assert!(t.pointer(&cx, &ev(PointerKind::Down, 75.0, 25.0).with_mods(shift)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_paint_selection_options_set_the_highlight() {
|
||||
let (d, ..) = live_group_doc();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let cx = cx(&d, &s, &p);
|
||||
let mut t = LivePaintSelectionTool::default();
|
||||
let highlight = |t: &mut LivePaintSelectionTool| {
|
||||
t.pointer(&cx, &ev(PointerKind::Move, 75.0, 25.0));
|
||||
match t.overlays(&cx).as_slice() {
|
||||
[Overlay::Path { color, width, .. }] => (*color, *width),
|
||||
ov => panic!("{ov:?}"),
|
||||
}
|
||||
};
|
||||
assert_eq!(highlight(&mut t), (HIGHLIGHT_RED, 4.0));
|
||||
t.set_option("color", &json!("#00FF00"));
|
||||
t.set_option("width", &json!(9));
|
||||
assert_eq!(highlight(&mut t), ([0, 0xff, 0], 9.0));
|
||||
// A layer color's name; a value that is not a color, or a width that is not a number, is ignored.
|
||||
t.set_option("color", &json!("light blue"));
|
||||
t.set_option("width", &json!("wide"));
|
||||
assert_eq!(highlight(&mut t), ([0x4f, 0x80, 0xff], 9.0));
|
||||
t.set_option("color", &json!("nope"));
|
||||
assert_eq!(t.options()["color"], "#4f80ff");
|
||||
// The width stays within its range.
|
||||
t.set_option("width", &json!(100));
|
||||
assert_eq!(t.options()["width"], json!(LP_SELECT_WIDTH_RANGE.1));
|
||||
t.set_option("width", &json!(0));
|
||||
assert_eq!(t.options()["width"], json!(LP_SELECT_WIDTH_RANGE.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_builds_the_three_tools() {
|
||||
for id in ["shapeBuilder", "livePaintBucket", "livePaintSelection"] {
|
||||
|
||||
@@ -34,6 +34,8 @@ const ROWS: &[Row] = &[
|
||||
(&["smooth"], None, &["fidelity"]),
|
||||
(&["blobBrush", "eraser"], None, &["size"]),
|
||||
(&["blobBrush"], None, &["keepSelected", "mergeOnlySelection"]),
|
||||
// Live Paint Selection Tool Options (`builder::LivePaintSelectionTool`).
|
||||
(&["livePaintSelection"], None, &["selectFills", "selectStrokes", "color", "width"]),
|
||||
(&["polygon"], None, &["sides"]),
|
||||
(&["star"], None, &["points"]),
|
||||
(&["freeTransform"], None, &["constrain"]),
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
//! Live Paint Selection Tool Options (double-click the Live Paint Selection tool): what a click
|
||||
//! with the tool selects and how the face or edge under the pointer highlights. The options last
|
||||
//! across tool switches and are saved with the preferences. OK sets them with
|
||||
//! `tool.setOption {tool: "livePaintSelection", values}`.
|
||||
//!
|
||||
//! Fields: `selectFills` and `selectStrokes` (the faces and the edges of Live Paint groups),
|
||||
//! `color` (the highlight color: `#rrggbb`, or a layer color's name such as "Red") and `width`
|
||||
//! (the highlight's width in screen pixels).
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_color::Color;
|
||||
use vectorcraft_doc::LAYER_COLORS;
|
||||
use vectorcraft_tools::builder::LP_SELECT_WIDTH_RANGE;
|
||||
|
||||
use super::tile_edge_color::resolve;
|
||||
use super::{DialogSpec, form, run_and_close};
|
||||
use crate::state::Dialog;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// The dialog kind of the Live Paint Selection Tool Options.
|
||||
pub const KIND: &str = "livePaintSelectionOptions";
|
||||
|
||||
/// The tool the dialog serves.
|
||||
const TOOL: &str = "livePaintSelection";
|
||||
|
||||
/// The options the dialog edits.
|
||||
const KEYS: [&str; 4] = ["selectFills", "selectStrokes", "color", "width"];
|
||||
|
||||
/// Width of the label column.
|
||||
const LABEL_W: f32 = 60.0;
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_width: 300.0, ..DialogSpec::FORM };
|
||||
|
||||
fn heading(_: &Dialog) -> String {
|
||||
let label = vectorcraft_tools::tool_info(TOOL).map_or("", |t| t.label);
|
||||
crate::i18n::fmt(tl!("{tool} Options"), &[("tool", tl!(label))])
|
||||
}
|
||||
|
||||
/// Open the Live Paint Selection Tool Options with the tool's current values.
|
||||
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
let o = app.session.tool_options_of(TOOL);
|
||||
let fields: Map<String, Value> = KEYS.iter().filter_map(|key| Some((key.to_string(), o.get(*key)?.clone()))).collect();
|
||||
app.ui.dialog = Some(Dialog { kind: KIND.into(), fields });
|
||||
Ok(json!({ "dialog": KIND }))
|
||||
}
|
||||
|
||||
fn body(_app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
form::check(ui, d, "selectFills", tl!("Select Fills"));
|
||||
form::check(ui, d, "selectStrokes", tl!("Select Strokes"));
|
||||
ui.add_space(6.0);
|
||||
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();
|
||||
widgets::label_row(ui, tl!("Color:"), LABEL_W, |ui| {
|
||||
if let Some((_, [r, g, b])) =
|
||||
widgets::dropdown(ui, "lp-select-color", names[preset.unwrap_or(LAYER_COLORS.len())], &names, 130.0).and_then(|i| LAYER_COLORS.get(i))
|
||||
{
|
||||
d.fields.insert("color".into(), json!(Color::rgb8(*r, *g, *b).to_hex()));
|
||||
}
|
||||
// The color button sets any other color; the dropdown then shows Custom.
|
||||
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()));
|
||||
}
|
||||
});
|
||||
let (min, max) = LP_SELECT_WIDTH_RANGE;
|
||||
let v = d.f64("width", min);
|
||||
widgets::label_row(ui, tl!("Width:"), LABEL_W, |ui| {
|
||||
if let Some(x) = widgets::plain_field(ui, "lp-select-width", v, "px", 0, 72.0) {
|
||||
d.fields.insert("width".into(), json!(x.round().clamp(min, max)));
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
|
||||
/// OK: set the tool's options.
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
let values: Map<String, Value> = KEYS.iter().filter_map(|k| Some((k.to_string(), d.fields.get(*k)?.clone()))).collect();
|
||||
run_and_close(app, "tool.setOption", json!({ "tool": TOOL, "values": values }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
use vectorcraft_tools::{Overlay, PointerEvent, PointerKind};
|
||||
|
||||
/// The labels the open dialog paints.
|
||||
fn painted(app: &mut VectorcraftApp) -> String {
|
||||
crate::tests_labels::painted_text(app, |app, ui| super::super::show(app, ui.ctx()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_clicking_the_tool_opens_its_options_and_ok_sets_them() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
// Two squares side by side, made a Live Paint group: two faces and the edge between them.
|
||||
for x in [0, 100] {
|
||||
app.run("shape.rectangle", json!({"x": 50 + x, "y": 50, "width": 100, "height": 100})).unwrap();
|
||||
}
|
||||
app.run("select.all", json!({})).unwrap();
|
||||
app.run("livePaint.make", json!({})).unwrap();
|
||||
app.run("select.set", json!({"ids": []})).unwrap();
|
||||
app.select_tool(TOOL);
|
||||
let click = |app: &mut VectorcraftApp, x: f64| {
|
||||
let view = app.view_info();
|
||||
crate::canvas::dispatch(app, &PointerEvent::new(PointerKind::Down, x, 100.0), view);
|
||||
crate::canvas::dispatch(app, &PointerEvent::new(PointerKind::Up, x, 100.0), view);
|
||||
app.session.doc().unwrap().selection.objects.len()
|
||||
};
|
||||
assert_eq!(click(&mut app, 100.0), 1, "a face is selected by default");
|
||||
|
||||
app.run("tool.options", json!({"tool": TOOL})).unwrap();
|
||||
let text = painted(&mut app);
|
||||
for label in ["Live Paint Selection Tool Options", "Select Fills", "Select Strokes", "Color:", "Width:"] {
|
||||
assert!(text.contains(label), "{label} in {text}");
|
||||
}
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
assert_eq!(d.kind, KIND);
|
||||
assert_eq!((d.bool("selectFills"), d.bool("selectStrokes"), d.str("color"), d.f64("width", 0.0)), (true, true, "#ff2a2a".to_string(), 4.0));
|
||||
d.fields.insert("selectFills".into(), json!(false));
|
||||
d.fields.insert("color".into(), json!("Green"));
|
||||
d.fields.insert("width".into(), json!(8));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
// The tool takes them: a click inside a face selects nothing, and the highlight shows the edge
|
||||
// in the new color and width.
|
||||
assert_eq!(click(&mut app, 100.0), 0);
|
||||
let view = app.view_info();
|
||||
crate::canvas::dispatch(&mut app, &PointerEvent::new(PointerKind::Move, 151.0, 100.0), view);
|
||||
let ov = app.session.overlays(view);
|
||||
assert!(ov.iter().any(|o| matches!(o, Overlay::Path { color: [0x4f, 0xff, 0x4f], width: 8.0, .. })), "{ov:?}");
|
||||
// They last while another tool is chosen, and the dialog opens on them again.
|
||||
app.select_tool("selection");
|
||||
let o = app.session.tool_options_of(TOOL);
|
||||
assert_eq!((o["selectFills"].as_bool(), o["color"].as_str(), o["width"].as_f64()), (Some(false), Some("#4fff4f"), Some(8.0)));
|
||||
app.run("tool.options", json!({"tool": TOOL})).unwrap();
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert_eq!((d.bool("selectFills"), d.str("color")), (false, "#4fff4f".to_string()));
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ pub mod layer_options;
|
||||
pub mod layers_panel_options;
|
||||
pub mod liquify;
|
||||
pub mod live_paint_gaps;
|
||||
pub mod live_paint_selection;
|
||||
pub mod missing_fonts;
|
||||
pub mod missing_links;
|
||||
pub(crate) mod modal;
|
||||
@@ -320,6 +321,7 @@ registry! {
|
||||
PerspectiveGridOptions: [perspective_options::KIND] => perspective_options::SPEC,
|
||||
BlendOptions: [blend_options::KIND] => blend_options::SPEC,
|
||||
LivePaintGaps: [live_paint_gaps::KIND] => live_paint_gaps::SPEC,
|
||||
LivePaintSelectionOptions: [live_paint_selection::KIND] => live_paint_selection::SPEC,
|
||||
BrushOptions: [brush_options::KIND] => brush_options::SPEC,
|
||||
EditSelection: [edit_selection::KIND] => edit_selection::SPEC,
|
||||
PerspectivePlane: [perspective_plane::KIND] => perspective_plane::SPEC,
|
||||
|
||||
@@ -1174,6 +1174,8 @@
|
||||
Large Gaps Големи пролуки
|
||||
Gap Preview Color: Цвят за преглед на пролуките:
|
||||
Close Gaps With Paths Затваряне на пролуките с пътища
|
||||
Select Fills Избиране на запълвания
|
||||
Select Strokes Избиране на щрихове
|
||||
Gaussian Blur… Гаусово размазване…
|
||||
General Общи
|
||||
General › Placement › X Общи › Разположение › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -264,6 +264,8 @@
|
||||
Large Gaps Velké mezery
|
||||
Gap Preview Color: Barva náhledu mezer:
|
||||
Close Gaps With Paths Uzavřít mezery cestami
|
||||
Select Fills Vybírat výplně
|
||||
Select Strokes Vybírat tahy
|
||||
Image Trace Vektorizace obrazu
|
||||
Image Trace (Make and Expand) Vektorizace obrazu (vytvořit a rozložit)
|
||||
Release Image Trace Uvolnit vektorizaci obrazu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5076,6 +5076,8 @@ swatch Bright Violet Kraftig violet
|
||||
Chop Design Beskær design
|
||||
Clear Events Ryd hændelser
|
||||
Close Gaps With Paths Luk huller med stier
|
||||
Select Fills Markér udfyldninger
|
||||
Select Strokes Markér konturer
|
||||
Column Type: Søjletype:
|
||||
Each Design Represents: Hvert design repræsenterer:
|
||||
Event: Hændelse:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1192,6 +1192,8 @@ file-filter Templates Vorlagen
|
||||
Large Gaps Große Lücken
|
||||
Gap Preview Color: Vorschaufarbe für Lücken:
|
||||
Close Gaps With Paths Lücken mit Pfaden schließen
|
||||
Select Fills Flächen auswählen
|
||||
Select Strokes Konturen auswählen
|
||||
Gaussian Blur… Gaußscher Weichzeichner…
|
||||
General Allgemein
|
||||
General › Placement › X Allgemein › Platzierung › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1139,6 +1139,8 @@
|
||||
Large Gaps Μεγάλα διάκενα
|
||||
Gap Preview Color: Χρώμα προεπισκόπησης διακένων:
|
||||
Close Gaps With Paths Κλείσιμο διακένων με διαδρομές
|
||||
Select Fills Επιλογή γεμισμάτων
|
||||
Select Strokes Επιλογή περιγραμμάτων
|
||||
Gaussian Blur… Θόλωση Γκάους…
|
||||
General Γενικά
|
||||
General › Placement › X Γενικά › Τοποθέτηση › X
|
||||
|
||||
|
@@ -1199,6 +1199,8 @@
|
||||
Large Gaps Huecos grandes
|
||||
Gap Preview Color: Color de previsualización de huecos:
|
||||
Close Gaps With Paths Cerrar huecos con trazados
|
||||
Select Fills Seleccionar rellenos
|
||||
Select Strokes Seleccionar trazos
|
||||
Gaussian Blur… Desenfoque gaussiano…
|
||||
General General
|
||||
General › Placement › X General › Colocación › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -271,6 +271,8 @@
|
||||
Large Gaps Suuret aukot
|
||||
Gap Preview Color: Aukkojen esikatseluväri:
|
||||
Close Gaps With Paths Sulje aukot poluilla
|
||||
Select Fills Valitse täytöt
|
||||
Select Strokes Valitse viivat
|
||||
Image Trace Kuvan jäljitys
|
||||
Image Trace (Make and Expand) Kuvan jäljitys (luo ja laajenna)
|
||||
Release Image Trace Vapauta kuvan jäljitys
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1198,6 +1198,8 @@
|
||||
Large Gaps Grands espaces
|
||||
Gap Preview Color: Couleur d’aperçu des espaces :
|
||||
Close Gaps With Paths Fermer les espaces avec des tracés
|
||||
Select Fills Sélectionner les fonds
|
||||
Select Strokes Sélectionner les contours
|
||||
Gaussian Blur… Flou gaussien…
|
||||
General Général
|
||||
General › Placement › X Général › Placement › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1504,6 +1504,8 @@ file-filter Templates Sablonok
|
||||
Large Gaps Nagy hézagok
|
||||
Gap Preview Color: Hézagok előnézeti színe:
|
||||
Close Gaps With Paths Hézagok lezárása útvonalakkal
|
||||
Select Fills Kitöltések kijelölése
|
||||
Select Strokes Körvonalak kijelölése
|
||||
Gaussian Blur Gauss-elmosás
|
||||
Gaussian Blur… Gauss-elmosás…
|
||||
General Általános
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5076,6 +5076,8 @@ swatch Bright Violet Violet cerah
|
||||
Chop Design Potong desain
|
||||
Clear Events Hapus peristiwa
|
||||
Close Gaps With Paths Tutup celah dengan jalur
|
||||
Select Fills Pilih isian
|
||||
Select Strokes Pilih stroke
|
||||
Column Type: Jenis kolom:
|
||||
Each Design Represents: Setiap desain mewakili:
|
||||
Event: Peristiwa:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3770 and column 19.
|
@@ -1208,6 +1208,8 @@ file-filter Templates Modelli
|
||||
Large Gaps Spazi vuoti grandi
|
||||
Gap Preview Color: Colore anteprima spazi vuoti:
|
||||
Close Gaps With Paths Chiudi spazi vuoti con tracciati
|
||||
Select Fills Seleziona riempimenti
|
||||
Select Strokes Seleziona tracce
|
||||
Gaussian Blur… Sfocatura gaussiana…
|
||||
General Generale
|
||||
General › Placement › X Generale › Posizionamento › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -262,6 +262,8 @@
|
||||
Large Gaps 大きい間隔
|
||||
Gap Preview Color: 間隔のプレビューカラー:
|
||||
Close Gaps With Paths パスで間隔を閉じる
|
||||
Select Fills 塗りを選択
|
||||
Select Strokes 線を選択
|
||||
Image Trace 画像トレース
|
||||
Image Tracing 画像トレースオブジェクト
|
||||
Tracing Presets トレースプリセット
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5035,6 +5035,8 @@ imageTrace Outline კონტური
|
||||
Resize Type Area ტექსტის არის ზომის შეცვლა
|
||||
Scale Design დიზაინის მასშტაბირება
|
||||
Scale in Perspective პერსპექტივაში მასშტაბირება
|
||||
Select Fills შევსებების მონიშვნა
|
||||
Select Strokes კონტურების მონიშვნა
|
||||
Set History Limit… ისტორიის ლიმიტის დაყენება…
|
||||
Shape Builder Delete ფიგურების კონსტრუქტორით წაშლა
|
||||
Sliding სრიალა
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -258,6 +258,8 @@
|
||||
Large Gaps 큰 간격
|
||||
Gap Preview Color: 간격 미리 보기 색상:
|
||||
Close Gaps With Paths 패스로 간격 닫기
|
||||
Select Fills 칠 선택
|
||||
Select Strokes 선 선택
|
||||
Image Trace 이미지 추적
|
||||
Image Tracing 이미지 추적
|
||||
Tracing Presets 추적 프리셋
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5088,6 +5088,8 @@ swatch Bright Violet Felviolet
|
||||
Chop Design Ontwerp afknippen
|
||||
Clear Events Gebeurtenissen wissen
|
||||
Close Gaps With Paths Openingen sluiten met paden
|
||||
Select Fills Vullingen selecteren
|
||||
Select Strokes Lijnen selecteren
|
||||
Column Type: Kolomtype:
|
||||
Each Design Represents: Elk ontwerp staat voor:
|
||||
Event: Gebeurtenis:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -229,6 +229,8 @@
|
||||
Large Gaps Duże przerwy
|
||||
Gap Preview Color: Kolor podglądu przerw:
|
||||
Close Gaps With Paths Zamknij przerwy ścieżkami
|
||||
Select Fills Zaznaczaj wypełnienia
|
||||
Select Strokes Zaznaczaj obrysy
|
||||
Image Trace Obrys obrazu
|
||||
Text Wrap Oblewanie tekstem
|
||||
Text Wrap Options… Opcje oblewania tekstem…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1056,6 +1056,8 @@
|
||||
Large Gaps Lacunas Grandes
|
||||
Gap Preview Color: Cor de Visualização das Lacunas:
|
||||
Close Gaps With Paths Fechar Lacunas com Caminhos
|
||||
Select Fills Selecionar Preenchimentos
|
||||
Select Strokes Selecionar Traçados
|
||||
Gaussian Blur… Desfoque Gaussiano…
|
||||
General Geral
|
||||
Generate CSS Gerar CSS
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1206,6 +1206,8 @@ file-filter Templates Шаблоны
|
||||
Large Gaps Большие зазоры
|
||||
Gap Preview Color: Цвет просмотра зазоров:
|
||||
Close Gaps With Paths Закрыть зазоры контурами
|
||||
Select Fills Выделять заливки
|
||||
Select Strokes Выделять обводки
|
||||
Gaussian Blur… Размытие по Гауссу…
|
||||
General Общие
|
||||
General › Placement › X Общие › Размещение › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1176,6 +1176,8 @@
|
||||
Large Gaps Stora mellanrum
|
||||
Gap Preview Color: Förhandsgranskningsfärg för mellanrum:
|
||||
Close Gaps With Paths Stäng mellanrum med banor
|
||||
Select Fills Markera fyllningar
|
||||
Select Strokes Markera konturer
|
||||
Gaussian Blur… Gaussisk oskärpa…
|
||||
General Allmänt
|
||||
General › Placement › X Allmänt › Placering › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1202,6 +1202,8 @@ file-filter Templates Шаблони
|
||||
Large Gaps Великі проміжки
|
||||
Gap Preview Color: Колір перегляду проміжків:
|
||||
Close Gaps With Paths Закрити проміжки контурами
|
||||
Select Fills Виділяти заливки
|
||||
Select Strokes Виділяти обведення
|
||||
Gaussian Blur… Розмиття за Гауссом…
|
||||
General Загальні
|
||||
General › Placement › X Загальні › Розміщення › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 725 and column 40.
|
@@ -1394,6 +1394,8 @@ file-filter Templates Mẫu
|
||||
Large Gaps Khoảng cách lớn
|
||||
Gap Preview Color: Màu xem trước khoảng cách:
|
||||
Close Gaps With Paths Đóng khoảng cách bằng đường dẫn
|
||||
Select Fills Chọn vùng tô
|
||||
Select Strokes Chọn nét
|
||||
Gaussian Blur Mờ Gaussian
|
||||
Gaussian Blur… Làm mờ Gaussian…
|
||||
General Chung
|
||||
|
||||
|
@@ -1155,6 +1155,8 @@ file-filter Templates 模板
|
||||
Large Gaps 大间隙
|
||||
Gap Preview Color: 间隙预览颜色:
|
||||
Close Gaps With Paths 用路径封闭间隙
|
||||
Select Fills 选择填色
|
||||
Select Strokes 选择描边
|
||||
Gaussian Blur… 高斯模糊…
|
||||
General 常规
|
||||
General › Placement › X 一般 › 位置 › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1179,6 +1179,8 @@ file-filter Templates 範本
|
||||
Large Gaps 大間隙
|
||||
Gap Preview Color: 間隙預覽顏色:
|
||||
Close Gaps With Paths 以路徑封閉間隙
|
||||
Select Fills 選取填色
|
||||
Select Strokes 選取筆畫
|
||||
Gaussian Blur… 高斯模糊…
|
||||
General 一般
|
||||
General › Placement › X 一般 › 位置 › X
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -471,7 +471,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"tool.options",
|
||||
"Tool Options…",
|
||||
"",
|
||||
"{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height; OK runs artboard.setProps), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x and y, draws one there with them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), size (pt); OK runs tool.setOption {tool, values})",
|
||||
"{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height; OK runs artboard.setProps), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x and y, draws one there with them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), size (pt); OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values})",
|
||||
),
|
||||
(
|
||||
"ui.colorGuideOptions",
|
||||
|
||||
@@ -366,6 +366,8 @@ pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::
|
||||
_ if vectorcraft_tools::settings::LIQUIFY.contains(&tool) => crate::dialogs::liquify::open(app, tool),
|
||||
// The freehand tools: their Tool Options (Fidelity, fill, the tolerances, the brush size).
|
||||
_ if crate::dialogs::freehand::TOOLS.contains(&tool) => crate::dialogs::freehand::open(app, tool),
|
||||
// The Live Paint Selection tool: its Tool Options (what a click selects, the highlight).
|
||||
"livePaintSelection" => crate::dialogs::live_paint_selection::open(app),
|
||||
// The Symbolism tools: the brush they share.
|
||||
_ if vectorcraft_tools::settings::SYMBOLISM.contains(&tool) => Ok(crate::dialogs::symbolism_options::open(app, tool)),
|
||||
// The graph tools: Graph Type for the selected graph.
|
||||
|
||||
@@ -1233,6 +1233,12 @@ already walks Image Trace. Short agent entry points:
|
||||
and `livePaint.info` list the gaps up to `size` wide that no path closes, as `[x0, y0, x1, y1]`. `livePaint.merge`
|
||||
keeps the bottom group's options when its Gap Detection is on, and otherwise turns Gap Detection on with `small`
|
||||
when another merged group has it on. When Gap Detection is off in every merged group, it stays off.
|
||||
The Live Paint Selection tool's options (`tool.setOption {tool: "livePaintSelection", values}`; double-clicking the
|
||||
tool opens them, dialog `livePaintSelectionOptions`) are `selectFills` and `selectStrokes` (both on), `color` (the
|
||||
highlight color, `#rrggbb` or a layer color's name; `#ff2a2a`) and `width` (the highlight's width, 1 to 20 screen
|
||||
pixels; 4). With `selectStrokes` off, Alt-click selects the face under the pointer as a click does; with
|
||||
`selectFills` off, a click selects the edge nearest the pointer as Alt-click does; with both off, a click selects
|
||||
no face or edge.
|
||||
|
||||
`dataset.select`, `dataset.next` and `dataset.prev` reply `{name, applied, skipped}`. They add `overflow: [id…]`
|
||||
when area type or type on a path whose text they rewrote doesn't fit, as `text.setText` does (see
|
||||
|
||||
@@ -654,6 +654,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Advanced art:** live Blends (steps/distance/smooth colour, editable spine, anchor-targeted Blend tool, faithful interpolation, knockout), Envelope Distort (warp/mesh/top object, Reset, full Envelope Options, mesh handles, Edit Contents; type, images, symbols, gradients, patterns and appearance distorted everywhere), Liquify tools with options and pen pressure, Puppet Warp with rotating pins, Perspective Grid with presets, movable planes and projected type (choosing a perspective tool shows the grid; Hide Grid hides it with the tool still chosen), Gradient Mesh, Shape Builder (stroke-only open paths and lines cut the regions they cross and enclose regions of their own, and regions they bound merge without seams where the lines cross at rounded coordinates (#893); filled open paths count as closed; erase mode while Alt/Option is held: a minus pointer, red highlights of what will go, a click or drag deletes the regions touched, the pieces of open lines and the edges (outline pieces between the points where paths meet, opening their paths there) it passes over, one undo step, `shapeBuilder.merge {erase}` and `shapeBuilder.regions` edges over MCP), Live Paint (every path an edge, open lines included), Image Trace (12 presets, colour layers traced in parallel; a trace past two million anchor points is refused with advice rather than running out of memory, an image over 64 megapixels is traced scaled down to that size (aspect kept, paths at the image's own size) and one over 192 megapixels is refused, and a selection too large to outline shows its bounds (#525); Object › Image Trace › Make and Make and Expand trace at once with the Default preset, #543; with an image selected the Control bar and the Properties panel show Image Trace, whose arrow traces with any preset, Mask (`object.maskImage`: a clipping path on the image's outline, selected so its handles crop), Crop Image (a crop box on the image, starting on its part over the artboard, dragged by its handles or inside it, the outside dimmed, with X/Y/W/H, Apply and Cancel in the Control bar, Enter and Escape, #734; `object.cropImage {rect}` for agents, `{trim: true}` cuts an image to its pixels that aren't fully transparent, pixel for pixel) and Embed or Unembed in place of Fill and Stroke; with an Image Trace object selected, its Preset (choosing another traces again), View, the Image Trace panel and Expand, plus Release in Properties; the five View modes (Tracing Result, with Outlines, Outlines, Outlines with Source Image, Source Image) redraw a trace on screen without tracing again, are saved with it and `imageTrace.setView` sets them, while exports and Expand keep the result; the panel's Advanced section is a header that stays open or closed as it was left (part of #641); Color mode's Palette: Limited, Full Tone (as many colours as the tones need, Less … More), Automatic (flat art with its own colours, else Full Tone; a heuristic) and Document Library (the most used colours of the document's swatches or a swatch library, filled with the swatch colours as they are), part of #641; Advanced › Create: Fills and/or Strokes, where lines up to the Stroke width trace as stroked centre lines (distance transform, thinning, a skeleton graph with spurs pruned, curve fitting; one width per line; without Fills, wider areas become stroked outlines), part of #641), pattern swatches with pattern editing mode, live Repeat (radial/grid/mirror). A single selected mirror repeat shows a prominent axis with a center move handle and two rotation handles. Drag the line or center to change spacing, or an end to change angle; Shift snaps rotation to 45°. The original artwork stays in place, the reflected copy previews live, Escape cancels and each drag takes one undo step through `object.repeat.options`. Controls follow zoom/view rotation and hide with edges or the bounding box. Engine and headless canvas regressions passed for gestures, history and visibility; native move, rotation and Escape screenshots were reviewed. [Public mirror-repeat reference](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/set-mirror-repeat-options.html). Object › Expand turns radial, grid and mirror repeats into groups of their instances (#1765, #1756).
|
||||
- Create Strokes uses exact squared Euclidean distances and Guo–Hall thinning in bounded per-blob crops. Maximal-disc pruning, split-crossing merges and degree-two joins simplify its skeleton graph. The whole-blob width rule and ink fitting remain; 46 code-drawn inputs measure ends and junctions in [`image-trace-quality.md`](image-trace-quality.md#centre-lines). Junction degrees measure 44/45 correct in each input variant; remaining spurs are recorded. This is a measured skeleton improvement; the Image Trace score, missing per-feature selection and width controls, and estimates remain unchanged.
|
||||
Live Paint Gap Options (Object › Live Paint › Gap Options…, `livePaint.gapOptions`): with Gap Detection on, paint stops at the gaps that run from a loose end of a path to the nearest path, up to Small (3 pt), Medium (6 pt), Large (12 pt) or a custom size; Close Gaps With Paths adds an unpainted path across each gap; Preview applies the options live and draws the gaps in the Gap Preview Color. The options are saved, copied and undone with the group, and Merge keeps the bottom group's options when its Gap Detection is on, or else turns Gap Detection on with Small when another merged group has it on. When Gap Detection is off in every merged group, it stays off.
|
||||
Double-clicking the Live Paint Selection tool opens its Tool Options ([Live Paint Selection tool options](https://web.archive.org/web/20250904012025/https://helpx.adobe.com/illustrator/using/selecting-objects.html)): with Select Fills off, a click selects an edge instead of a face; with Select Strokes off, Alt-click selects a face instead of an edge; Color and Width set the color and width of the highlight on the face or edge under the pointer (red and 4 px by default). The options last across tool switches and are saved with the preferences (`tool.setOption {tool: "livePaintSelection"}`).
|
||||
Window › Blend opens the Blend panel: Make, the selected blend's Spacing and Orientation (with no blend selected, the options new blends start with), Reverse Front to Back, Reverse Spine, Replace Spine, Expand and Release, each running its Object › Blend command. Make is dimmed until two or more objects are selected. Spacing and Orientation are dimmed until a blend or two or more objects are selected, and the other buttons are dimmed until a blend is selected.
|
||||
- **Paint and appearance (M3):**
|
||||
- **Swatches:** names follow the colour model and are unique; Swatch Options (process/spot, Global, Gray/RGB/HSB/CMYK/Web, live preview); editing a global or spot swatch recolours every linked fill, stroke and text run in one undo step; multi-select and delete with confirmation; New Swatch and New Color Group dialogs (a group from the selected artwork's colours); swatches in colour groups convert, separate and count like any other. The Swatches panel selects ranges and whole groups, has a find field, and drags swatches to reorder, regroup or paint art; Add Used Colors, Select All Unused, Merge, Ungroup and Sort by Kind; a CMYK document keeps applied colours in CMYK. Swatch libraries: nine generated colour libraries and five gradient libraries in a library panel, saved as `.vcswatches`, `.gpl`, swatch exchange (`.ase`) or CSS, and loaded from `.vcswatches`, `.gpl` and `.ase` files (`.ase` both ways: RGB, CMYK, Lab and Gray; global, spot and process; groups) and from color books (`.acb`, read only: the RGB, CMYK or Lab colors of a book such as a Pantone book a user owns, named with its prefix and suffix, spot unless it says process, #831); `swatch.library.copyToUser` copies a loaded library into the User Defined folder without replacing a file, and a file with the same extension and bytes as a User Defined library opens as that library. Tints of global and spot colours ("Name 40%") stay linked and separate as a percentage of their plate. Spot colours can be defined in Lab (Spot Colors options). A built-in [Registration] swatch prints on every plate.
|
||||
|
||||
Reference in New Issue
Block a user