mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 15:52:52 +00:00
Merge main into fix/options-dialog-menus
This commit is contained in:
@@ -673,10 +673,14 @@ fn undo_group_end(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// the step before. As any new edit it ends the steps to redo, so Redo never swaps the document
|
||||
/// under an interaction that would then preview on, or cancel to, the document from before (#1719).
|
||||
/// A typing session also ends in the Type tool, whose caret and marked text (IME) must follow the
|
||||
/// document.
|
||||
/// document. A preview that Real-time Drawing off left unapplied is applied first, as the tool's own
|
||||
/// commit applies it; when it fails, the interaction is canceled.
|
||||
pub(crate) fn settle_interaction(s: &mut Session) -> Result<()> {
|
||||
let typing = typing_in_progress(s);
|
||||
s.commit_interaction()?;
|
||||
match s.unapplied_interaction_preview().filter(|_| !s.previewing) {
|
||||
Some((cmd, p)) if s.preview(&cmd, &p).is_err() => s.cancel_interaction()?,
|
||||
_ => s.commit_interaction()?,
|
||||
}
|
||||
if typing {
|
||||
s.set_tool_option("endTyping", &Value::Bool(true));
|
||||
}
|
||||
|
||||
@@ -3784,3 +3784,60 @@ fn a_filter_that_calls_commands_is_given_the_edit_in_progress() {
|
||||
assert_eq!(x, json!(17.0), "undoing the filter brings back the moved rectangle");
|
||||
run(&mut s, "plugin.remove", json!({"id": id}));
|
||||
}
|
||||
|
||||
/// Installs a filter `id` that calls commands and hands back what it is given, with `commands` (a
|
||||
/// JSON list of command specs) in its manifest.
|
||||
fn install_echo_calling(s: &mut Session, id: &str, commands: &str) {
|
||||
let grant = json!(["doc.read", "doc.write", "commands"]);
|
||||
let manifest = format!(
|
||||
r#"{{"id":"{id}","name":"Test {id}","kind":"filter","api":2,"permissions":{grant},"commandAllowlist":["select.all"],"commands":{commands}}}"#
|
||||
);
|
||||
let wasm = wat::parse_str(tpl::module(&manifest, tpl::ECHO, "", 1)).unwrap();
|
||||
run(s, "plugin.install", json!({"dataBase64": vectorcraft_format::base64_encode(&wasm), "grant": grant, "replace": true}));
|
||||
}
|
||||
|
||||
/// With Real-time Drawing off, a 50 × 50 rectangle at (10, 10) whose move by 7 pt is still being
|
||||
/// dragged: the tool has stored the move, which the document does not hold yet.
|
||||
fn deferred_move(s: &mut Session) -> NodeId {
|
||||
let r = NodeId(run(s, "shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50}))["id"].as_u64().unwrap());
|
||||
run(s, "prefs.set", json!({"key": "realTimeDrawing", "value": false}));
|
||||
let move_by = vectorcraft_tools::Action::Preview("object.move".into(), json!({"dx": 7, "dy": 0}));
|
||||
s.apply_actions(vec![vectorcraft_tools::Action::Begin("Move".into()), move_by]).unwrap();
|
||||
assert_eq!(node(s, r).geometric_bounds().unwrap().x0, 10.0, "the move is not applied while it is dragged");
|
||||
r
|
||||
}
|
||||
|
||||
/// The rectangle a run named `label` during [`deferred_move`] left: moved, with the interaction
|
||||
/// ended and the move an undo step of its own before the run's.
|
||||
fn assert_moved_once(s: &Session, r: NodeId, label: &str) {
|
||||
assert!(!s.in_interaction());
|
||||
assert_eq!(node(s, r).geometric_bounds().unwrap().x0, 17.0, "the filter was given the moved rectangle");
|
||||
let labels: Vec<&str> = s.doc().unwrap().history.undo.iter().rev().take(2).map(|e| e.label.as_str()).collect();
|
||||
assert_eq!(labels, [label, "Move"]);
|
||||
}
|
||||
|
||||
/// With Real-time Drawing off, `plugin.run` of a filter that calls commands during a drag applies
|
||||
/// the dragged move first and gives the filter the moved rectangle, which it hands back as it was.
|
||||
#[test]
|
||||
fn a_filter_that_calls_commands_is_given_a_deferred_move() {
|
||||
let id = "test.engine.callsdeferred";
|
||||
let mut s = session();
|
||||
install_echo_calling(&mut s, id, "[]");
|
||||
let r = deferred_move(&mut s);
|
||||
run(&mut s, "plugin.run", json!({"id": id}));
|
||||
assert_moved_once(&s, r, &format!("Test {id}"));
|
||||
run(&mut s, "plugin.remove", json!({"id": id}));
|
||||
}
|
||||
|
||||
/// A plug-in command that calls commands, run with Real-time Drawing off during a drag, is given
|
||||
/// the moved rectangle as `plugin.run` is.
|
||||
#[test]
|
||||
fn a_plugin_command_that_calls_commands_is_given_a_deferred_move() {
|
||||
let id = "test.engine.callsdeferredcmd";
|
||||
let mut s = session();
|
||||
install_echo_calling(&mut s, id, r#"[{"id":"same","label":"Same"}]"#);
|
||||
let r = deferred_move(&mut s);
|
||||
run(&mut s, &format!("plugin.{id}.same"), json!({}));
|
||||
assert_moved_once(&s, r, "Same");
|
||||
run(&mut s, "plugin.remove", json!({"id": id}));
|
||||
}
|
||||
|
||||
@@ -2047,6 +2047,42 @@ fn real_time_drawing_turned_on_mid_drag_still_commits_deferred_preview() {
|
||||
assert_eq!(paths(&s), 1, "the deferred rectangle is committed");
|
||||
}
|
||||
|
||||
/// With Real-time Drawing off, Undo during a drag keeps the dragged move as its own step and takes
|
||||
/// back only that: the rectangle drawn before stays, and Redo brings the move back.
|
||||
#[test]
|
||||
fn undo_during_a_deferred_drag_takes_back_only_the_drag() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
||||
let r = NodeId(s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap()["id"].as_u64().unwrap());
|
||||
set_pref(&mut s, "realTimeDrawing", json!(false));
|
||||
let x = |s: &Session| s.doc().unwrap().doc.node(r).and_then(vectorcraft_doc::Node::geometric_bounds).map(|b| b.x0);
|
||||
let move_by = vectorcraft_tools::Action::Preview("object.move".into(), json!({"dx": 7, "dy": 0}));
|
||||
s.apply_actions(vec![vectorcraft_tools::Action::Begin("Move".into()), move_by]).unwrap();
|
||||
assert_eq!(x(&s), Some(10.0));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(!s.in_interaction());
|
||||
assert_eq!(x(&s), Some(10.0), "the rectangle stays");
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
assert_eq!(x(&s), Some(17.0), "Redo brings the move back");
|
||||
}
|
||||
|
||||
/// Undo during a drag whose stored preview fails cancels the drag, as its release would, and then
|
||||
/// takes back the step before it.
|
||||
#[test]
|
||||
fn undo_during_a_deferred_drag_that_fails_cancels_it() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
||||
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
|
||||
set_pref(&mut s, "realTimeDrawing", json!(false));
|
||||
let bad = vectorcraft_tools::Action::Preview("no.such.command".into(), json!({}));
|
||||
s.apply_actions(vec![vectorcraft_tools::Action::Begin("Bad".into()), bad]).unwrap();
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(!s.in_interaction());
|
||||
let d = s.doc().unwrap();
|
||||
assert_eq!(d.doc.layers.iter().map(|l| l.children().map_or(0, |c| c.len())).sum::<usize>(), 0, "the rectangle is undone");
|
||||
assert!(d.history.undo.iter().all(|e| e.label != "Bad"));
|
||||
}
|
||||
|
||||
/// Repeated overlay redraws with Real-time Drawing off reuse the cached deferred feedback.
|
||||
#[test]
|
||||
fn deferred_preview_overlays_are_cached_across_redraws() {
|
||||
|
||||
Reference in New Issue
Block a user