diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 5c4b70df..ad94b596 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -183,6 +183,7 @@ pub fn show(app: &mut FigApp, ui: &mut Ui, worker: &mut Worker) { } } let own_cursor = input(app, ui, &resp); + crate::guides::end_released(&ctx); crate::live::track_pointer(app, &ctx); app.sync_preedit(); request_render(app, worker, ppp); diff --git a/crates/ui-egui/src/guide_release_tests.rs b/crates/ui-egui/src/guide_release_tests.rs new file mode 100644 index 00000000..b2e0f015 --- /dev/null +++ b/crates/ui-egui/src/guide_release_tests.rs @@ -0,0 +1,85 @@ +//! Ruler guide drags released off the canvas, driven through egui headless: the gesture ends there, +//! and a later click on the canvas neither adds a guide nor moves one. + +use egui::{Event, Pos2, vec2}; +use figcraft_engine::Session; +use serde_json::json; + +use crate::layers_drag_tests::Ui; + +fn rulers() -> Ui { + let mut s = Session::new(); + s.execute("view.rulers", &json!({"on": true})).unwrap(); + Ui::new(s) +} + +fn offsets(u: &Ui) -> Vec { + let ds = u.app.session.ds().unwrap(); + ds.doc.node(ds.page).unwrap().guides().iter().map(|g| g.offset).collect() +} + +fn drag(u: &mut Ui, from: Pos2, to: Pos2) { + u.frame(vec![Event::PointerMoved(from)]); + u.button(from, true); + u.frame(vec![Event::PointerMoved(to)]); + u.button(to, false); +} + +fn click(u: &mut Ui, p: Pos2) { + u.frame(vec![Event::PointerMoved(p)]); + u.button(p, true); + u.button(p, false); +} + +#[test] +fn a_ruler_drag_released_off_the_canvas_adds_no_guide_on_a_later_click() { + for left in [false, true] { + let mut u = rulers(); + let r = u.app.canvas.rect; + let start = if left { Pos2::new(r.min.x + 5.0, r.min.y + 60.0) } else { Pos2::new(r.min.x + 60.0, r.min.y + 5.0) }; + drag(&mut u, start, Pos2::new(r.min.x - 100.0, r.center().y)); + let released = offsets(&u); + click(&mut u, r.center() + vec2(30.0, 60.0)); + assert_eq!(offsets(&u), released, "left ruler: {left}"); + } +} + +#[test] +fn a_ruler_drag_released_off_the_canvas_ends_even_when_the_next_frame_presses() { + let mut u = rulers(); + let r = u.app.canvas.rect; + let start = Pos2::new(r.min.x + 60.0, r.min.y + 5.0); + let off = Pos2::new(r.min.x - 100.0, r.center().y); + u.frame(vec![Event::PointerMoved(start)]); + u.button(start, true); + u.frame(vec![Event::PointerMoved(off)]); + u.button(off, false); + let p = r.center() + vec2(30.0, 60.0); + u.button(p, true); + u.button(p, false); + assert!(offsets(&u).is_empty()); +} + +#[test] +fn a_guide_moved_off_the_canvas_doesnt_follow_a_later_click() { + let mut u = rulers(); + let r = u.app.canvas.rect; + drag(&mut u, Pos2::new(r.min.x + 60.0, r.min.y + 5.0), r.center()); + assert_eq!(offsets(&u).len(), 1); + drag(&mut u, r.center(), Pos2::new(r.min.x - 100.0, r.center().y)); + let released = offsets(&u); + click(&mut u, r.center() + vec2(30.0, 60.0)); + assert_eq!(offsets(&u), released); +} + +#[test] +fn a_ruler_drag_released_on_the_canvas_adds_one_guide() { + let mut u = rulers(); + let r = u.app.canvas.rect; + drag(&mut u, Pos2::new(r.min.x + 60.0, r.min.y + 5.0), r.center()); + assert_eq!(offsets(&u).len(), 1); + click(&mut u, r.center() + vec2(30.0, 60.0)); + assert_eq!(offsets(&u).len(), 1); + u.app.session.execute("edit.undo", &json!({})).unwrap(); + assert!(offsets(&u).is_empty()); +} diff --git a/crates/ui-egui/src/guides.rs b/crates/ui-egui/src/guides.rs index b4a7cc0d..69ab8ee8 100644 --- a/crates/ui-egui/src/guides.rs +++ b/crates/ui-egui/src/guides.rs @@ -279,6 +279,19 @@ pub fn input(app: &mut FigApp, ctx: &egui::Context, pos: Pos2) -> bool { false } +/// Ends a guide drag whose button is up by the end of the frame without [`input`] taking the +/// release (released off the canvas, or over something on it): the gesture is dropped, so a later +/// click doesn't add or move a guide. +pub fn end_released(ctx: &egui::Context) { + if ctx.input(|i| i.pointer.primary_down()) { + return; + } + ctx.data_mut(|d| { + d.remove::(drag_id()); + d.remove::(preview_id()); + }); +} + /// Draw the page's and its frames' guides (under the rulers) and the one being dragged with its /// position. pub fn draw(app: &FigApp, painter: &egui::Painter) { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 40f2d5b3..b727caa1 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -95,6 +95,8 @@ mod gap_handle_tests; #[cfg(test)] mod gradient_shorthand_tests; #[cfg(test)] +mod guide_release_tests; +#[cfg(test)] mod home_recents_tests; #[cfg(test)] mod home_tests;