mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-26 03:16:53 -06:00
208 lines
8.3 KiB
Rust
208 lines
8.3 KiB
Rust
//! Real field and canvas gestures for #402; distances are checked in the engine document.
|
|
use crate::layers_drag_tests::Ui;
|
|
use egui::{Event, Id, Key, Modifiers, Pos2, vec2};
|
|
use figcraft_doc::NodeId;
|
|
use figcraft_engine::Session;
|
|
use serde_json::json;
|
|
|
|
fn scene(grid: bool) -> (Ui, Vec<NodeId>) {
|
|
let mut s = Session::new();
|
|
let mut ids = vec![];
|
|
for row in 0..if grid { 2 } else { 1 } {
|
|
for col in 0..3 {
|
|
let id = NodeId(
|
|
s.execute("shape.rectangle", &json!({"x":100+col*80,"y":100+row*100,"width":50,"height":60})).unwrap()["id"].as_u64().unwrap(),
|
|
);
|
|
ids.push(id);
|
|
}
|
|
}
|
|
s.execute("select.set", &json!({"ids":ids})).unwrap();
|
|
let u = Ui::new(s);
|
|
(u, ids)
|
|
}
|
|
fn xy(u: &Ui, id: NodeId) -> (f64, f64) {
|
|
let b = u.app.session.doc().unwrap().world_bounds(id).unwrap();
|
|
(b.x0, b.y0)
|
|
}
|
|
fn key(u: &mut Ui, k: Key) {
|
|
u.frame(vec![
|
|
Event::Key { key: k, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE },
|
|
Event::Key { key: k, physical_key: None, pressed: false, repeat: false, modifiers: Modifiers::NONE },
|
|
]);
|
|
u.frame(vec![]);
|
|
}
|
|
fn drag(u: &mut Ui, p: Pos2, by: egui::Vec2, release: bool) {
|
|
u.frame(vec![Event::PointerMoved(p)]);
|
|
u.button(p, true);
|
|
for i in 1..=4 {
|
|
u.frame(vec![Event::PointerMoved(p + by * i as f32 / 4.)]);
|
|
}
|
|
if release {
|
|
u.button(p + by, false);
|
|
u.frame(vec![]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn spacing_fields_type_scrub_and_keep_one_undo_step() {
|
|
let (mut u, ids) = scene(true);
|
|
let field = Id::new(("selection_spacing", "horizontal"));
|
|
let p = u.ctx.read_response(field.with("field")).unwrap().rect.center();
|
|
let history = u.app.session.undo_depth().0;
|
|
u.button(p, true);
|
|
u.button(p, false);
|
|
u.frame(vec![]);
|
|
u.frame(vec![Event::Text("45.5".into())]);
|
|
key(&mut u, Key::Enter);
|
|
assert_eq!(xy(&u, ids[1]), (195.5, 100.));
|
|
assert_eq!(xy(&u, ids[4]), (195.5, 200.));
|
|
assert_eq!(u.app.session.undo_depth().0, history + 1);
|
|
u.app.run("edit.undo", json!({}));
|
|
u.frame(vec![]);
|
|
let lead = u.ctx.read_response(field.with("lead")).unwrap().rect.center();
|
|
drag(&mut u, lead, vec2(16., 0.), true);
|
|
assert_eq!(xy(&u, ids[1]), (196., 100.));
|
|
assert_eq!(u.app.session.undo_depth().0, history + 1);
|
|
u.app.run("edit.undo", json!({}));
|
|
assert_eq!(xy(&u, ids[1]), (180., 100.));
|
|
}
|
|
|
|
#[test]
|
|
fn canvas_gap_drag_keeps_selection_and_undo_redo_for_both_grid_axes() {
|
|
for (vertical, point) in [(false, (165., 130.)), (true, (125., 180.))] {
|
|
let (mut u, ids) = scene(true);
|
|
let before = u.app.session.undo_depth().0;
|
|
let p = u.app.to_screen(point.0, point.1);
|
|
let output = u.frame_output(vec![Event::PointerMoved(p)]);
|
|
assert_eq!(output.platform_output.cursor_icon, if vertical { egui::CursorIcon::ResizeVertical } else { egui::CursorIcon::ResizeHorizontal });
|
|
let z = u.app.session.ds().unwrap().view.zoom as f32;
|
|
drag(&mut u, p, if vertical { vec2(0., 20. * z) } else { vec2(20. * z, 0.) }, true);
|
|
assert_eq!(u.app.session.selection(), ids);
|
|
assert_eq!(xy(&u, ids[1]), if vertical { (180., 100.) } else { (200., 100.) });
|
|
assert_eq!(xy(&u, ids[3]), if vertical { (100., 220.) } else { (100., 200.) });
|
|
assert_eq!(u.app.session.undo_depth().0, before + 1);
|
|
u.app.run("edit.undo", json!({}));
|
|
assert_eq!(xy(&u, ids[4]), (180., 200.));
|
|
u.app.run("edit.redo", json!({}));
|
|
assert_eq!(xy(&u, ids[4]), if vertical { (180., 220.) } else { (200., 200.) });
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn escape_rolls_back_and_an_outside_release_finishes_the_gesture() {
|
|
let (mut u, ids) = scene(false);
|
|
let before = u.app.session.undo_depth().0;
|
|
// A canvas handle takes the keyboard from a previously focused panel field.
|
|
let field = u.ctx.read_response(Id::new(("selection_spacing", "horizontal")).with("field")).unwrap().rect.center();
|
|
u.button(field, true);
|
|
u.button(field, false);
|
|
u.frame(vec![]);
|
|
let p = u.app.to_screen(165., 130.);
|
|
let z = u.app.session.ds().unwrap().view.zoom as f32;
|
|
drag(&mut u, p, vec2(20. * z, 0.), false);
|
|
assert_eq!(xy(&u, ids[1]), (200., 100.));
|
|
key(&mut u, Key::Escape);
|
|
u.button(p + vec2(20. * z, 0.), false);
|
|
u.frame(vec![]);
|
|
assert_eq!(xy(&u, ids[1]), (180., 100.));
|
|
assert_eq!(u.app.session.undo_depth().0, before);
|
|
assert_eq!(u.app.session.selection(), ids);
|
|
drag(&mut u, p, vec2(20. * z, 0.), false);
|
|
// Leave the window while held; releasing with no pointer position still commits once.
|
|
u.frame(vec![Event::PointerGone]);
|
|
u.frame(vec![Event::PointerButton {
|
|
pos: Pos2::new(-1., -1.),
|
|
button: egui::PointerButton::Primary,
|
|
pressed: false,
|
|
modifiers: Modifiers::NONE,
|
|
}]);
|
|
u.frame(vec![]);
|
|
assert_eq!(u.app.session.undo_depth().0, before + 1);
|
|
assert!(u.app.session.ds().unwrap().interaction.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn switching_documents_cancels_only_the_spacing_documents_gesture() {
|
|
let (mut u, ids) = scene(false);
|
|
let p = u.app.to_screen(165., 130.);
|
|
let z = u.app.session.ds().unwrap().view.zoom as f32;
|
|
drag(&mut u, p, vec2(20. * z, 0.), false);
|
|
u.app.run("file.new", json!({}));
|
|
u.app.session.begin_interaction("Unrelated").unwrap();
|
|
u.frame(vec![]);
|
|
assert!(u.app.session.ds().unwrap().interaction.is_some());
|
|
u.app.session.active = 0;
|
|
assert_eq!(xy(&u, ids[1]), (180., 100.));
|
|
assert!(u.app.session.ds().unwrap().interaction.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn handle_click_is_a_noop_and_modifiers_can_be_held_before_pressing() {
|
|
let (mut u, ids) = scene(false);
|
|
let p = u.app.to_screen(165., 130.);
|
|
let before = u.app.session.undo_depth().0;
|
|
u.frame(vec![Event::PointerMoved(p)]);
|
|
u.button(p, true);
|
|
u.button(p, false);
|
|
u.frame(vec![]);
|
|
assert_eq!(u.app.session.undo_depth().0, before);
|
|
for (modifiers, delta, gap) in [(Modifiers::SHIFT, 17., 50.), (Modifiers::ALT, 17.25, 47.3)] {
|
|
let (mut u, ids) = scene(false);
|
|
let p = u.app.to_screen(165., 130.);
|
|
let z = u.app.session.ds().unwrap().view.zoom as f32;
|
|
u.frame_with(vec![Event::PointerMoved(p)], modifiers);
|
|
u.frame_with(vec![Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: true, modifiers }], modifiers);
|
|
for k in 1..=4 {
|
|
u.frame_with(vec![Event::PointerMoved(p + vec2(delta * z * k as f32 / 4., 0.))], modifiers);
|
|
}
|
|
u.frame_with(
|
|
vec![Event::PointerButton { pos: p + vec2(delta * z, 0.), button: egui::PointerButton::Primary, pressed: false, modifiers }],
|
|
modifiers,
|
|
);
|
|
u.frame(vec![]);
|
|
assert!((xy(&u, ids[1]).0 - 150. - gap).abs() < 1e-6);
|
|
}
|
|
assert_eq!(xy(&u, ids[1]), (180., 100.));
|
|
}
|
|
|
|
#[test]
|
|
fn undo_while_dragging_cancels_the_gesture_without_undoing_an_earlier_edit() {
|
|
let (mut u, ids) = scene(false);
|
|
let before = u.app.session.undo_depth().0;
|
|
let p = u.app.to_screen(165., 130.);
|
|
let z = u.app.session.ds().unwrap().view.zoom as f32;
|
|
drag(&mut u, p, vec2(20. * z, 0.), false);
|
|
let m = Modifiers::COMMAND;
|
|
u.frame_with(
|
|
vec![
|
|
Event::Key { key: Key::Z, physical_key: None, pressed: true, repeat: false, modifiers: m },
|
|
Event::Key { key: Key::Z, physical_key: None, pressed: false, repeat: false, modifiers: m },
|
|
],
|
|
m,
|
|
);
|
|
u.button(p + vec2(20. * z, 0.), false);
|
|
u.frame(vec![]);
|
|
assert_eq!(u.app.session.undo_depth().0, before);
|
|
assert_eq!(xy(&u, ids[1]), (180., 100.));
|
|
assert_eq!(u.app.session.selection(), ids);
|
|
}
|
|
|
|
#[test]
|
|
fn stationary_gap_drag_does_not_change_the_document_again() {
|
|
let (mut u, ids) = scene(false);
|
|
let p = u.app.to_screen(165., 130.);
|
|
let z = u.app.session.ds().unwrap().view.zoom as f32;
|
|
drag(&mut u, p, vec2(20. * z, 0.), false);
|
|
assert_eq!(xy(&u, ids[1]), (200., 100.));
|
|
let revision = u.app.session.ds().unwrap().revision;
|
|
let doc = u.app.session.ds().unwrap().doc.clone();
|
|
for _ in 0..5 {
|
|
u.frame(vec![]);
|
|
u.frame(vec![Event::PointerMoved(p + vec2(20.2 * z, 0.))]);
|
|
}
|
|
assert_eq!(u.app.session.ds().unwrap().revision, revision);
|
|
assert!(std::sync::Arc::ptr_eq(&doc, &u.app.session.ds().unwrap().doc));
|
|
u.button(p + vec2(20.2 * z, 0.), false);
|
|
assert_eq!(xy(&u, ids[1]), (200., 100.));
|
|
}
|