mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-26 11:16:50 -06:00
Selection tool: Alt and Shift resize around the centre and keep side-handle drags centred
This commit is contained in:
+32
-39
@@ -178,12 +178,18 @@ fn resize_edges_rect(from: Rect, handle: usize, p: Point) -> Rect {
|
||||
}
|
||||
|
||||
fn finish_resize(from: Rect, handle: usize, rect: Rect, m: Mods) -> Rect {
|
||||
let rect = if m.shift && from.width() > 1e-9 && from.height() > 1e-9 {
|
||||
apply_aspect(from, handle, rect, aspect_scale(from, handle, rect))
|
||||
} else {
|
||||
rect
|
||||
};
|
||||
if m.alt { apply_alt(from, handle, rect) } else { rect }
|
||||
// Alt first: Shift then measures the drag from the centre and keeps it there.
|
||||
let rect = if m.alt { apply_alt(from, handle, rect) } else { rect };
|
||||
if !(m.shift && from.width() > 1e-9 && from.height() > 1e-9) {
|
||||
return rect;
|
||||
}
|
||||
let s = aspect_scale(from, handle, rect);
|
||||
if m.alt { centered_rect(from, from.width() * s, from.height() * s) } else { apply_aspect(from, handle, rect, s) }
|
||||
}
|
||||
|
||||
fn centered_rect(from: Rect, w: f64, h: f64) -> Rect {
|
||||
let c = from.center();
|
||||
Rect::new(c.x - w / 2.0, c.y - h / 2.0, c.x + w / 2.0, c.y + h / 2.0)
|
||||
}
|
||||
|
||||
fn aspect_scale(from: Rect, handle: usize, rect: Rect) -> f64 {
|
||||
@@ -214,31 +220,17 @@ fn drives_x(handle: usize, from: Rect, rect: Rect) -> bool {
|
||||
sx > sy
|
||||
}
|
||||
|
||||
/// Scales `from` by `s` with the edges opposite the handle fixed. An axis the handle doesn't move stays centred.
|
||||
fn apply_aspect(from: Rect, handle: usize, rect: Rect, s: f64) -> Rect {
|
||||
let (mut x0, mut y0, mut x1, mut y1) = (rect.x0, rect.y0, rect.x1, rect.y1);
|
||||
let (w, h) = (from.width() * s, from.height() * s);
|
||||
match handle {
|
||||
0 => {
|
||||
x0 = x1 - w;
|
||||
y0 = y1 - h;
|
||||
}
|
||||
2 => {
|
||||
x1 = x0 + w;
|
||||
y0 = y1 - h;
|
||||
}
|
||||
3..=5 => {
|
||||
x1 = x0 + w;
|
||||
y1 = y0 + h;
|
||||
}
|
||||
6 | 7 => {
|
||||
x0 = x1 - w;
|
||||
y1 = y0 + h;
|
||||
}
|
||||
_ => {
|
||||
x0 = x1 - w;
|
||||
y0 = y1 - h;
|
||||
}
|
||||
}
|
||||
let (x_edges, y_edges) = resize_edge_flags(handle);
|
||||
let span = |edges: [bool; 3], lo: f64, hi: f64, mid: f64, len: f64| match edges {
|
||||
[true, _, _] => (hi - len, hi),
|
||||
[_, _, true] => (lo, lo + len),
|
||||
_ => (mid - len / 2.0, mid + len / 2.0),
|
||||
};
|
||||
let c = from.center();
|
||||
let (x0, x1) = span(x_edges, rect.x0, rect.x1, c.x, from.width() * s);
|
||||
let (y0, y1) = span(y_edges, rect.y0, rect.y1, c.y, from.height() * s);
|
||||
Rect::new(x0, y0, x1, y1)
|
||||
}
|
||||
|
||||
@@ -285,8 +277,7 @@ fn finish_snapped_resize(
|
||||
if !(w.is_finite() && h.is_finite()) {
|
||||
return proposed;
|
||||
}
|
||||
let c = from.center();
|
||||
Rect::new(c.x - w / 2.0, c.y - h / 2.0, c.x + w / 2.0, c.y + h / 2.0)
|
||||
centered_rect(from, w, h)
|
||||
}
|
||||
|
||||
/// Length match: add the difference once. Position match: the flagged edge moves and the other side mirrors.
|
||||
@@ -313,16 +304,18 @@ fn axis_size_delta(edges: [bool; 3], delta: f64) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors the dragged sides around the centre of `from`. Shrinking and crossing the centre follow the pointer.
|
||||
fn apply_alt(from: Rect, handle: usize, rect: Rect) -> Rect {
|
||||
let (x0, y0, x1, y1) = (rect.x0, rect.y0, rect.x1, rect.y1);
|
||||
let (x_edges, y_edges) = resize_edge_flags(handle);
|
||||
let c = from.center();
|
||||
let (hw, hh) = ((x1 - x0) / 2.0, (y1 - y0) / 2.0);
|
||||
let (hw, hh) = match handle {
|
||||
1 | 5 => (from.width() / 2.0, (if handle == 1 { c.y - y0 } else { y1 - c.y })),
|
||||
3 | 7 => ((if handle == 7 { c.x - x0 } else { x1 - c.x }), from.height() / 2.0),
|
||||
_ => (hw.abs().max((c.x - x0).abs()).max((x1 - c.x).abs()), hh.abs().max((c.y - y0).abs()).max((y1 - c.y).abs())),
|
||||
let size = |edges: [bool; 3], lo: f64, hi: f64, mid: f64, len: f64| match edges {
|
||||
[true, _, _] => 2.0 * (mid - lo),
|
||||
[_, _, true] => 2.0 * (hi - mid),
|
||||
_ => len,
|
||||
};
|
||||
Rect::new(c.x - hw, c.y - hh, c.x + hw, c.y + hh)
|
||||
let w = size(x_edges, rect.x0, rect.x1, c.x, from.width());
|
||||
let h = size(y_edges, rect.y0, rect.y1, c.y, from.height());
|
||||
centered_rect(from, w, h)
|
||||
}
|
||||
|
||||
/// Left/center/right and top/center/bottom for a handle. Centers stay off.
|
||||
|
||||
@@ -60,6 +60,31 @@ fn resize_rect_modifiers() {
|
||||
assert_eq!(c, Rect::new(-50.0, 0.0, 150.0, 50.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_side_shift_keeps_the_other_axis_centred() {
|
||||
let r = Rect::new(0.0, 0.0, 100.0, 50.0);
|
||||
let shift = Mods { shift: true, ..Default::default() };
|
||||
assert_eq!(select::resize_rect(r, 3, Point::new(150.0, 25.0), shift), Rect::new(0.0, -12.5, 150.0, 62.5));
|
||||
assert_eq!(select::resize_rect(r, 5, Point::new(50.0, 100.0), shift), Rect::new(-50.0, 0.0, 150.0, 100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_corner_alt_shift_scales_around_the_centre() {
|
||||
let r = Rect::new(0.0, 0.0, 100.0, 50.0);
|
||||
let mods = Mods { shift: true, alt: true, ..Default::default() };
|
||||
// From the centre the pointer is at 0.2 by 0.6; the larger factor drives and the corner stays on y = 40.
|
||||
assert_eq!(select::resize_rect(r, 4, Point::new(60.0, 40.0), mods), Rect::new(20.0, 10.0, 80.0, 40.0));
|
||||
assert_eq!(select::resize_rect(r, 0, Point::new(-50.0, 10.0), mods), Rect::new(-50.0, -25.0, 150.0, 75.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_corner_alt_mirrors_the_opposite_side() {
|
||||
let r = Rect::new(0.0, 0.0, 100.0, 50.0);
|
||||
let alt = Mods { alt: true, ..Default::default() };
|
||||
assert_eq!(select::resize_rect(r, 0, Point::new(10.0, 5.0), alt), Rect::new(10.0, 5.0, 90.0, 45.0));
|
||||
assert_eq!(select::resize_rect(r, 4, Point::new(120.0, 60.0), alt), Rect::new(-20.0, -10.0, 120.0, 60.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmd_shift_click_overrides_parent_item() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
|
||||
Reference in New Issue
Block a user