mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
LR-CROP-RECT: a crop a handle drag left at a tilted edge still moves (#2299)
A handle drag stops where is_within_image's 1e-6 tolerance ends, so a corner can sit right at it. The move solve then let that corner go 1e-9 further, the result failed the check, and the across-then-down fallback left the frame stuck. Solve first with every corner kept within half the tolerance, and only then where it is.
This commit is contained in:
@@ -208,16 +208,22 @@ pub fn drag_crop(start: CropGeometry, handle: CropHandle, grab: Point, pointer:
|
||||
|
||||
/// The translation (pixels) nearest `want` that keeps the frame `r` inside a `w × h` image turned by `angle` degrees.
|
||||
fn nearest_move(r: Rect, angle: f64, w: f64, h: f64, want: Vec2) -> Option<Vec2> {
|
||||
// first within half of `is_within_image`'s tolerance, so a frame a handle drag left at its edge still moves
|
||||
solve_move(r, angle, (w, h), want, 5e-7).or_else(|| solve_move(r, angle, (w, h), want, f64::INFINITY))
|
||||
}
|
||||
|
||||
/// [`nearest_move`] with each corner kept at most `pull` (a fraction of the size) past the image, or where it is.
|
||||
fn solve_move(r: Rect, angle: f64, (w, h): (f64, f64), want: Vec2, pull: f64) -> Option<Vec2> {
|
||||
let back = Affine::rotate_about(-angle.to_radians(), Point::new(w / 2.0, h / 2.0));
|
||||
let (ex, ey) = (back.apply_vec(Vec2::new(1.0, 0.0)), back.apply_vec(Vec2::new(0.0, 1.0)));
|
||||
let (nx, ny) = (Vec2::new(ex.x, ey.x), Vec2::new(ex.y, ey.y));
|
||||
// never tighter than where the frame is, plus a slack so a zero-width strip survives rounding
|
||||
// plus a slack so a zero-width strip survives rounding
|
||||
let (sx, sy) = (1e-9 * w, 1e-9 * h);
|
||||
let mut planes = Vec::with_capacity(16);
|
||||
for c in r.corners() {
|
||||
let q = back.apply(c);
|
||||
for (n, b, s) in [(nx, w - q.x, sx), (-nx, q.x, sx), (ny, h - q.y, sy), (-ny, q.y, sy)] {
|
||||
planes.push((n, b.max(0.0) + s));
|
||||
for (n, b, s, size) in [(nx, w - q.x, sx, w), (-nx, q.x, sx, w), (ny, h - q.y, sy, h), (-ny, q.y, sy, h)] {
|
||||
planes.push((n, b.max(0.0).min(b + pull * size) + s));
|
||||
}
|
||||
}
|
||||
if planes.iter().all(|(n, b)| n.dot(want) <= *b) {
|
||||
@@ -578,6 +584,25 @@ mod tests {
|
||||
assert!(g.is_within_image(W, H));
|
||||
}
|
||||
|
||||
// Scenario: a frame a handle drag enlarged to a tilted edge (review of #2299)
|
||||
#[test]
|
||||
fn given_a_frame_enlarged_to_a_tilted_edge_by_a_handle_when_moved_then_it_still_slides() {
|
||||
let a = 2.0 / 3.0;
|
||||
for angle in [3.0, 6.0, -10.0, 12.0] {
|
||||
let fit = crate::crop_fit_angle(W, H, angle, Some(a));
|
||||
let small = CropGeometry { rect: Rect::from_center(fit.rect.center(), fit.rect.width() * 0.6, fit.rect.height() * 0.6), angle };
|
||||
let big = drag(small, BottomRight, p(small.rect.x1, small.rect.y1), p(1.5, 1.5), Some(a));
|
||||
let c = big.rect.center();
|
||||
let g = drag(big, Move, c, p(c.x - 0.5, c.y), Some(a));
|
||||
assert!(g.rect.x0 < big.rect.x0 - 0.2, "{angle}: stuck at {:?}", g.rect);
|
||||
assert!(
|
||||
g.is_within_image(W, H) && near(g.rect.width(), big.rect.width()) && near(g.rect.height(), big.rect.height()),
|
||||
"{angle}: {:?}",
|
||||
g.rect
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario: a smaller frame pushed sideways past a tilted edge slides along it (#2299)
|
||||
#[test]
|
||||
fn given_a_small_frame_pushed_past_a_tilted_edge_then_it_stops_on_the_edge_and_slides_along_it() {
|
||||
|
||||
Reference in New Issue
Block a user