M2: Rectangular Grid click options dialog
This commit is contained in:
parent
e61ed740c3
commit
c570414873
|
|
@ -49,7 +49,7 @@ pub(super) fn length(ui: &mut egui::Ui, d: &mut Dialog, key: &str, unit: Unit, w
|
|||
fn lengths(kind: &str) -> &'static [&'static str] {
|
||||
match kind {
|
||||
"move" => &["dx", "dy"],
|
||||
"rectangle" | "ellipse" | "artboardOptions" => &["width", "height"],
|
||||
"rectangle" | "ellipse" | "rectangularGrid" | "artboardOptions" => &["width", "height"],
|
||||
"roundedRectangle" => &["width", "height", "radius"],
|
||||
"polygon" => &["radius"],
|
||||
"star" => &["radius1", "radius2"],
|
||||
|
|
@ -67,6 +67,7 @@ fn order(kind: &str) -> &'static [&'static str] {
|
|||
match kind {
|
||||
"offsetPath" => &["offset", "joins", "miterLimit"],
|
||||
"splitIntoGrid" => &["rows", "columns", "gutter"],
|
||||
"rectangularGrid" => &["width", "height", "rows", "columns"],
|
||||
"artboardOptions" => &["name", "width", "height"],
|
||||
_ => &[],
|
||||
}
|
||||
|
|
@ -178,7 +179,12 @@ pub(super) fn grid(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) {
|
|||
if lengths.contains(&k.as_str()) {
|
||||
length_field(ui, d, &k, &humanized(&k), unit);
|
||||
} else if options.is_empty() {
|
||||
field(ui, d, &k, &humanized(&k));
|
||||
let label = match (d.kind.as_str(), k.as_str()) {
|
||||
("rectangularGrid", "rows") => tl!("Horizontal Dividers:").into(),
|
||||
("rectangularGrid", "columns") => tl!("Vertical Dividers:").into(),
|
||||
_ => humanized(&k),
|
||||
};
|
||||
field(ui, d, &k, &label);
|
||||
} else {
|
||||
row_label(ui, &humanized(&k));
|
||||
dropdown(ui, d, &k, FIELD_W, &options, true);
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ macro_rules! registry {
|
|||
|
||||
registry! {
|
||||
NewDocument: [new_document::KIND] => new_document::SPEC,
|
||||
Shape: ["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment"] => shapes::SPEC,
|
||||
Shape: ["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment", "rectangularGrid"] => shapes::SPEC,
|
||||
Transform: ["move", "rotate", "scale", "reflect", "shear"] => transform::SPEC,
|
||||
PathOp: ["average", "offsetPath", "simplify", "splitIntoGrid"] => path_ops::SPEC,
|
||||
DocumentSetup: ["documentSetup"] => document_setup::SPEC,
|
||||
|
|
@ -513,3 +513,6 @@ mod tests_perspective;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests_modal;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_rectangular_grid;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ fn title(kind: &str) -> &'static str {
|
|||
"ellipse" => tl!("Ellipse"),
|
||||
"polygon" => tl!("Polygon"),
|
||||
"star" => tl!("Star"),
|
||||
"rectangularGrid" => tl!("Rectangular Grid"),
|
||||
_ => tl!("Line Segment Tool Options"),
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +30,11 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
|||
"shape.star",
|
||||
json!({"cx": x, "cy": y, "radius1": d.f64("radius1", 50.0), "radius2": d.f64("radius2", 25.0), "points": d.f64("points", 5.0) as u64}),
|
||||
),
|
||||
"rectangularGrid" => (
|
||||
"shape.rectangularGrid",
|
||||
json!({"x": x, "y": y, "width": d.f64("width", 100.0), "height": d.f64("height", 100.0),
|
||||
"rows": (d.f64("rows", 5.0) as u64).min(999), "columns": (d.f64("columns", 5.0) as u64).min(999)}),
|
||||
),
|
||||
"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()}))
|
||||
|
|
@ -39,8 +45,13 @@ 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.
|
||||
match vectorcraft_engine::perspective_click(&app.session, id, ¶ms, vectorcraft_geom::Point::new(x, y)) {
|
||||
let options = (d.kind == "rectangularGrid").then(|| json!({"rows": params["rows"], "columns": params["columns"]}));
|
||||
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 => run_and_close(app, id, params),
|
||||
}?;
|
||||
if let Some(options) = options {
|
||||
app.run("tool.setOption", json!({"tool": "rectangularGrid", "values": options}))?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ fn kinds_map_to_their_dialogs() {
|
|||
("newDocument", DialogKind::NewDocument),
|
||||
("roundedRectangle", DialogKind::Shape),
|
||||
("lineSegment", DialogKind::Shape),
|
||||
("rectangularGrid", DialogKind::Shape),
|
||||
("shear", DialogKind::Transform),
|
||||
("splitIntoGrid", DialogKind::PathOp),
|
||||
("artboardOptions", DialogKind::ArtboardOptions),
|
||||
|
|
@ -78,9 +79,20 @@ fn every_dialog_draws() {
|
|||
app.ui.dialog = None;
|
||||
let _ = app.session.cancel_interaction();
|
||||
}
|
||||
for kind in
|
||||
["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment", "rotate", "reflect", "scale", "shear", "artboardOptions"]
|
||||
{
|
||||
for kind in [
|
||||
"rectangle",
|
||||
"roundedRectangle",
|
||||
"ellipse",
|
||||
"polygon",
|
||||
"star",
|
||||
"lineSegment",
|
||||
"rectangularGrid",
|
||||
"rotate",
|
||||
"reflect",
|
||||
"scale",
|
||||
"shear",
|
||||
"artboardOptions",
|
||||
] {
|
||||
open_tool_dialog(&mut app, kind, json!({"x": 5, "y": 5, "index": 0}));
|
||||
assert!(app.ui.dialog.as_ref().is_some_and(|d| d.kind == kind), "{kind}");
|
||||
frame(&mut app, Default::default());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,313 @@
|
|||
//! Rectangular Grid click-to-size flow, from canvas dispatch through the registered dialog.
|
||||
//! All geometry here is original, synthetic test data.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{Document, NodeId, NodeKind};
|
||||
use vectorcraft_engine::{Session, ViewInfo};
|
||||
use vectorcraft_geom::{PathData, Point, Rect};
|
||||
use vectorcraft_tools::distort::perspective::{PerspectiveGrid, Plane};
|
||||
use vectorcraft_tools::{PointerEvent, PointerKind};
|
||||
|
||||
use crate::{VectorcraftApp, theme};
|
||||
|
||||
const KIND: &str = "rectangularGrid";
|
||||
|
||||
fn app() -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 800, "height": 600})).unwrap();
|
||||
app.select_tool(KIND);
|
||||
// Pointer-up persists tool settings even for a click. Seed them so opening/cancelling can
|
||||
// be compared with the complete pre-click preferences, including an unchanged store.
|
||||
saved_counts(&mut app, 5, 5);
|
||||
app
|
||||
}
|
||||
|
||||
fn saved_counts(app: &mut VectorcraftApp, rows: u32, columns: u32) {
|
||||
let values = json!({"rows": rows, "columns": columns});
|
||||
app.session.set_tool_options(Some(KIND), values.as_object().unwrap());
|
||||
}
|
||||
|
||||
fn view() -> ViewInfo {
|
||||
ViewInfo { smart_guides: false, snap_to_point: false, snap_to_grid: false, snap_to_pixel: false, ..Default::default() }
|
||||
}
|
||||
|
||||
fn pointer(app: &mut VectorcraftApp, kind: PointerKind, p: Point) {
|
||||
crate::canvas::dispatch(app, &PointerEvent::new(kind, p.x, p.y), view());
|
||||
}
|
||||
|
||||
fn click(app: &mut VectorcraftApp, at: Point) {
|
||||
app.select_tool(KIND);
|
||||
pointer(app, PointerKind::Down, at);
|
||||
assert!(app.ui.dialog.is_none(), "the dialog opens on release");
|
||||
pointer(app, PointerKind::Up, at);
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(KIND));
|
||||
assert!(!app.session.tool_busy());
|
||||
assert!(!app.session.in_interaction());
|
||||
}
|
||||
|
||||
fn field(app: &VectorcraftApp, key: &str) -> Value {
|
||||
app.ui.dialog.as_ref().unwrap().fields.get(key).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn set(app: &mut VectorcraftApp, key: &str, value: Value) {
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert(key.into(), value);
|
||||
}
|
||||
|
||||
fn values(app: &mut VectorcraftApp, width: Value, height: Value, rows: Value, columns: Value) {
|
||||
for (key, value) in [("width", width), ("height", height), ("rows", rows), ("columns", columns)] {
|
||||
set(app, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp) -> NodeId {
|
||||
let result = super::confirm(app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
NodeId(result["id"].as_u64().expect("confirmation returns the created group"))
|
||||
}
|
||||
|
||||
fn paths(app: &VectorcraftApp, id: NodeId) -> Vec<PathData> {
|
||||
let doc = &app.session.doc().unwrap().doc;
|
||||
let group = doc.node(id).unwrap();
|
||||
assert!(matches!(group.kind, NodeKind::Group { .. }));
|
||||
group.children().unwrap().iter().map(|n| n.path_data().expect("every grid child is an editable path").clone()).collect()
|
||||
}
|
||||
|
||||
fn assert_path(path: &PathData, expected: &[Point], closed: bool) {
|
||||
assert_eq!(path.subpaths.len(), 1);
|
||||
let subpath = &path.subpaths[0];
|
||||
assert_eq!(subpath.closed, closed);
|
||||
assert_eq!(subpath.anchors.len(), expected.len());
|
||||
for (anchor, expected) in subpath.anchors.iter().zip(expected) {
|
||||
assert!(anchor.p.distance(*expected) < 1e-7, "{:?} != {expected:?}", anchor.p);
|
||||
assert!(anchor.h_in.distance(anchor.p) < 1e-7 && anchor.h_out.distance(anchor.p) < 1e-7, "grid edges stay straight");
|
||||
}
|
||||
}
|
||||
|
||||
/// Independent coordinate oracle: counts mean interior dividers, plus one closed outer frame.
|
||||
fn assert_grid(app: &VectorcraftApp, id: NodeId, bounds: Rect, rows: u32, columns: u32) {
|
||||
let paths = paths(app, id);
|
||||
assert_eq!(paths.len(), (rows + columns + 1) as usize);
|
||||
let mut remaining = paths.iter();
|
||||
for i in 1..=rows {
|
||||
let y = bounds.y0 + bounds.height() * f64::from(i) / f64::from(rows + 1);
|
||||
assert_path(remaining.next().unwrap(), &[Point::new(bounds.x0, y), Point::new(bounds.x1, y)], false);
|
||||
}
|
||||
for i in 1..=columns {
|
||||
let x = bounds.x0 + bounds.width() * f64::from(i) / f64::from(columns + 1);
|
||||
assert_path(remaining.next().unwrap(), &[Point::new(x, bounds.y0), Point::new(x, bounds.y1)], false);
|
||||
}
|
||||
assert_path(
|
||||
remaining.next().unwrap(),
|
||||
&[Point::new(bounds.x0, bounds.y0), Point::new(bounds.x1, bounds.y0), Point::new(bounds.x1, bounds.y1), Point::new(bounds.x0, bounds.y1)],
|
||||
true,
|
||||
);
|
||||
assert!(remaining.next().is_none());
|
||||
}
|
||||
|
||||
struct Snapshot {
|
||||
doc: Arc<Document>,
|
||||
selected: Vec<NodeId>,
|
||||
prefs: Value,
|
||||
options: Value,
|
||||
undo: usize,
|
||||
redo: usize,
|
||||
journal: Vec<(String, Value)>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
fn take(app: &VectorcraftApp) -> Self {
|
||||
let state = app.session.doc().unwrap();
|
||||
Self {
|
||||
doc: state.doc.clone(),
|
||||
selected: state.selection.objects.clone(),
|
||||
prefs: app.session.prefs.to_json(),
|
||||
options: app.session.tool_options_of(KIND),
|
||||
undo: state.history.undo.len(),
|
||||
redo: state.history.redo.len(),
|
||||
journal: app.session.journal.clone(),
|
||||
dirty: state.is_dirty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_unchanged(&self, app: &VectorcraftApp) {
|
||||
let state = app.session.doc().unwrap();
|
||||
assert_eq!(state.doc, self.doc, "the document, including allocated ids, stays unchanged");
|
||||
assert_eq!(state.selection.objects, self.selected);
|
||||
assert_eq!(app.session.prefs.to_json(), self.prefs);
|
||||
assert_eq!(app.session.tool_options_of(KIND), self.options);
|
||||
assert_eq!((state.history.undo.len(), state.history.redo.len()), (self.undo, self.redo));
|
||||
assert_eq!(app.session.journal, self.journal);
|
||||
assert_eq!(state.is_dirty(), self.dirty);
|
||||
assert!(!app.session.in_interaction());
|
||||
}
|
||||
}
|
||||
|
||||
fn key_frame(app: &mut VectorcraftApp, key: egui::Key) {
|
||||
let ctx = egui::Context::default();
|
||||
theme::install_fonts(&ctx);
|
||||
theme::apply(&ctx, Default::default());
|
||||
let event = egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() };
|
||||
let input = egui::RawInput { events: vec![event], ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
crate::shortcuts::handle(app, ui.ctx());
|
||||
super::show(app, ui.ctx());
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_canvas_click_opens_registered_dialog_with_saved_counts_and_point_defaults() {
|
||||
let mut app = app();
|
||||
saved_counts(&mut app, 7, 3);
|
||||
app.run("document.setUnits", json!({"units": "mm"})).unwrap();
|
||||
let before = Snapshot::take(&app);
|
||||
click(&mut app, Point::new(137.25, 86.5));
|
||||
before.assert_unchanged(&app);
|
||||
assert!(super::DialogKind::of(KIND).is_some(), "must not fall through to the no-op form spec");
|
||||
assert_eq!((field(&app, "x"), field(&app, "y")), (json!(137.25), json!(86.5)));
|
||||
assert_eq!((field(&app, "width"), field(&app, "height")), (json!("100 pt"), json!("100 pt")));
|
||||
assert_eq!((field(&app, "rows"), field(&app, "columns")), (json!(7), json!(3)));
|
||||
let text = crate::tests_labels::painted_text(&mut app, |app, ui| super::show(app, ui.ctx()));
|
||||
let labels = ["Width:", "Height:", "Horizontal Dividers:", "Vertical Dividers:"];
|
||||
let positions: Vec<_> = labels.iter().map(|label| text.find(label).expect("a labelled field")).collect();
|
||||
assert!(positions.windows(2).all(|pair| pair[0] < pair[1]), "{text}");
|
||||
let id = confirm(&mut app);
|
||||
assert_grid(&app, id, Rect::new(137.25, 86.5, 237.25, 186.5), 7, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_creates_exact_dividers_as_one_undo_redo_step() {
|
||||
let mut app = app();
|
||||
app.run("shape.rectangle", json!({"x": 12, "y": 18, "width": 9, "height": 11})).unwrap();
|
||||
let before = Snapshot::take(&app);
|
||||
click(&mut app, Point::new(125.0, 75.0));
|
||||
values(&mut app, json!(120), json!(60), json!(2), json!(3));
|
||||
let id = confirm(&mut app);
|
||||
assert_grid(&app, id, Rect::new(125.0, 75.0, 245.0, 135.0), 2, 3);
|
||||
assert_eq!(app.session.doc().unwrap().selection.objects, vec![id]);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), before.undo + 1);
|
||||
assert_eq!(app.session.journal.last().unwrap().0, "shape.rectangularGrid");
|
||||
assert_eq!(app.session.journal.len(), before.journal.len() + 1);
|
||||
let created = app.session.doc().unwrap().doc.clone();
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
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);
|
||||
assert_eq!(app.session.tool_options_of(KIND), json!({"rows": 2, "columns": 3}), "undo does not undo the saved tool settings");
|
||||
app.run("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(app.session.doc().unwrap().doc, created);
|
||||
assert_grid(&app, id, Rect::new(125.0, 75.0, 245.0, 135.0), 2, 3);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), before.undo + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_uses_shared_length_parsing_and_numeric_points() {
|
||||
for (unit, width, height, expected_width, expected_height) in [
|
||||
("mm", json!("25.4 mm"), json!("12.7 mm"), 72.0, 36.0),
|
||||
("mm", json!(72), json!(36), 72.0, 36.0),
|
||||
("inches", json!("2 cm"), json!("18 pt"), 144.0 / 2.54, 18.0),
|
||||
("mm", json!("1 in + 18 pt"), json!("2p6"), 90.0, 30.0),
|
||||
] {
|
||||
let mut app = app();
|
||||
app.run("document.setUnits", json!({"units": unit})).unwrap();
|
||||
click(&mut app, Point::new(-13.5, 29.25));
|
||||
values(&mut app, width, height, json!("2"), json!("3"));
|
||||
let id = confirm(&mut app);
|
||||
assert_grid(&app, id, Rect::new(-13.5, 29.25, -13.5 + expected_width, 29.25 + expected_height), 2, 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_cancel_and_escape_discard_edits_and_reopen_from_saved_values() {
|
||||
for escape in [false, true] {
|
||||
let mut app = app();
|
||||
saved_counts(&mut app, 2, 8);
|
||||
// A pending redo must survive opening, editing and cancelling the size dialog.
|
||||
app.run("shape.rectangle", json!({"x": 5, "y": 6, "width": 7, "height": 8})).unwrap();
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
let before = Snapshot::take(&app);
|
||||
click(&mut app, Point::new(111.0, 147.0));
|
||||
values(&mut app, json!("18 mm"), json!("nonsense"), json!(9), json!(1));
|
||||
if escape {
|
||||
key_frame(&mut app, egui::Key::Escape);
|
||||
} else {
|
||||
super::cancel(&mut app);
|
||||
}
|
||||
assert!(app.ui.dialog.is_none());
|
||||
before.assert_unchanged(&app);
|
||||
click(&mut app, Point::new(209.0, 173.0));
|
||||
assert_eq!((field(&app, "x"), field(&app, "y")), (json!(209.0), json!(173.0)));
|
||||
assert_eq!((field(&app, "width"), field(&app, "height")), (json!("100 pt"), json!("100 pt")));
|
||||
assert_eq!((field(&app, "rows"), field(&app, "columns")), (json!(2), json!(8)));
|
||||
super::cancel(&mut app);
|
||||
before.assert_unchanged(&app);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_enter_confirms_through_the_shared_modal_frame() {
|
||||
let mut app = app();
|
||||
click(&mut app, Point::new(121.0, 143.0));
|
||||
values(&mut app, json!("84 pt"), json!("56 pt"), json!(2), json!(3));
|
||||
key_frame(&mut app, egui::Key::Enter);
|
||||
assert!(app.ui.dialog.is_none());
|
||||
let selected = &app.session.doc().unwrap().selection.objects;
|
||||
assert_eq!(selected.len(), 1);
|
||||
assert_grid(&app, selected[0], Rect::new(121.0, 143.0, 205.0, 199.0), 2, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_perspective_click_projects_each_anchor_and_is_one_undo_step() {
|
||||
let mut app = app();
|
||||
app.run("perspective.grid.preset", json!({"kind": 2})).unwrap();
|
||||
app.run("perspective.grid.set", json!({"snap": false})).unwrap();
|
||||
app.run("perspective.plane.set", json!({"plane": "right"})).unwrap();
|
||||
let grid = PerspectiveGrid::effective(&app.session.doc().unwrap().doc);
|
||||
let at = grid.to_page(Plane::Right, Point::new(65.0, 85.0)).unwrap();
|
||||
let map = grid.size_homography(Plane::Right, at, false).unwrap();
|
||||
let before = Snapshot::take(&app);
|
||||
click(&mut app, at);
|
||||
values(&mut app, json!(96), json!(54), json!(1), json!(2));
|
||||
let id = confirm(&mut app);
|
||||
let expected = [
|
||||
vec![Point::new(at.x, at.y + 27.0), Point::new(at.x + 96.0, at.y + 27.0)],
|
||||
vec![Point::new(at.x + 32.0, at.y), Point::new(at.x + 32.0, at.y + 54.0)],
|
||||
vec![Point::new(at.x + 64.0, at.y), Point::new(at.x + 64.0, at.y + 54.0)],
|
||||
vec![at, Point::new(at.x + 96.0, at.y), Point::new(at.x + 96.0, at.y + 54.0), Point::new(at.x, at.y + 54.0)],
|
||||
];
|
||||
let actual = paths(&app, id);
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for (index, (path, points)) in actual.iter().zip(expected).enumerate() {
|
||||
let projected: Vec<Point> = points.into_iter().map(|p| map.apply(p).unwrap()).collect();
|
||||
assert_path(path, &projected, index == 3);
|
||||
}
|
||||
let state = app.session.doc().unwrap();
|
||||
assert_eq!(PerspectiveGrid::effective(&state.doc).attachment_of(&state.doc, id), Some((Plane::Right, 0.0)));
|
||||
assert_eq!(state.history.undo.len(), before.undo + 1);
|
||||
let (command, params) = app.session.journal.last().unwrap();
|
||||
assert_eq!(command, "perspective.draw");
|
||||
assert_eq!(params["command"], json!("shape.rectangularGrid"));
|
||||
assert_eq!(app.session.journal.len(), before.journal.len() + 1);
|
||||
let created = state.doc.clone();
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(app.session.doc().unwrap().doc, before.doc);
|
||||
app.run("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(app.session.doc().unwrap().doc, created);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangular_grid_failed_creation_does_not_save_counts() {
|
||||
let mut app = app();
|
||||
saved_counts(&mut app, 2, 3);
|
||||
let layer = app.session.doc().unwrap().doc.layers[0].id;
|
||||
app.run("layer.setProps", json!({"ids": [layer.0], "locked": true})).unwrap();
|
||||
let before = Snapshot::take(&app);
|
||||
click(&mut app, Point::new(30.0, 40.0));
|
||||
values(&mut app, json!(120), json!(60), json!(7), json!(8));
|
||||
assert!(super::confirm(&mut app).is_err());
|
||||
assert_eq!(app.session.tool_options_of(KIND), before.options);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), before.undo);
|
||||
}
|
||||
|
|
@ -16,6 +16,10 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) {
|
|||
"polygon" => Dialog::new(kind, json!({"x": x, "y": y, "radius": "50 pt", "sides": 6})),
|
||||
"star" => Dialog::new(kind, json!({"x": x, "y": y, "radius1": "50 pt", "radius2": "25 pt", "points": 5})),
|
||||
"lineSegment" => Dialog::new(kind, json!({"x": x, "y": y, "length": "100 pt", "angle": 0})),
|
||||
"rectangularGrid" => {
|
||||
let options = app.session.tool_options_of(kind);
|
||||
Dialog::new(kind, json!({"x": x, "y": y, "width": "100 pt", "height": "100 pt", "rows": options["rows"], "columns": options["columns"]}))
|
||||
}
|
||||
// Graph tool click: the graph's size.
|
||||
"graph" => Dialog::new(
|
||||
"command",
|
||||
|
|
|
|||
|
|
@ -2626,6 +2626,8 @@ theme Appearance Motiv
|
|||
Polar Grid Polární mřížka
|
||||
Polygon Mnohoúhelník
|
||||
Rectangular Grid Obdélníková mřížka
|
||||
Horizontal Dividers: Vodorovné dělicí čáry:
|
||||
Vertical Dividers: Svislé dělicí čáry:
|
||||
Rounded Rectangle Zaoblený obdélník
|
||||
Spiral Spirála
|
||||
Export CSS Exportovat CSS
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2097,6 +2097,9 @@
|
|||
Rectangle: Sunshine Rechteck: Sonnenschein
|
||||
Rectangle… Rechteck…
|
||||
Rectangular Grid Rechteckiges Raster
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: Horizontale Trennlinien:
|
||||
Vertical Dividers: Vertikale Trennlinien:
|
||||
Rectangular Grid Tool Rechteckiges-Raster-Werkzeug
|
||||
Red Rot
|
||||
Redefine Character Style Zeichenformat neu definieren
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2148,6 +2148,9 @@
|
|||
Rectangle: Sunshine Rectángulo: Sol
|
||||
Rectangle… Rectángulo…
|
||||
Rectangular Grid Cuadrícula rectangular
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: Líneas divisorias horizontales:
|
||||
Vertical Dividers: Líneas divisorias verticales:
|
||||
Rectangular Grid Tool Herramienta Cuadrícula rectangular
|
||||
Red Rojo
|
||||
Redefine Character Style Redefinir estilo de carácter
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2145,6 +2145,9 @@
|
|||
Rectangle: Sunshine Rectangle : soleil
|
||||
Rectangle… Rectangle…
|
||||
Rectangular Grid Grille rectangulaire
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: Séparations horizontales :
|
||||
Vertical Dividers: Séparations verticales :
|
||||
Rectangular Grid Tool Outil Grille rectangulaire
|
||||
Red Rouge
|
||||
Redefine Character Style Redéfinir le style de caractère
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2148,6 +2148,9 @@
|
|||
Rectangle: Sunshine Rettangolo: Sole
|
||||
Rectangle… Rettangolo…
|
||||
Rectangular Grid Griglia rettangolare
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: Divisori orizzontali:
|
||||
Vertical Dividers: Divisori verticali:
|
||||
Rectangular Grid Tool Strumento Griglia rettangolare
|
||||
Red Rosso
|
||||
Redefine Character Style Ridefinisci stile carattere
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2151,6 +2151,9 @@
|
|||
Rectangle Properties: 長方形のプロパティ:
|
||||
Rectangle Tool 長方形ツール
|
||||
Rectangular Grid 長方形グリッド
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: 水平方向の分割線:
|
||||
Vertical Dividers: 垂直方向の分割線:
|
||||
Rectangular Grid Tool 長方形グリッドツール
|
||||
Red レッド
|
||||
Redefine Character Style 文字スタイルを再定義
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2144,6 +2144,9 @@
|
|||
Rectangle: Sunshine Прямоугольник: Солнце
|
||||
Rectangle… Прямоугольник…
|
||||
Rectangular Grid Прямоугольная сетка
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: Горизонтальные разделители:
|
||||
Vertical Dividers: Вертикальные разделители:
|
||||
Rectangular Grid Tool Инструмент «Прямоугольная сетка»
|
||||
Red Красный
|
||||
Redefine Character Style Переопределить стиль символа
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2140,6 +2140,9 @@
|
|||
Rectangle: Sunshine Прямокутник: Сонце
|
||||
Rectangle… Прямокутник…
|
||||
Rectangular Grid Прямокутна сітка
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: Горизонтальні розділювачі:
|
||||
Vertical Dividers: Вертикальні розділювачі:
|
||||
Rectangular Grid Tool Інструмент «Прямокутна сітка»
|
||||
Red Червоний
|
||||
Redefine Character Style Перевизначити стиль символу
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 13.
|
|
|
@ -2072,6 +2072,9 @@
|
|||
Rectangle: Sunshine 矩形:阳光
|
||||
Rectangle… 矩形…
|
||||
Rectangular Grid 矩形网格
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: 水平分隔线:
|
||||
Vertical Dividers: 垂直分隔线:
|
||||
Rectangular Grid Tool 矩形网格工具
|
||||
Red 红
|
||||
Redefine Character Style 重新定义字符样式
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -2126,6 +2126,9 @@
|
|||
Rectangle: Sunshine 矩形:陽光
|
||||
Rectangle… 矩形…
|
||||
Rectangular Grid 矩形格線
|
||||
# Rectangular Grid click dialog.
|
||||
Horizontal Dividers: 水平分隔線:
|
||||
Vertical Dividers: 垂直分隔線:
|
||||
Rectangular Grid Tool 矩形格線工具
|
||||
Red 紅色
|
||||
Redefine Character Style 重新定義字元樣式
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
Loading…
Reference in New Issue