mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
Merge pull request #3815 from Taha-Mahmoodi/feat/2691-grid-arc-tool-options
Tools: double-clicking the Line Segment, Arc, Rectangular Grid and Polar Grid tools opens their Tool Options
This commit is contained in:
@@ -30,7 +30,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
flare
|
||||
),
|
||||
cmd!("shape.line", "Line Segment", [], None, "{x1, y1, x2, y2} → {id}", has_doc, line),
|
||||
cmd!("shape.line", "Line Segment", [], None, "{x1, y1, x2, y2, fill?: bool (Fill Line: the current fill, else none)} → {id}", has_doc, line),
|
||||
cmd!("shape.spiral", "Spiral", [], None, "{cx, cy, radius, decay=80 (%), segments=10, clockwise?} → {id}", has_doc, spiral),
|
||||
cmd!(
|
||||
"shape.arc",
|
||||
@@ -486,9 +486,10 @@ fn line(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let a = Point::new(f64_req(p, "x1", "shape.line")?, f64_req(p, "y1", "shape.line")?);
|
||||
let b = Point::new(f64_req(p, "x2", "shape.line")?, f64_req(p, "y2", "shape.line")?);
|
||||
let live = LiveShape::Line { a, b };
|
||||
// Lines are stroked, never filled (Illustrator ignores the fill for new open lines).
|
||||
// Lines are stroked and, unless Fill Line asks for the current fill, not filled.
|
||||
let fill = if switch_or(p, "fill", false, "shape.line")? { s.paint.fill.clone() } else { vectorcraft_color::Paint::None };
|
||||
let look = s.new_art_look(
|
||||
vectorcraft_color::Paint::None,
|
||||
fill,
|
||||
if s.paint.stroke.is_none() { vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK) } else { s.paint.stroke.clone() },
|
||||
s.paint.stroke_width.max(0.1),
|
||||
);
|
||||
|
||||
@@ -141,3 +141,17 @@ fn the_option_round_trips_with_the_preferences() {
|
||||
let old: Prefs = serde_json::from_value(json!({"keyboardIncrement": 2})).unwrap();
|
||||
assert!(old.new_art_basic);
|
||||
}
|
||||
|
||||
/// Line Segment Tool Options' Fill Line: `shape.line {fill: true}` gives the line the current
|
||||
/// fill; without it a new line has none.
|
||||
#[test]
|
||||
fn fill_line_gives_a_new_line_the_current_fill() {
|
||||
let mut s = session();
|
||||
run(&mut s, "paint.setFill", json!({"color": "#ff0000"}));
|
||||
let plain = NodeId(run(&mut s, "shape.line", json!({"x1": 10, "y1": 20, "x2": 200, "y2": 80}))["id"].as_u64().unwrap());
|
||||
let filled = NodeId(run(&mut s, "shape.line", json!({"x1": 10, "y1": 20, "x2": 200, "y2": 80, "fill": true}))["id"].as_u64().unwrap());
|
||||
assert_eq!(node(&s, plain).appearance.fill_paint(), Paint::None);
|
||||
assert_eq!(node(&s, filled).appearance.fill_paint(), s.paint.fill);
|
||||
assert_ne!(s.paint.fill, Paint::None);
|
||||
assert!(s.execute("shape.line", &json!({"x1": 0, "y1": 0, "x2": 1, "y2": 1, "fill": "yes"})).is_err());
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ fn an_unknown_key_is_named_with_the_nearest_accepted_one() {
|
||||
assert_eq!(refusal("select.all", json!({"zzz": 1})), "unknown parameter `zzz`; the command takes no parameters");
|
||||
assert_eq!(
|
||||
refusal("shape.line", json!({"x1": 0, "y1": 0, "x2": 5, "y2": 5, "colour": "red"})),
|
||||
"unknown parameter `colour`; the command takes: x1, y1, x2, y2"
|
||||
"unknown parameter `colour`; the command takes: x1, y1, x2, y2, fill"
|
||||
);
|
||||
for (id, ok) in [
|
||||
("file.new", json!({"width": 300, "title": "a", "artboardLayout": {"anything": 1}})),
|
||||
|
||||
@@ -41,6 +41,9 @@ pub struct FamilyTool {
|
||||
pub compound: bool,
|
||||
/// The grids take the current fill.
|
||||
pub fill: bool,
|
||||
/// The grids' Default Size (width, height): the size a click's dialog opens with (a drag sets
|
||||
/// its own). The Reference Point is `reference`.
|
||||
pub grid_size: (f64, f64),
|
||||
snap: DrawSnap,
|
||||
}
|
||||
|
||||
@@ -74,6 +77,7 @@ impl FamilyTool {
|
||||
frame: true,
|
||||
compound: false,
|
||||
fill: false,
|
||||
grid_size: (100.0, 100.0),
|
||||
snap: DrawSnap::default(),
|
||||
}
|
||||
}
|
||||
@@ -99,6 +103,20 @@ impl FamilyTool {
|
||||
matches!(self.id, "rectangularGrid" | "polarGrid")
|
||||
}
|
||||
|
||||
/// A grid's options a drag draws with: its dividers, skews and switches (the Default Size and
|
||||
/// Reference Point are for a click's dialog).
|
||||
fn grid_options(&self) -> Value {
|
||||
if self.id == "polarGrid" {
|
||||
json!({
|
||||
"concentric": self.rows, "radial": self.columns, "concentricSkew": self.skew[0], "radialSkew": self.skew[1], "compound": self.compound, "fill": self.fill,
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"rows": self.rows, "columns": self.columns, "rowSkew": self.skew[0], "columnSkew": self.skew[1], "frame": self.frame, "fill": self.fill,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The command for a drag from `start` to `p`.
|
||||
pub fn command(&self, start: Point, p: Point, m: Mods) -> (String, Value) {
|
||||
self.command_at(start, p, m, 0.0)
|
||||
@@ -117,7 +135,7 @@ impl FamilyTool {
|
||||
}
|
||||
// The grids: the dragged box (along the Constrain Angle's axes) with the tool's options.
|
||||
"rectangularGrid" | "polarGrid" => {
|
||||
let mut params = self.options();
|
||||
let mut params = self.grid_options();
|
||||
if let (Some(o), Value::Object(b)) = (params.as_object_mut(), box_params(start, p, m, base)) {
|
||||
o.extend(b);
|
||||
}
|
||||
@@ -252,12 +270,13 @@ impl Tool for FamilyTool {
|
||||
fn options(&self) -> Value {
|
||||
match self.id {
|
||||
"spiral" => json!({"decay": self.decay, "segments": self.segments, "clockwise": self.clockwise}),
|
||||
"rectangularGrid" => json!({
|
||||
"rows": self.rows, "columns": self.columns, "rowSkew": self.skew[0], "columnSkew": self.skew[1], "frame": self.frame, "fill": self.fill,
|
||||
}),
|
||||
"polarGrid" => json!({
|
||||
"concentric": self.rows, "radial": self.columns, "concentricSkew": self.skew[0], "radialSkew": self.skew[1], "compound": self.compound, "fill": self.fill,
|
||||
}),
|
||||
"rectangularGrid" | "polarGrid" => {
|
||||
let mut o = self.grid_options();
|
||||
o["width"] = json!(self.grid_size.0);
|
||||
o["height"] = json!(self.grid_size.1);
|
||||
o["reference"] = json!(self.reference);
|
||||
o
|
||||
}
|
||||
_ => json!({
|
||||
"closed": self.closed,
|
||||
"lengthX": self.length_x,
|
||||
@@ -292,6 +311,8 @@ impl Tool for FamilyTool {
|
||||
"frame" => self.frame = v.as_bool().unwrap_or(self.frame),
|
||||
"compound" => self.compound = v.as_bool().unwrap_or(self.compound),
|
||||
"fill" => self.fill = v.as_bool().unwrap_or(self.fill),
|
||||
"width" => self.grid_size.0 = grid_length(v, self.grid_size.0),
|
||||
"height" => self.grid_size.1 = grid_length(v, self.grid_size.1),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +323,12 @@ fn arc_length(v: &Value, current: f64) -> f64 {
|
||||
v.as_f64().filter(|n| n.is_finite()).map_or(current, |n| n.abs().max(1e-9))
|
||||
}
|
||||
|
||||
/// A grid's Default Size side from option value `v`: a finite positive length (at most 1e6 pt),
|
||||
/// else `current`.
|
||||
fn grid_length(v: &Value, current: f64) -> f64 {
|
||||
v.as_f64().filter(|n| n.is_finite() && *n > 0.0).map_or(current, |n| n.min(1e6))
|
||||
}
|
||||
|
||||
/// A grid divider skew from option value `v`: a finite number within the range, else `current`.
|
||||
fn grid_skew(v: &Value, current: f64) -> f64 {
|
||||
let max = vectorcraft_geom::shapes::GRID_SKEW_MAX;
|
||||
@@ -361,7 +388,9 @@ mod tests {
|
||||
t.set_option(key, &value);
|
||||
}
|
||||
let options = json!({"concentric": 2, "radial": 5, "concentricSkew": -40.0, "radialSkew": 500.0, "compound": true, "fill": true});
|
||||
assert_eq!(t.options(), options);
|
||||
let mut reported = options.clone();
|
||||
reported.as_object_mut().unwrap().extend(json!({"width": 100.0, "height": 100.0, "reference": 0}).as_object().unwrap().clone());
|
||||
assert_eq!(t.options(), reported);
|
||||
let (command, params) = t.command(Point::new(10.0, 20.0), Point::new(40.0, 80.0), Mods::default());
|
||||
assert_eq!(command, "shape.polarGrid");
|
||||
let mut want = options;
|
||||
@@ -370,7 +399,10 @@ mod tests {
|
||||
let mut t = FamilyTool::new("rectangularGrid");
|
||||
t.set_option("frame", &json!(false));
|
||||
t.set_option("rowSkew", &json!(12.5));
|
||||
assert_eq!(t.options(), json!({"rows": 5, "columns": 5, "rowSkew": 12.5, "columnSkew": 0.0, "frame": false, "fill": false}));
|
||||
assert_eq!(
|
||||
t.options(),
|
||||
json!({"rows": 5, "columns": 5, "rowSkew": 12.5, "columnSkew": 0.0, "frame": false, "fill": false, "width": 100.0, "height": 100.0, "reference": 0})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -475,4 +507,21 @@ mod tests {
|
||||
);
|
||||
assert!(t.overlays(&cx).iter().any(|o| matches!(o, Overlay::Label { text, .. } if text == "align")));
|
||||
}
|
||||
|
||||
/// A grid's Default Size and Reference Point are options for a click's dialog; the drag
|
||||
/// draws the box it is dragged to and leaves them out of its command.
|
||||
#[test]
|
||||
fn grid_default_size_is_an_option_the_drag_leaves_out() {
|
||||
for id in ["rectangularGrid", "polarGrid"] {
|
||||
let mut t = FamilyTool::new(id);
|
||||
for (k, v) in [("width", json!(150.0)), ("height", json!(-3.0)), ("reference", json!(4))] {
|
||||
t.set_option(k, &v);
|
||||
}
|
||||
let o = t.options();
|
||||
assert_eq!((o["width"].clone(), o["height"].clone(), o["reference"].clone()), (json!(150.0), json!(100.0), json!(4)), "{id}");
|
||||
let (_, p) = t.command(Point::new(0.0, 0.0), Point::new(40.0, 30.0), Mods::default());
|
||||
assert!(p.get("reference").is_none(), "{id}: {p}");
|
||||
assert_eq!((p["width"].clone(), p["height"].clone()), (json!(40.0), json!(30.0)), "{id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,12 @@ const ROWS: &[Row] = &[
|
||||
(&["puppetWarp"], None, &["showMesh", "expand"]),
|
||||
(&["arc"], None, &["closed", "lengthX", "lengthY", "reference", "baseAlong", "slope", "fillArc"]),
|
||||
(&["spiral"], None, &["decay", "segments", "clockwise"]),
|
||||
(&["rectangularGrid"], None, &["rows", "columns", "rowSkew", "columnSkew", "frame", "fill"]),
|
||||
(&["polarGrid"], None, &["concentric", "radial", "concentricSkew", "radialSkew", "compound", "fill"]),
|
||||
// The grids' Tool Options: the dividers and switches the drag uses, the Default Size and
|
||||
// Reference Point a click's dialog opens with.
|
||||
(&["rectangularGrid"], None, &["rows", "columns", "rowSkew", "columnSkew", "frame", "fill", "width", "height", "reference"]),
|
||||
(&["polarGrid"], None, &["concentric", "radial", "concentricSkew", "radialSkew", "compound", "fill", "width", "height", "reference"]),
|
||||
// Line Segment Tool Options: the Length and Angle a click's dialog opens with, Fill Line.
|
||||
(&["lineSegment"], None, &["length", "angle", "fillLine"]),
|
||||
(&["pencil", "paintbrush"], None, &["fidelity", "fill", "keepSelected", "editWithin", "closeWithin"]),
|
||||
// Pencil Tool Options' Alt key toggles to Smooth Tool (the Paintbrush has no such option).
|
||||
(&["pencil"], None, &["altSmooth"]),
|
||||
|
||||
@@ -26,6 +26,11 @@ pub struct ShapeTool {
|
||||
pub star_ratio: f64,
|
||||
/// The star's inner radius held while Ctrl/Cmd is down in a drag.
|
||||
inner: Option<f64>,
|
||||
/// Line Segment Tool Options: the length and angle (degrees) a click's dialog opens with, and
|
||||
/// Fill Line (new lines take the current fill).
|
||||
pub line_length: f64,
|
||||
pub line_angle: f64,
|
||||
pub fill_line: bool,
|
||||
snap: DrawSnap,
|
||||
}
|
||||
|
||||
@@ -59,6 +64,9 @@ impl ShapeTool {
|
||||
star_ratio: 0.5,
|
||||
inner: None,
|
||||
snap: DrawSnap::default(),
|
||||
line_length: 100.0,
|
||||
line_angle: 0.0,
|
||||
fill_line: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +139,11 @@ impl ShapeTool {
|
||||
}
|
||||
"lineSegment" => {
|
||||
let (a, b) = if m.alt { (start - (p - start), p) } else { (start, p) };
|
||||
("shape.line".into(), json!({ "x1": a.x, "y1": a.y, "x2": b.x, "y2": b.y }))
|
||||
let mut v = json!({ "x1": a.x, "y1": a.y, "x2": b.x, "y2": b.y });
|
||||
if self.fill_line {
|
||||
v["fill"] = json!(true);
|
||||
}
|
||||
("shape.line".into(), v)
|
||||
}
|
||||
_ => {
|
||||
let cmd = if self.id == "ellipse" { "shape.ellipse" } else { "shape.rectangle" };
|
||||
@@ -314,14 +326,26 @@ impl Tool for ShapeTool {
|
||||
Cursor::Crosshair
|
||||
}
|
||||
fn options(&self) -> Value {
|
||||
json!({ "sides": self.sides, "points": self.points, "cornerRadius": self.corner_radius, "starRatio": self.star_ratio })
|
||||
json!({
|
||||
"sides": self.sides,
|
||||
"points": self.points,
|
||||
"cornerRadius": self.corner_radius,
|
||||
"starRatio": self.star_ratio,
|
||||
"length": self.line_length,
|
||||
"angle": self.line_angle,
|
||||
"fillLine": self.fill_line,
|
||||
})
|
||||
}
|
||||
fn set_option(&mut self, key: &str, v: &Value) {
|
||||
let finite = v.as_f64().filter(|n| n.is_finite());
|
||||
match key {
|
||||
"sides" => self.sides = v.as_u64().unwrap_or(6).clamp(3, 1000) as u32,
|
||||
"points" => self.points = v.as_u64().unwrap_or(5).clamp(3, 1000) as u32,
|
||||
"cornerRadius" => self.corner_radius = v.as_f64().unwrap_or(12.0).max(0.0),
|
||||
"starRatio" => self.star_ratio = v.as_f64().unwrap_or(0.5).clamp(0.01, 1.0),
|
||||
"length" => self.line_length = finite.map_or(self.line_length, |n| n.abs().min(1e6)),
|
||||
"angle" => self.line_angle = finite.map_or(self.line_angle, |n| n.rem_euclid(360.0)),
|
||||
"fillLine" => self.fill_line = v.as_bool().unwrap_or(self.fill_line),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -532,4 +556,22 @@ mod tests {
|
||||
let to = Point::new(200.0, 300.0);
|
||||
assert!(t.overlays(&cx).iter().any(|o| matches!(o, Overlay::Measure { text, .. } if text == &cx.segment_measure_label(from, to))));
|
||||
}
|
||||
|
||||
/// Line Segment Tool Options: Length and Angle are options (for a click's dialog), and Fill
|
||||
/// Line makes the drag's line take the current fill.
|
||||
#[test]
|
||||
fn line_segment_options_and_fill_line() {
|
||||
let mut t = ShapeTool::new("lineSegment");
|
||||
let (a, b) = (Point::new(0.0, 0.0), Point::new(30.0, 40.0));
|
||||
assert!(t.command(a, b, Mods::default()).1.get("fill").is_none());
|
||||
for (k, v) in [("length", json!(-80.0)), ("angle", json!(-90.0)), ("fillLine", json!(true))] {
|
||||
t.set_option(k, &v);
|
||||
}
|
||||
t.set_option("length", &json!(f64::NAN));
|
||||
assert_eq!(
|
||||
(t.options()["length"].clone(), t.options()["angle"].clone(), t.options()["fillLine"].clone()),
|
||||
(json!(80.0), json!(270.0), json!(true))
|
||||
);
|
||||
assert_eq!(t.command(a, b, Mods::default()).1["fill"], json!(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ fn suffix(kind: &str, key: &str) -> Option<&'static str> {
|
||||
fn own_rows(kind: &str) -> &'static [&'static str] {
|
||||
match kind {
|
||||
"artboardOptions" => &["color"],
|
||||
// The grids' Reference Point is their 9-point locator.
|
||||
"rectangularGrid" | "polarGrid" => &["reference"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ pub mod save_style_library;
|
||||
pub mod save_swatch_library;
|
||||
pub mod script_check;
|
||||
pub mod shape_builder_options;
|
||||
mod shapes;
|
||||
pub(crate) mod shapes;
|
||||
pub mod shortcut_import;
|
||||
pub(crate) mod simplify_slider;
|
||||
pub mod slices;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
//! Shape size dialogs (a click with a shape tool): Rectangle, Rounded Rectangle, Ellipse, Polygon,
|
||||
//! Star, Line Segment, Arc and Spiral tool options, and the two grids (size, divider counts and skews, switches).
|
||||
//! The Line Segment, Arc and grid dialogs are also their tools' Tool Options (a double-click on the
|
||||
//! tool, [`open_options`]): OK then sets the options the tool's next drag and click use, and draws
|
||||
//! nothing.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::Unit;
|
||||
@@ -11,23 +14,97 @@ use crate::panels::transform::artboard_rect;
|
||||
use crate::state::Dialog;
|
||||
use crate::widgets;
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec { heading: |d| title(&d.kind).into(), body, confirm, ..DialogSpec::FORM };
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec { heading: |d| title(d).into(), body, confirm, ..DialogSpec::FORM };
|
||||
|
||||
fn title(kind: &str) -> &'static str {
|
||||
match kind {
|
||||
/// The field that marks a dialog opened as its tool's Tool Options rather than by a click.
|
||||
const TOOL_OPTIONS: &str = "__toolOptions";
|
||||
|
||||
/// The tools whose Tool Options are this dialog.
|
||||
pub(crate) const OPTION_TOOLS: [&str; 4] = ["rectangularGrid", "polarGrid", "arc", "lineSegment"];
|
||||
|
||||
fn title(d: &Dialog) -> &'static str {
|
||||
let options = d.bool(TOOL_OPTIONS);
|
||||
match d.kind.as_str() {
|
||||
"rectangle" => tl!("Rectangle"),
|
||||
"roundedRectangle" => tl!("Rounded Rectangle"),
|
||||
"ellipse" => tl!("Ellipse"),
|
||||
"polygon" => tl!("Polygon"),
|
||||
"star" => tl!("Star"),
|
||||
"spiral" => tl!("Spiral"),
|
||||
"rectangularGrid" if options => tl!("Rectangular Grid Tool Options"),
|
||||
"rectangularGrid" => tl!("Rectangular Grid"),
|
||||
"polarGrid" if options => tl!("Polar Grid Tool Options"),
|
||||
"polarGrid" => tl!("Polar Grid"),
|
||||
"arc" => tl!("Arc Segment Tool Options"),
|
||||
_ => tl!("Line Segment Tool Options"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open `tool`'s Tool Options (one of [`OPTION_TOOLS`]), as a double-click on the tool does
|
||||
/// (<https://helpx.adobe.com/illustrator/using/tool-techniques/rectangular-grid-tool.html>,
|
||||
/// <https://helpx.adobe.com/illustrator/using/tool-techniques/arc-tool.html>): the click dialog's
|
||||
/// fields at the tool's options, with no click to draw at.
|
||||
pub(crate) fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<Value, String> {
|
||||
if !OPTION_TOOLS.contains(&tool) {
|
||||
return Err(format!("the {tool} tool has no options"));
|
||||
}
|
||||
super::open_tool_dialog(app, tool, json!({}));
|
||||
let d = app.ui.dialog.as_mut().filter(|d| d.kind == tool).ok_or_else(|| format!("the {tool} tool has no options"))?;
|
||||
for key in ["x", "y", "__anchorX", "__anchorY"] {
|
||||
d.fields.remove(key);
|
||||
}
|
||||
d.fields.insert(TOOL_OPTIONS.into(), json!(true));
|
||||
Ok(json!({ "dialog": tool }))
|
||||
}
|
||||
|
||||
/// What OK sets as the options of `d`'s tool ([`OPTION_TOOLS`]), from its fields: a grid's are its
|
||||
/// switches as they are, its typed fields as numbers, its Default Size and Reference Point.
|
||||
fn tool_options(d: &Dialog) -> Value {
|
||||
match d.kind.as_str() {
|
||||
"lineSegment" => {
|
||||
let (length, angle) = line_length_angle(d);
|
||||
json!({"length": length, "angle": angle, "fillLine": d.bool("fillLine")})
|
||||
}
|
||||
"arc" => arc_options(d),
|
||||
kind => {
|
||||
let (mut o, (counts, skews)) = (form::params(d), grid_keys(kind));
|
||||
if let Some(m) = o.as_object_mut() {
|
||||
m.remove("x");
|
||||
m.remove("y");
|
||||
}
|
||||
for (count, skew) in counts.into_iter().zip(skews) {
|
||||
o[count] = json!((d.f64(count, 5.0) as u64).min(999));
|
||||
o[skew] = json!(d.f64(skew, 0.0));
|
||||
}
|
||||
o["width"] = json!(d.f64("width", 100.0));
|
||||
o["height"] = json!(d.f64("height", 100.0));
|
||||
o["reference"] = json!(grid_reference(d));
|
||||
o
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Line Segment dialog's length and angle (degrees), a negative length turned into the same
|
||||
/// line drawn the other way: -80 pt at 0° is 80 pt at 180°. The line a click draws and the options
|
||||
/// OK keeps both use it, so the next line goes the way this one went.
|
||||
fn line_length_angle(d: &Dialog) -> (f64, f64) {
|
||||
let (length, angle) = (d.f64("length", 100.0), d.f64("angle", 0.0));
|
||||
if length < 0.0 { (-length, (angle + 180.0).rem_euclid(360.0)) } else { (length, angle) }
|
||||
}
|
||||
|
||||
/// The Arc tool's options from its dialog's fields.
|
||||
fn arc_options(d: &Dialog) -> Value {
|
||||
json!({
|
||||
"closed": d.str("arcType").eq_ignore_ascii_case("closed"),
|
||||
"lengthX": d.f64("lengthX", 100.0),
|
||||
"lengthY": d.f64("lengthY", 100.0),
|
||||
"reference": arc_reference(d),
|
||||
"baseAlong": d.str("baseAlong"),
|
||||
"slope": d.f64("slope", 50.0),
|
||||
"fillArc": d.bool("fillArc"),
|
||||
})
|
||||
}
|
||||
|
||||
/// The grids' divider counts and skews: (rows / concentric, columns / radial).
|
||||
fn grid_keys(kind: &str) -> ([&'static str; 2], [&'static str; 2]) {
|
||||
if kind == "polarGrid" { (["concentric", "radial"], ["concentricSkew", "radialSkew"]) } else { (["rows", "columns"], ["rowSkew", "columnSkew"]) }
|
||||
@@ -109,6 +186,11 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
let own = match d.kind.as_str() {
|
||||
"rectangularGrid" => ("frame", tl!("Use Outside Rectangle As Frame")),
|
||||
"polarGrid" => ("compound", tl!("Create Compound Path From Ellipses")),
|
||||
"lineSegment" => {
|
||||
ui.add_space(8.0);
|
||||
form::check(ui, d, "fillLine", tl!("Fill Line"));
|
||||
return false;
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
ui.add_space(8.0);
|
||||
@@ -173,6 +255,12 @@ fn box_origin(d: &Dialog) -> (f64, f64) {
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
// Tool Options: the options the tool's next drag and click use; nothing is drawn.
|
||||
if d.bool(TOOL_OPTIONS) {
|
||||
let r = app.run("tool.setOption", json!({"tool": d.kind, "values": tool_options(d)}));
|
||||
app.ui.dialog = None;
|
||||
return r;
|
||||
}
|
||||
let count = match d.kind.as_str() {
|
||||
"polygon" => Some(("sides", 6.0, 3.0)),
|
||||
"star" => Some(("points", 5.0, 2.0)),
|
||||
@@ -184,19 +272,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
values[key] = json!(d.f64(key, default).clamp(min, 1000.0) as u64);
|
||||
values
|
||||
})
|
||||
.or_else(|| {
|
||||
(d.kind == "arc").then(|| {
|
||||
json!({
|
||||
"closed": d.str("arcType").eq_ignore_ascii_case("closed"),
|
||||
"lengthX": d.f64("lengthX", 100.0),
|
||||
"lengthY": d.f64("lengthY", 100.0),
|
||||
"reference": arc_reference(d),
|
||||
"baseAlong": d.str("baseAlong"),
|
||||
"slope": d.f64("slope", 50.0),
|
||||
"fillArc": d.bool("fillArc"),
|
||||
})
|
||||
})
|
||||
});
|
||||
.or_else(|| OPTION_TOOLS.contains(&d.kind.as_str()).then(|| tool_options(d)));
|
||||
let (x, y) = (d.f64("x", 0.0), d.f64("y", 0.0));
|
||||
let (bx, by) = box_origin(d);
|
||||
let (id, params) = match d.kind.as_str() {
|
||||
@@ -231,8 +307,13 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
(if kind == "polarGrid" { "shape.polarGrid" } else { "shape.rectangularGrid" }, p)
|
||||
}
|
||||
"lineSegment" => {
|
||||
let (l, a) = (d.f64("length", 100.0), d.f64("angle", 0.0).to_radians());
|
||||
("shape.line", json!({"x1": x, "y1": y, "x2": x + l * a.cos(), "y2": y - l * a.sin()}))
|
||||
let (l, a) = line_length_angle(d);
|
||||
let a = a.to_radians();
|
||||
let mut p = json!({"x1": x, "y1": y, "x2": x + l * a.cos(), "y2": y - l * a.sin()});
|
||||
if d.bool("fillLine") {
|
||||
p["fill"] = json!(true);
|
||||
}
|
||||
("shape.line", p)
|
||||
}
|
||||
"arc" => {
|
||||
let anchor = Point::new(d.f64("__anchorX", x), d.f64("__anchorY", y));
|
||||
@@ -259,7 +340,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
// On the active plane while the perspective grid shows, sized in plane units from the click;
|
||||
// else along General › Constrain Angle, turned about the point the dialog places at the click
|
||||
// (a grid's reference point, the other shapes' top-left corner or centre).
|
||||
let options = options.or_else(|| matches!(d.kind.as_str(), "spiral" | "rectangularGrid" | "polarGrid").then(|| params.clone()));
|
||||
let options = options.or_else(|| (d.kind == "spiral").then(|| params.clone()));
|
||||
let result = match vectorcraft_engine::perspective_click(&app.session, id, ¶ms, vectorcraft_geom::Point::new(x, y)) {
|
||||
Some((id, params)) => run_and_close(app, &id, params),
|
||||
None => {
|
||||
@@ -314,4 +395,32 @@ mod tests {
|
||||
assert_eq!(box_origin(&fields(false)), (100.0, 50.0));
|
||||
assert_eq!(box_origin(&fields(true)), (80.0, 40.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_negative_line_length_draws_and_keeps_the_same_direction() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
app.run("tool.select", json!({"tool": "lineSegment"})).unwrap();
|
||||
let line_bounds = |app: &VectorcraftApp, i: usize| {
|
||||
let doc = &app.session.doc().unwrap().doc;
|
||||
let n = &doc.layers[0].children().unwrap()[i];
|
||||
n.path_data().and_then(|p| p.bounds()).expect("line bounds")
|
||||
};
|
||||
|
||||
super::super::open_tool_dialog(&mut app, "lineSegment", json!({"x": 100.0, "y": 60.0}));
|
||||
let d = app.ui.dialog.as_mut().expect("Line Segment dialog");
|
||||
d.fields.insert("length".into(), json!(-80.0));
|
||||
d.fields.insert("angle".into(), json!(0.0));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let first = line_bounds(&app, 0);
|
||||
assert!((first.x0 - 20.0).abs() < 1e-6 && (first.x1 - 100.0).abs() < 1e-6, "{first:?}");
|
||||
let kept = app.session.tool_options_of("lineSegment");
|
||||
assert_eq!((kept["length"].as_f64(), kept["angle"].as_f64()), (Some(80.0), Some(180.0)));
|
||||
|
||||
// The next click opens at what was kept and draws the same way.
|
||||
super::super::open_tool_dialog(&mut app, "lineSegment", json!({"x": 100.0, "y": 120.0}));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let second = line_bounds(&app, 1);
|
||||
assert!((second.x0 - 20.0).abs() < 1e-6 && (second.x1 - 100.0).abs() < 1e-6, "{second:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1842,7 +1842,10 @@ fn polar_grid_dialog_edits_skews_compound_and_fill() {
|
||||
assert!((widths[0] - 100.0).abs() < 1e-6 && (widths[1] - 93.75).abs() < 1e-6, "outward: {widths:?}");
|
||||
let tips: Vec<f64> = children[3..].iter().map(|n| n.path_data().unwrap().subpaths[0].anchors[1].p.x).collect();
|
||||
assert!(tips.iter().all(|x| *x > 60.0), "right of the top divider: {tips:?}");
|
||||
let saved = json!({"concentric": 3, "radial": 4, "concentricSkew": 100.0, "radialSkew": 500.0, "compound": true, "fill": true});
|
||||
let saved = json!({
|
||||
"concentric": 3, "radial": 4, "concentricSkew": 100.0, "radialSkew": 500.0, "compound": true, "fill": true,
|
||||
"width": 100.0, "height": 100.0, "reference": 0,
|
||||
});
|
||||
assert_eq!(app.session.tool_options_of("polarGrid"), saved);
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ fn rectangular_grid_creates_exact_dividers_as_one_undo_redo_step() {
|
||||
assert_eq!(app.session.doc().unwrap().doc, before.doc);
|
||||
assert_eq!(app.session.doc().unwrap().selection.objects, before.selected);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), before.undo);
|
||||
let saved = json!({"rows": 2, "columns": 3, "rowSkew": 0.0, "columnSkew": 0.0, "frame": true, "fill": false});
|
||||
let saved = json!({"rows": 2, "columns": 3, "rowSkew": 0.0, "columnSkew": 0.0, "frame": true, "fill": false, "width": 120.0, "height": 60.0, "reference": 0});
|
||||
assert_eq!(app.session.tool_options_of(KIND), saved, "undo does not undo the saved tool settings");
|
||||
app.run("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(app.session.doc().unwrap().doc, created);
|
||||
@@ -380,7 +380,7 @@ fn rectangular_grid_dialog_edits_skews_frame_and_fill() {
|
||||
}
|
||||
let x = paths[7].subpaths[0].anchors[0].p.x;
|
||||
assert!(x > 10.0 && x < 30.0, "toward the left: {x}");
|
||||
let saved = json!({"rows": 3, "columns": 1, "rowSkew": 100.0, "columnSkew": -250.0, "frame": false, "fill": true});
|
||||
let saved = json!({"rows": 3, "columns": 1, "rowSkew": 100.0, "columnSkew": -250.0, "frame": false, "fill": true, "width": 100.0, "height": 100.0, "reference": 0});
|
||||
assert_eq!(app.session.tool_options_of(KIND), saved);
|
||||
// A drag draws with the same options.
|
||||
pointer(&mut app, PointerKind::Down, Point::new(300.0, 300.0));
|
||||
|
||||
@@ -19,7 +19,15 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) {
|
||||
"star" => {
|
||||
Dialog::new(kind, json!({"x": x, "y": y, "radius1": "50 pt", "radius2": "25 pt", "points": app.session.tool_options_of(kind)["points"]}))
|
||||
}
|
||||
"lineSegment" => Dialog::new(kind, json!({"x": x, "y": y, "length": "100 pt", "angle": 0})),
|
||||
// The Line Segment Tool Options: Length, Angle and Fill Line as the tool keeps them.
|
||||
"lineSegment" => {
|
||||
let o = app.session.tool_options_of(kind);
|
||||
let fill = o.get("fillLine").and_then(Value::as_bool).unwrap_or(false);
|
||||
Dialog::new(
|
||||
kind,
|
||||
json!({"x": x, "y": y, "length": pt(o.get("length")), "angle": o.get("angle").cloned().unwrap_or(json!(0)), "fillLine": fill}),
|
||||
)
|
||||
}
|
||||
"spiral" => {
|
||||
let options = app.session.tool_options_of(kind);
|
||||
Dialog::new(
|
||||
@@ -56,16 +64,20 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) {
|
||||
super::shapes::arc_pin_to_anchor(&mut d);
|
||||
d
|
||||
}
|
||||
// The grids: the tool's stored options (divider counts and skews, switches) at the default size.
|
||||
// The grids: the tool's stored options (Default Size, Reference Point, divider counts and
|
||||
// skews, switches).
|
||||
"rectangularGrid" | "polarGrid" => {
|
||||
let mut obj = match app.session.tool_options_of(kind) {
|
||||
Value::Object(o) => o,
|
||||
_ => serde_json::Map::new(),
|
||||
};
|
||||
for (key, value) in [("__anchorX", json!(x)), ("__anchorY", json!(y)), ("width", json!("100 pt")), ("height", json!("100 pt"))] {
|
||||
obj.insert(key.into(), value);
|
||||
}
|
||||
obj.insert("reference".into(), json!(0));
|
||||
obj.insert("__anchorX".into(), json!(x));
|
||||
obj.insert("__anchorY".into(), json!(y));
|
||||
// The Default Size in points, as typed lengths.
|
||||
let (w, h) = (pt(obj.get("width")), pt(obj.get("height")));
|
||||
obj.insert("width".into(), w);
|
||||
obj.insert("height".into(), h);
|
||||
obj.entry("reference").or_insert(json!(0));
|
||||
let mut d = Dialog::new(kind, Value::Object(obj));
|
||||
grid_pin_to_anchor(&mut d);
|
||||
d
|
||||
@@ -150,3 +162,8 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) {
|
||||
};
|
||||
app.ui.dialog = Some(d);
|
||||
}
|
||||
|
||||
/// A stored length option (points) as a dialog's typed length, `"N pt"`; 100 pt when unset.
|
||||
fn pt(v: Option<&Value>) -> Value {
|
||||
json!(format!("{} pt", v.and_then(Value::as_f64).filter(|n| n.is_finite()).unwrap_or(100.0)))
|
||||
}
|
||||
|
||||
@@ -5412,3 +5412,6 @@ axis Y Axis Y ос
|
||||
The plug-in is computing… Приставката изчислява…
|
||||
Inspect Plug-in Проверка на приставка
|
||||
Cancel Plug-in Run Отказ от изпълнението на приставката
|
||||
Rectangular Grid Tool Options Опции на инструмента „Правоъгълна мрежа“
|
||||
Polar Grid Tool Options Опции на инструмента „Полярна мрежа“
|
||||
Fill Line Запълване на линия
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5463,3 +5463,6 @@ axis Y Axis osa Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` živý efekt běží během vykreslování dokumentu, takže nemůže být `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out exportní zásuvný modul dostává vždy rozpočet exportu: klíč `budget` vynechte
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ žádá o velký výpočetní rozpočet (každé spuštění až {minutes} minut a {times}krát víc instrukcí než obvykle): nainstalujte ho s `allowHeavy: true`, jakmile uživatel souhlasí
|
||||
Rectangular Grid Tool Options Možnosti nástroje Obdélníková mřížka
|
||||
Polar Grid Tool Options Možnosti nástroje Polární mřížka
|
||||
Fill Line Výplň úsečky
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5428,3 +5428,6 @@ axis Y Axis Y-akse
|
||||
The plug-in is computing… Plug-in'en regner…
|
||||
Inspect Plug-in Undersøg plug-in
|
||||
Cancel Plug-in Run Annullér plug-in-kørsel
|
||||
Rectangular Grid Tool Options Indstillinger for rektangulært gitterværktøj
|
||||
Polar Grid Tool Options Indstillinger for polært gitterværktøj
|
||||
Fill Line Udfyld linje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5046,3 +5046,6 @@ axis Y Axis Y-Achse
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` ein Live-Effekt läuft, während das Dokument gezeichnet wird, daher kann er nicht `heavy` sein
|
||||
@msg an exporter always gets an export's budget: leave `budget` out ein Export-Plug-in erhält immer das Budget eines Exports: Lassen Sie `budget` weg
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ verlangt ein großes Rechenbudget (jede Ausführung bis zu {minutes} Minuten und {times}-mal so viele Anweisungen wie üblich): Installieren Sie es mit `allowHeavy: true`, sobald der Benutzer zustimmt
|
||||
Rectangular Grid Tool Options Optionen für das Rechteckiges-Raster-Werkzeug
|
||||
Polar Grid Tool Options Optionen für das Radiales-Raster-Werkzeug
|
||||
Fill Line Linie füllen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5415,3 +5415,6 @@ axis Y Axis Άξονας Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` ένα δυναμικό εφέ εκτελείται κατά τη σχεδίαση του εγγράφου, οπότε δεν μπορεί να είναι `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out ένα πρόσθετο εξαγωγής παίρνει πάντα τον προϋπολογισμό μιας εξαγωγής: παραλείψτε το `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees το «{_1}» ζητά μεγάλο υπολογιστικό προϋπολογισμό (κάθε εκτέλεση έως {minutes} λεπτά και {times} φορές τις συνήθεις εντολές): εγκαταστήστε το με `allowHeavy: true` μόλις συμφωνήσει ο χρήστης
|
||||
Rectangular Grid Tool Options Επιλογές εργαλείου ορθογώνιου πλέγματος
|
||||
Polar Grid Tool Options Επιλογές εργαλείου πολικού πλέγματος
|
||||
Fill Line Γέμισμα γραμμής
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2049 and column 19.
|
@@ -5405,3 +5405,6 @@ axis Y Axis Eje Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` un efecto interactivo se ejecuta mientras se dibuja el documento, así que no puede ser `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out un plugin de exportación siempre recibe el presupuesto de una exportación: omite `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” pide un presupuesto de cálculo grande (cada ejecución hasta {minutes} minutos y {times} veces las instrucciones habituales): instálalo con `allowHeavy: true` cuando el usuario lo acepte
|
||||
Rectangular Grid Tool Options Opciones de la herramienta Cuadrícula rectangular
|
||||
Polar Grid Tool Options Opciones de la herramienta Cuadrícula polar
|
||||
Fill Line Rellenar línea
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5494,3 +5494,6 @@ axis Y Axis Y-akseli
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` live-tehoste suoritetaan asiakirjaa piirrettäessä, joten se ei voi olla `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out vientilaajennus saa aina viennin budjetin: jätä `budget` pois
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees ”{_1}” pyytää suurta laskentabudjettia (jokainen ajo enintään {minutes} minuuttia ja {times} kertaa tavallinen määrä käskyjä): asenna se asetuksella `allowHeavy: true`, kun käyttäjä hyväksyy
|
||||
Rectangular Grid Tool Options Suorakulmaisen ruudukon työkalun asetukset
|
||||
Polar Grid Tool Options Napakoordinaattiruudukon työkalun asetukset
|
||||
Fill Line Täytä viiva
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5462,3 +5462,6 @@ axis Y Axis Axe Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` un effet dynamique s’exécute pendant le tracé du document, il ne peut donc pas être `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out un module externe d’exportation reçoit toujours le budget d’une exportation : omettez `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees « {_1} » demande un budget de calcul important (chaque exécution jusqu’à {minutes} minutes et {times} fois les instructions habituelles) : installez-le avec `allowHeavy: true` une fois que l’utilisateur l’accepte
|
||||
Rectangular Grid Tool Options Options de l’outil Grille rectangulaire
|
||||
Polar Grid Tool Options Options de l’outil Grille à coordonnées polaires
|
||||
Fill Line Remplir le trait
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5449,3 +5449,6 @@ axis Y Axis Y tengely
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` az élő effektus a dokumentum rajzolása közben fut, ezért nem lehet `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out az exportáló bővítmény mindig az exportálás keretét kapja: hagyd ki a `budget` kulcsot
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees a(z) „{_1}” nagy számítási keretet kér (futásonként legfeljebb {minutes} perc és a szokásos utasításszám {times}-szorosa): telepítsd az `allowHeavy: true` megadásával, miután a felhasználó beleegyezett
|
||||
Rectangular Grid Tool Options Téglalaprács beállításai
|
||||
Polar Grid Tool Options Polárrács beállításai
|
||||
Fill Line Szakasz kitöltése
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5428,3 +5428,6 @@ axis Y Axis Sumbu Y
|
||||
The plug-in is computing… Plug-in sedang menghitung…
|
||||
Inspect Plug-in Periksa plug-in
|
||||
Cancel Plug-in Run Batalkan jalannya plug-in
|
||||
Rectangular Grid Tool Options Opsi alat kisi persegi panjang
|
||||
Polar Grid Tool Options Opsi alat kisi polar
|
||||
Fill Line Isi garis
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3820 and column 19.
|
@@ -5080,3 +5080,6 @@ axis Y Axis Asse Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` un effetto dinamico viene eseguito mentre il documento viene disegnato, quindi non può essere `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out un plug-in di esportazione riceve sempre il budget di un’esportazione: ometti `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” chiede un budget di calcolo elevato (ogni esecuzione fino a {minutes} minuti e {times} volte le istruzioni consuete): installalo con `allowHeavy: true` quando l’utente acconsente
|
||||
Rectangular Grid Tool Options Opzioni strumento Griglia rettangolare
|
||||
Polar Grid Tool Options Opzioni strumento Griglia polare
|
||||
Fill Line Riempi linea
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5142,3 +5142,6 @@ axis Y Axis Y 軸
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` ライブ効果はドキュメントの描画中に実行されるため、`heavy` にはできません
|
||||
@msg an exporter always gets an export's budget: leave `budget` out 書き出しプラグインには常に書き出しの予算が割り当てられます。`budget` は省略してください
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees 「{_1}」は大きな計算予算を求めています (1 回の実行につき最大 {minutes} 分、通常の {times} 倍の命令)。ユーザーが同意したら `allowHeavy: true` を指定してインストールしてください
|
||||
Rectangular Grid Tool Options 長方形グリッドツールオプション
|
||||
Polar Grid Tool Options 同心円グリッドツールオプション
|
||||
Fill Line 線を塗りつぶす
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5379,3 +5379,6 @@ axis Y Axis Y ღერძი
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` ცოცხალი ეფექტი დოკუმენტის დახატვისას ეშვება, ამიტომ ის ვერ იქნება `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out ექსპორტის პლაგინი ყოველთვის ექსპორტის ბიუჯეტს იღებს: გამოტოვეთ `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ დიდ გამოთვლით ბიუჯეტს ითხოვს (თითო გაშვება {minutes} წუთამდე და ჩვეულებრივზე {times}-ჯერ მეტი ინსტრუქცია): მომხმარებლის თანხმობის შემდეგ დააყენეთ ის `allowHeavy: true` პარამეტრით
|
||||
Rectangular Grid Tool Options მართკუთხა ბადის ხელსაწყოს პარამეტრები
|
||||
Polar Grid Tool Options პოლარული ბადის ხელსაწყოს პარამეტრები
|
||||
Fill Line ხაზის შევსება
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5043,3 +5043,6 @@ axis Y Axis Y축
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` 라이브 효과는 문서를 그리는 동안 실행되므로 `heavy`일 수 없습니다
|
||||
@msg an exporter always gets an export's budget: leave `budget` out 내보내기 플러그인은 항상 내보내기 예산을 받습니다. `budget`은 생략하세요
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}”은(는) 큰 계산 예산을 요청합니다(실행당 최대 {minutes}분, 평소의 {times}배 명령어). 사용자가 동의하면 `allowHeavy: true`로 설치하세요
|
||||
Rectangular Grid Tool Options 사각형 격자 도구 옵션
|
||||
Polar Grid Tool Options 극좌표 격자 도구 옵션
|
||||
Fill Line 선 채우기
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5471,3 +5471,6 @@ axis Y Axis Y-as
|
||||
The plug-in is computing… De plug-in is aan het rekenen…
|
||||
Inspect Plug-in Plug-in inspecteren
|
||||
Cancel Plug-in Run Plug-in-run annuleren
|
||||
Rectangular Grid Tool Options Opties rechthoekig-rastergereedschap
|
||||
Polar Grid Tool Options Opties polair-rastergereedschap
|
||||
Fill Line Lijn vullen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5315,3 +5315,6 @@ axis Y Axis Oś Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` efekt na żywo działa podczas rysowania dokumentu, więc nie może być `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out wtyczka eksportu zawsze dostaje budżet eksportu: pomiń `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}” prosi o duży budżet obliczeń (każde uruchomienie do {minutes} minut i {times} razy więcej instrukcji niż zwykle): zainstaluj wtyczkę z `allowHeavy: true`, gdy użytkownik się zgodzi
|
||||
Rectangular Grid Tool Options Opcje narzędzia Siatka prostokątna
|
||||
Polar Grid Tool Options Opcje narzędzia Siatka biegunowa
|
||||
Fill Line Wypełnij odcinek
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5380,3 +5380,6 @@ axis Y Axis Eixo Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` um efeito dinâmico é executado enquanto o documento é desenhado, então não pode ser `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out um plug-in de exportação sempre recebe o orçamento de uma exportação: omita `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” pede um orçamento de processamento grande (cada execução até {minutes} minutos e {times} vezes as instruções habituais): instale-o com `allowHeavy: true` depois que o usuário concordar
|
||||
Rectangular Grid Tool Options Opções da Ferramenta Grade Retangular
|
||||
Polar Grid Tool Options Opções da Ferramenta Grade Polar
|
||||
Fill Line Preencher linha
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -5076,3 +5076,6 @@ axis Y Axis Ось Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` динамический эффект выполняется во время отрисовки документа, поэтому он не может быть `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out модуль экспорта всегда получает бюджет экспорта: не указывайте `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees «{_1}» запрашивает большой вычислительный бюджет (каждый запуск до {minutes} минут и в {times} раз больше инструкций, чем обычно): установите его с `allowHeavy: true`, когда пользователь согласится
|
||||
Rectangular Grid Tool Options Параметры инструмента «Прямоугольная сетка»
|
||||
Polar Grid Tool Options Параметры инструмента «Полярная сетка»
|
||||
Fill Line Заливка отрезка
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5419,3 +5419,6 @@ axis Y Axis Y-axel
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` en live-effekt körs medan dokumentet ritas, så den kan inte vara `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out ett exportinsticksprogram får alltid en exports budget: utelämna `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees ”{_1}” begär en stor beräkningsbudget (varje körning upp till {minutes} minuter och {times} gånger de vanliga instruktionerna): installera det med `allowHeavy: true` när användaren har godkänt
|
||||
Rectangular Grid Tool Options Alternativ för verktyget för rektangulärt rutnät
|
||||
Polar Grid Tool Options Alternativ för verktyget för polärt rutnät
|
||||
Fill Line Fyll linje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5426,3 +5426,6 @@ axis Y Axis Y ekseni
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` canlı efekt belge çizilirken çalışır, bu yüzden `heavy` olamaz
|
||||
@msg an exporter always gets an export's budget: leave `budget` out dışa aktarma eklentisi her zaman bir dışa aktarmanın bütçesini alır: `budget` anahtarını çıkarın
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” büyük bir hesaplama bütçesi istiyor (her çalıştırma en fazla {minutes} dakika ve olağan komutların {times} katı): kullanıcı onayladıktan sonra `allowHeavy: true` ile yükleyin
|
||||
Rectangular Grid Tool Options Dikdörtgen Izgara Aracı Seçenekleri
|
||||
Polar Grid Tool Options Yuvarlak Izgara Aracı Seçenekleri
|
||||
Fill Line Çizgiyi doldur
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5060,3 +5060,6 @@ axis Y Axis Вісь Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` динамічний ефект виконується під час відмальовування документа, тому він не може бути `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out модуль експорту завжди отримує бюджет експорту: не вказуйте `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees «{_1}» запитує великий обчислювальний бюджет (кожен запуск до {minutes} хвилин і в {times} разів більше інструкцій, ніж зазвичай): встановіть його з `allowHeavy: true`, коли користувач погодиться
|
||||
Rectangular Grid Tool Options Параметри інструменту «Прямокутна сітка»
|
||||
Polar Grid Tool Options Параметри інструменту «Полярна сітка»
|
||||
Fill Line Заливка відрізка
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 737 and column 40.
|
@@ -5060,3 +5060,6 @@ axis Y Axis Trục Y
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` hiệu ứng trực tiếp chạy trong lúc tài liệu được vẽ nên không thể là `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out plug-in xuất luôn nhận ngân sách của một lần xuất: hãy bỏ `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” yêu cầu ngân sách tính toán lớn (mỗi lần chạy tới {minutes} phút và gấp {times} lần số lệnh thông thường): hãy cài đặt với `allowHeavy: true` khi người dùng đồng ý
|
||||
Rectangular Grid Tool Options Tùy chọn công cụ Lưới chữ nhật
|
||||
Polar Grid Tool Options Tùy chọn công cụ Lưới cực
|
||||
Fill Line Tô đoạn thẳng
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4034 and column 10.
|
@@ -5303,3 +5303,6 @@ axis Y Axis Y 轴
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` 实时效果在绘制文档时运行,因此不能为 `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out 导出增效工具始终使用导出的预算:请省略 `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}”需要大量计算预算(每次运行最多 {minutes} 分钟,指令数为通常的 {times} 倍):请在用户同意后使用 `allowHeavy: true` 安装
|
||||
Rectangular Grid Tool Options 矩形网格工具选项
|
||||
Polar Grid Tool Options 极坐标网格工具选项
|
||||
Fill Line 填充线段
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5099,3 +5099,6 @@ axis Y Axis Y 軸
|
||||
@msg a live effect runs while the document is drawn, so it can't be `heavy` 即時效果會在繪製文件時執行,因此不能是 `heavy`
|
||||
@msg an exporter always gets an export's budget: leave `budget` out 匯出增效模組一律使用匯出的預算:請移除 `budget`
|
||||
@msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees 「{_1}」需要大量運算預算(每次執行最多 {minutes} 分鐘,指令數為一般的 {times} 倍):請在使用者同意後以 `allowHeavy: true` 安裝
|
||||
Rectangular Grid Tool Options 矩形格線工具選項
|
||||
Polar Grid Tool Options 極座標格點工具選項
|
||||
Fill Line 填色線段
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -596,7 +596,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, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), 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}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", 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, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), 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), lineSegment|arc|rectangularGrid|polarGrid → that tool's Tool Options, its click dialog without x and y (dialog kind = the tool id, `__toolOptions: true`; lineSegment: length (pt), angle (°), fillLine; arc: lengthX, lengthY (pt), reference (0–8), arcType: open|closed, baseAlong: x|y, slope (−100…100), fillArc; rectangularGrid: width, height (pt, the Default Size), reference (0–8), rows, columns, rowSkew, columnSkew (%), frame, fill; polarGrid: the same with concentric, radial, concentricSkew, radialSkew and compound for rows, columns, the skews and frame; OK runs tool.setOption {tool, values} and draws nothing: the next drag uses them (Fill Line, the arc's type, base, slope and fill, the grids' dividers, skews and switches) and a click's dialog opens at 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}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", values})",
|
||||
),
|
||||
(
|
||||
"ui.colorGuideOptions",
|
||||
|
||||
@@ -327,7 +327,8 @@ fn flyout_press(ui: &Ui, resp: &egui::Response, rect: egui::Rect) -> Option<Flyo
|
||||
/// tool's fits the artboard in the window, the Zoom tool's shows it at 100%, the Rotate, Scale,
|
||||
/// Reflect and Shear tools' are their Object › Transform dialogs, the selection tools' are the
|
||||
/// Move dialog, and the Pencil, Paintbrush, Smooth, Blob Brush, Eraser and Shape Builder tools' are
|
||||
/// their Tool Options.
|
||||
/// their Tool Options, as are the Line Segment, Arc, Rectangular Grid and Polar Grid tools'
|
||||
/// ([`crate::dialogs::shapes::open_options`]).
|
||||
pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::Value, String> {
|
||||
match tool {
|
||||
"dimension" => crate::dialogs::dimension::open(app),
|
||||
@@ -359,6 +360,9 @@ pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::
|
||||
Ok(json!({ "dialog": crate::dialogs::perspective_options::KIND }))
|
||||
}
|
||||
"shapeBuilder" => Ok(crate::dialogs::shape_builder_options::open(app)),
|
||||
// The Line Segment, Arc and grid tools: their Tool Options, the options of their next
|
||||
// drag and click.
|
||||
_ if crate::dialogs::shapes::OPTION_TOOLS.contains(&tool) => crate::dialogs::shapes::open_options(app, tool),
|
||||
// The Flare tool: its options, which draw the next flare (OK draws none).
|
||||
"flare" => Ok(crate::dialogs::flare_options::open(app, None)),
|
||||
// A double click on the Print Tiling tool puts the pages back where the placement puts them.
|
||||
@@ -1061,6 +1065,80 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// #2691: a double-click on the Line Segment, Arc, Rectangular Grid or Polar Grid tool opens
|
||||
/// its Tool Options (the Rectangular Grid and Arc help pages, step 3). Cancel leaves the next
|
||||
/// drag as it was; OK draws nothing and the next drag, and a click's dialog, use the options.
|
||||
#[test]
|
||||
fn double_clicking_the_line_arc_and_grid_tools_opens_their_tool_options() {
|
||||
// (tool, the option changed, its new value, the drag's command, the param it shows in)
|
||||
let cases = [
|
||||
("lineSegment", "fillLine", json!(true), "shape.line", "fill"),
|
||||
("arc", "slope", json!(-30.0), "shape.arc", "slope"),
|
||||
("rectangularGrid", "rows", json!(2), "shape.rectangularGrid", "rows"),
|
||||
("polarGrid", "radial", json!(3), "shape.polarGrid", "radial"),
|
||||
];
|
||||
let objects =
|
||||
|app: &VectorcraftApp| app.session.active().unwrap().doc.layers.iter().map(|l| l.children().map_or(0, |c| c.len())).sum::<usize>();
|
||||
let drag = |app: &mut VectorcraftApp| {
|
||||
let view = app.view_info();
|
||||
for (kind, x, y) in [
|
||||
(vectorcraft_tools::PointerKind::Down, 50.0, 50.0),
|
||||
(vectorcraft_tools::PointerKind::Drag, 150.0, 120.0),
|
||||
(vectorcraft_tools::PointerKind::Up, 150.0, 120.0),
|
||||
] {
|
||||
app.session.pointer(&vectorcraft_tools::PointerEvent::new(kind, x, y), view).unwrap();
|
||||
}
|
||||
app.session.journal.last().cloned().expect("the drag's command")
|
||||
};
|
||||
for (tool, key, value, cmd, param) in cases {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.ui.toolbar_advanced = true;
|
||||
app.run("file.new", json!({"width": 300, "height": 300})).unwrap();
|
||||
app.select_tool(tool);
|
||||
let index = slots(&app).iter().position(|(_, tools)| tools.contains(&tool)).expect("a toolbar slot");
|
||||
let ctx = egui::Context::default();
|
||||
let at = frame_in(&mut app, &ctx, 0.0, vec![], 2000.0)[index].center();
|
||||
let b = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame_in(&mut app, &ctx, 1.0, vec![Event::PointerMoved(at), b(true), b(false), b(true), b(false)], 2000.0);
|
||||
let d = app.ui.dialog.clone().unwrap_or_else(|| panic!("{tool}: Tool Options"));
|
||||
assert_eq!((d.kind.as_str(), d.bool("__toolOptions")), (tool, true));
|
||||
assert!(!d.fields.contains_key("x"), "{tool}: no click to draw at");
|
||||
let text = crate::tests_labels::painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx()));
|
||||
let title = match tool {
|
||||
"lineSegment" => "Line Segment Tool Options",
|
||||
"arc" => "Arc Segment Tool Options",
|
||||
"rectangularGrid" => "Rectangular Grid Tool Options",
|
||||
_ => "Polar Grid Tool Options",
|
||||
};
|
||||
assert!(text.contains(title) && !text.contains("Reference:"), "{tool}: {text}");
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert(key.into(), value.clone());
|
||||
crate::dialogs::cancel(&mut app);
|
||||
let (c, p) = drag(&mut app);
|
||||
assert_eq!(c, cmd);
|
||||
assert_ne!(p.get(param), Some(&value), "{tool}: Cancel changes nothing");
|
||||
assert_eq!(objects(&app), 1);
|
||||
app.run("tool.options", json!({ "tool": tool })).unwrap();
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert(key.into(), value.clone());
|
||||
if tool.ends_with("Grid") {
|
||||
d.fields.insert("width".into(), json!("150 pt"));
|
||||
}
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(objects(&app), 1, "{tool}: OK draws nothing");
|
||||
let (c, p) = drag(&mut app);
|
||||
assert_eq!((c.as_str(), &p[param]), (cmd, &value), "{tool}: the next drag uses the option");
|
||||
// A click's dialog opens at them too, a grid at its Default Size.
|
||||
crate::dialogs::open_tool_dialog(&mut app, tool, json!({"x": 100, "y": 100}));
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert_eq!(d.fields[key], value, "{tool}");
|
||||
if tool.ends_with("Grid") {
|
||||
assert_eq!(d.f64("width", 0.0), 150.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_clicking_the_hand_zoom_and_transform_tools() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
|
||||
@@ -182,8 +182,11 @@ Transform (dialog kind = the tool id), or fails with `nothing selected`. For `se
|
||||
drag or Move (`dx`, `dy`) and with the `copy`, `objects` and `preview` it was last confirmed with (its `patterns` is
|
||||
the `transformPatternTiles` preference, which its OK sets). `ui.key` Enter with one of those seven
|
||||
tools opens its dialog too (nothing happens without a selection), with the transform tools' `origin` at their reference
|
||||
point. Gradient tool handles snap to anchors, edges and smart guides; Shift constrains them to 45° steps from the
|
||||
`constrainAngle` preference.
|
||||
point. For `lineSegment`, `arc`, `rectangularGrid` and `polarGrid` it opens the tool's Tool Options: the dialog its
|
||||
click opens (kind = the tool id), without `x` and `y` and with `__toolOptions: true`, whose `ui.dialog.confirm` runs
|
||||
`tool.setOption {tool, values}` and draws nothing; the next drag with the tool and its click dialog use the values
|
||||
(the line's `fillLine`, the grids' Default Size `width`, `height` and `reference` too). Gradient tool handles snap to
|
||||
anchors, edges and smart guides; Shift constrains them to 45° steps from the `constrainAngle` preference.
|
||||
|
||||
Effect dialogs: `engine.execute {command: "effect.dialog", params: {effect, index?, item?}}` opens the `effect` dialog
|
||||
(fields: the effect's parameters, `preview`). With `index` it edits that applied effect of `item` (null: the object's
|
||||
|
||||
+3
-1
File diff suppressed because one or more lines are too long
+2
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user