From b83ba84a7935425ea7bfe408d16fd24f583421c0 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Fri, 9 Oct 2026 12:04:10 -0400 Subject: [PATCH] LR-CROP-STRAIGHTEN: a move or resize drag keeps its pointer past the box The box stops at the photo's edge, so a move or resize drag can carry the pointer outside it, where the rotation glyph replaced the move or resize cursor and said the drag rotated. The drag's own cursor now holds until it ends. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/detail.rs | 11 +++++++++-- crates/ui-egui/src/tests_crop_rotate.rs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/detail.rs b/crates/ui-egui/src/panels/detail.rs index c98aeda123..870fd380d3 100644 --- a/crates/ui-egui/src/panels/detail.rs +++ b/crates/ui-egui/src/panels/detail.rs @@ -1254,12 +1254,19 @@ fn crop_overlay(app: &mut LightcraftApp, ui: &mut egui::Ui, resp: &egui::Respons // with ⌘ held a drag draws a level line (the straighten crosshair, set above), not a rotation let straightening = !rotating && ui.input(|i| i.modifiers.command); if let Some(hq) = pointer.filter(|_| !straightening) { - let near = handles.iter().position(|h| h.distance(hq) < 12.0).filter(|_| !rotating); + // a move or resize drag keeps its pointer wherever it goes, even past the box + let held = match app.gesture { + Some(Gesture::CropHandle { handle, .. }) => Some(usize::from(handle)), + _ => None, + }; + let near = held.or_else(|| handles.iter().position(|h| h.distance(hq) < 12.0)).filter(|_| !rotating); let cursor = match near { Some(0 | 2) => egui::CursorIcon::ResizeNwSe, Some(1 | 3) => egui::CursorIcon::ResizeNeSw, Some(4 | 6) => egui::CursorIcon::ResizeVertical, - Some(_) => egui::CursorIcon::ResizeHorizontal, + Some(5 | 7) => egui::CursorIcon::ResizeHorizontal, + // 8: the whole box + Some(_) => egui::CursorIcon::Move, None if inside(hq) && !rotating => egui::CursorIcon::Move, // a drag here rotates: no system cursor shows that, so draw a curved double arrow None => { diff --git a/crates/ui-egui/src/tests_crop_rotate.rs b/crates/ui-egui/src/tests_crop_rotate.rs index b7dfc07cf7..00178aa714 100644 --- a/crates/ui-egui/src/tests_crop_rotate.rs +++ b/crates/ui-egui/src/tests_crop_rotate.rs @@ -283,3 +283,20 @@ fn leaving_the_angle_field_applies_the_angle() { assert!((angle(&h) - 4.5).abs() < 1e-9, "applied on leaving: {}", angle(&h)); assert_eq!(h.app.session.undo.len(), undo + 1); } + +/// A move or resize drag keeps its own pointer when it runs past the box (the box stops at the +/// photo's edge): the rotation glyph would say the drag rotates. +#[test] +fn moving_the_box_never_shows_rotation() { + let mut h = crop_tool(); + let r = h.request( + "ui.pointer", + json!({"events": [{"kind": "down", "x": 0.5, "y": 0.5}, {"kind": "drag", "x": 0.8, "y": 0.5}, {"kind": "drag", "x": 1.2, "y": 0.5}]}), + T, + ); + assert_eq!(r["ok"], true, "{r}"); + h.settle(SETTLE); + assert_eq!(h.last_cursor, egui::CursorIcon::Move, "still moving"); + assert!(!has(&h, "cropRotateCursor")); + assert_eq!(angle(&h), 0.0, "nothing rotated"); +}