From 548e5345eec232a0473428b86d02835104a7b1a2 Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:53:46 -0400 Subject: [PATCH] Objects: only the selected object's handles resize; a press elsewhere on an object moves it A primary drag looked for a handle under the press on whatever object it grabbed, so pressing near the edge or corner of an object that was not selected started a resize, while the pointer over that spot showed the Move cursor (handles are only shown, and only offered by the cursor, on the selected object). The press now takes its handle from `chartview::selected_handle`, the same lookup the cursor uses; a press on any other object selects and moves it. Test: dragging an unselected shape by its bottom-right corner shows the Move cursor, then selects and moves the shape without resizing it. --- crates/ui-egui/src/grid/interact.rs | 8 +++++--- crates/ui-egui/tests/it/objects.rs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/grid/interact.rs b/crates/ui-egui/src/grid/interact.rs index a7bbede7e..4b7adf587 100644 --- a/crates/ui-egui/src/grid/interact.rs +++ b/crates/ui-egui/src/grid/interact.rs @@ -124,16 +124,18 @@ pub(super) fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Respo // A drag is recognised only after the pointer has moved, so grab the object where the press began. let at = if primary_drag { pointer.press_origin().map(|p| geo.mirror_pos(p)).or(pos) } else { pos }; // The selected object's handles reach a little outside it, so they win over hit-testing. - let on_handle = at.and_then(|p| crate::chartview::selected_handle(app, geo, sh, p)).map(|(o, r, _)| (o, r)); + // Only they resize: a press anywhere on another object selects and moves it, as the + // hover cursor shows. + let on_handle = at.and_then(|p| crate::chartview::selected_handle(app, geo, sh, p)); + let handle = on_handle.map(|(.., h)| h); if let Some(p) = at - && let Some((obj, rect)) = on_handle.or_else(|| crate::chartview::hit(app, geo, sh, p)) + && let Some((obj, _)) = on_handle.map(|(o, r, _)| (o, r)).or_else(|| crate::chartview::hit(app, geo, sh, p)) { app.selected_object = Some(obj); if resp.double_clicked() && app.open_object(obj) != crate::objects::OpenAction::Select { return; } if primary_drag && let Some(a) = sh.object_anchor(obj) { - let handle = crate::objects::handle_at(rect, p); app.grid.drag = Drag::Object { obj, start: p, orig: (a.cell, a.dx, a.dy, a.width, a.height), handle, stepped: false }; // The first recognised drag frame already holds movement away from the press. if let Some(now) = pos { diff --git a/crates/ui-egui/tests/it/objects.rs b/crates/ui-egui/tests/it/objects.rs index 521a2f44c..e3382352d 100644 --- a/crates/ui-egui/tests/it/objects.rs +++ b/crates/ui-egui/tests/it/objects.rs @@ -344,3 +344,21 @@ fn each_drag_of_an_object_is_its_own_undo_step() { h.run_steps(2); assert_eq!(rect(&h, o.image), start, "a second undo takes back the move"); } + +#[test] +fn dragging_an_unselected_object_by_its_edge_moves_it() { + let (mut h, o) = fixture(); + assert_eq!(h.state().selected_object, None); + let before = rect(&h, o.shape); + // On the bottom-right corner, where the handle would be if the shape were selected; the + // pointer shows Move there, and the press selects and moves the shape. + let grab = Handle::BottomRight.pos(before) - vec2(2.0, 2.0); + h.input_mut().events.push(Event::PointerMoved(grab)); + h.run_steps(2); + assert_eq!(h.output().platform_output.cursor_icon, egui::CursorIcon::Move); + drag(&mut h, grab, grab + vec2(30.0, 20.0)); + let after = rect(&h, o.shape); + assert_eq!(h.state().selected_object, Some(o.shape)); + assert!((after.size() - before.size()).length() < 0.5, "not resized: {before:?} {after:?}"); + assert!((after.min - before.min - vec2(30.0, 20.0)).length() < 1.0, "moved with the pointer: {before:?} {after:?}"); +}