From e812da58df5b2d1c268b6674c6b10dbee67ce8ce Mon Sep 17 00:00:00 2001 From: DominicTheD3v <119626487+DominicTheD3v@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:51:33 +0000 Subject: [PATCH] fix: addressing issues --- crates/engine/src/tests.rs | 12 ++++++++--- crates/engine/src/tools.rs | 2 ++ crates/engine/src/tools/drag.rs | 33 +++++++++++++++++++++++++----- crates/engine/src/tools/pointer.rs | 3 ++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index b72dcbd7..ef18a31f 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -5504,6 +5504,7 @@ fn pending_resize_autofit(s: &mut Session, ids: &[u64]) { .unwrap(); } +/// Resizing multiple selected shapes scales them proportionally around their anchors in one undo step. #[test] fn resizing_selected_shapes_is_one_undo_step() { let (mut s, [a, b]) = resize_selection_scene(); @@ -5512,7 +5513,7 @@ fn resizing_selected_shapes_is_one_undo_step() { let down = drag_gesture(&mut s, (200.0, 180.0), (250.0, 210.0)); assert_eq!(down["hit"]["kind"], "resize"); assert_eq!(down["hit"]["id"], a); - for (id, expected) in [(a, [100.0, 100.0, 150.0, 110.0]), (b, [120.0, 300.0, 130.0, 130.0])] { + for (id, expected) in [(a, [100.0, 100.0, 150.0, 110.0]), (b, [120.0, 300.0, 120.0, 137.5])] { let actual = box_of(&mut s, id); for (key, value) in ["x", "y", "w", "h"].into_iter().zip(expected) { assert_eq!(actual[key], value, "{id}: {key}"); @@ -5526,6 +5527,7 @@ fn resizing_selected_shapes_is_one_undo_step() { assert_eq!(s.doc().unwrap().doc, after, "one redo resizes both shapes again"); } +/// Resizing selected shapes refits their text boxes inside the same undo transaction. #[test] fn resizing_selected_shapes_refits_text_in_the_same_undo_step() { let (mut s, ids) = resize_selection_scene(); @@ -5545,6 +5547,7 @@ fn resizing_selected_shapes_refits_text_in_the_same_undo_step() { assert_eq!(s.doc().unwrap().doc, after, "redo includes the final text fit"); } +/// Clicking a resize handle without dragging neither modifies shapes nor triggers text autofit. #[test] fn clicking_a_resize_handle_does_not_change_or_refit_selected_shapes() { let (mut s, ids) = resize_selection_scene(); @@ -5566,6 +5569,7 @@ fn clicking_a_resize_handle_does_not_change_or_refit_selected_shapes() { assert_ne!(s.doc().unwrap().doc, before); } +/// Locked shapes in a multi-selection are excluded from both resizing and post-resize text fitting. #[test] fn resizing_selected_shapes_skips_locked_shapes_and_their_text_fit() { let (mut s, [a, b]) = resize_selection_scene(); @@ -5584,6 +5588,7 @@ fn resizing_selected_shapes_skips_locked_shapes_and_their_text_fit() { assert_eq!(s.doc().unwrap().doc, before); } +/// Resizing selected shapes excludes other selected resize targets from smart-guide snapping. #[test] fn resizing_selected_shapes_does_not_snap_to_other_resize_targets() { for reverse in [false, true] { @@ -5594,10 +5599,11 @@ fn resizing_selected_shapes_does_not_snap_to_other_resize_targets() { // Within snap tolerance of the other selected shape's original right edge. drag_gesture(&mut s, (200.0, 140.0), (203.0, 140.0)); assert_eq!(box_of(&mut s, a)["w"], 103.0, "first shape must not snap back"); - assert_eq!(box_of(&mut s, b)["w"], 83.0, "selection order must not affect snapping"); + assert_eq!(box_of(&mut s, b)["w"], 82.4, "selection order must not affect snapping"); } } +/// Resizing selected shapes snaps the active shape against unselected stationary shapes and scales others proportionally. #[test] fn resizing_selected_shapes_still_snaps_to_stationary_shapes() { let (mut s, [a, b]) = resize_selection_scene(); @@ -5607,7 +5613,7 @@ fn resizing_selected_shapes_still_snaps_to_stationary_shapes() { s.prefs.smart_guides = true; drag_gesture(&mut s, (200.0, 140.0), (203.0, 140.0)); assert_eq!(box_of(&mut s, a)["w"], 107.0); - assert_eq!(box_of(&mut s, b)["w"], 87.0); + assert!((box_of(&mut s, b)["w"].as_f64().unwrap() - 85.6).abs() < 1e-9); assert_eq!(box_of(&mut s, fixed), fixed_before); } diff --git a/crates/engine/src/tools.rs b/crates/engine/src/tools.rs index 19440fb3..82f53dfc 100644 --- a/crates/engine/src/tools.rs +++ b/crates/engine/src/tools.rs @@ -149,6 +149,8 @@ enum Drag { enter_text: Option<(ShapeId, Point)>, }, Resize { + id: ShapeId, + orig: Xfrm, ids: Vec, handle: u8, origs: Vec<(ShapeId, Xfrm)>, diff --git a/crates/engine/src/tools/drag.rs b/crates/engine/src/tools/drag.rs index 7dca3822..367e6b85 100644 --- a/crates/engine/src/tools/drag.rs +++ b/crates/engine/src/tools/drag.rs @@ -26,11 +26,34 @@ impl Session { Drag::Move { ids, start, origs, moved, duplicate, enter_text } => { return self.drag_move(MoveDrag { ids, start, origs, moved, duplicate, enter_text }, p, mods, tol); } - Drag::Resize { ids, handle, origs, start } => { - for (id, o) in &origs { - let nx = resize_box(o, handle, p - start, mods.shift || self.is_picture(*id), mods.alt); - let nx = self.snap_resize(nx, handle, &ids, tol); - self.set_xfrm(*id, nx)?; + Drag::Resize { id, orig, ids, handle, origs, start } => { + let nx = resize_box(&orig, handle, p - start, mods.shift || self.is_picture(id), mods.alt); + let nx = self.snap_resize(nx, handle, &ids, tol); + let sx = if orig.w > 1e-9 { (nx.w / orig.w) * if nx.flip_h != orig.flip_h { -1.0 } else { 1.0 } - 1.0 } else { 0.0 }; + let sy = if orig.h > 1e-9 { (nx.h / orig.h) * if nx.flip_v != orig.flip_v { -1.0 } else { 1.0 } - 1.0 } else { 0.0 }; + let div = if mods.alt { 2.0 } else { 1.0 }; + let hx = match handle { + 0 | 6 | 7 => -1.0, + 2..=4 => 1.0, + _ => 0.0, + }; + let hy = match handle { + 0..=2 => -1.0, + 4..=6 => 1.0, + _ => 0.0, + }; + for (tid, o) in &origs { + let target_nx = if *tid == id { + nx + } else { + let lp = Affine::rotate(o.rot.to_radians()) + * Point::new( + hx * sx * o.w / div * if o.flip_h { -1.0 } else { 1.0 }, + hy * sy * o.h / div * if o.flip_v { -1.0 } else { 1.0 }, + ); + resize_box(o, handle, Vec2::new(lp.x, lp.y), mods.shift || self.is_picture(*tid), mods.alt) + }; + self.set_xfrm(*tid, target_nx)?; } } Drag::LineEnd { id, end, orig } => { diff --git a/crates/engine/src/tools/pointer.rs b/crates/engine/src/tools/pointer.rs index dbaf495f..1f4cba0c 100644 --- a/crates/engine/src/tools/pointer.rs +++ b/crates/engine/src/tools/pointer.rs @@ -302,12 +302,13 @@ impl Session { Some(Hit::TableColumn { id, col }) => self.begin_table_border(id, true, col, p)?, Some(Hit::TableRow { id, row }) => self.begin_table_border(id, false, row, p)?, Some(Hit::Resize { id, handle }) => { + let orig = st.shape(id).map(|s| xfrm_of(&st.doc, &st.selection, s)).unwrap_or_default(); let ids: Vec = if st.selection.shapes.contains(&id) { st.selection.shapes.clone() } else { vec![id] }; let origs: Vec<(ShapeId, Xfrm)> = ids.iter().filter_map(|i| st.shape(*i).filter(|s| !s.locked).map(|s| (*i, xfrm_of(&st.doc, &st.selection, s)))).collect(); let ids: Vec = origs.iter().map(|(id, _)| *id).collect(); self.begin("Size"); - self.tool.drag = Some(Drag::Resize { ids, handle, origs, start: p }); + self.tool.drag = Some(Drag::Resize { id, orig, ids, handle, origs, start: p }); } Some(Hit::LineEnd { id, end }) => { let orig = st.shape(id).map(|s| xfrm_of(&st.doc, &st.selection, s)).unwrap_or_default();