Files
Ely-GPUI-Components/src/media/crop.rs
T
ZacharyZhang-NY 1788753f6d Media: a shaped corner follows the pointer up and down; the fitted crop is told once
With an aspect, a corner drag sizes the crop by whichever side the pointer
moved further, so a drag straight up or down resizes it too. The upload
tells the host the fitted crop once per file, whether the host keeps it or
not; a host that dropped it had set off a loop of reports. The annotator's
Escape stops only when it lets a chosen mark go.
2026-09-26 16:38:22 -04:00

373 lines
10 KiB
Rust

/// A crop of a picture: its box in shares of the picture's width and height, each from 0 to 1.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Crop {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
impl Crop {
/// The whole picture.
pub const WHOLE: Self = Self {
x: 0.0,
y: 0.0,
w: 1.0,
h: 1.0,
};
/// The largest box of `aspect`, its width over height in pixels, inside this one and centered on it, on a picture of `ratio`.
pub fn fitted(self, aspect: f32, ratio: f32) -> Self {
fitted(self, aspect / ratio)
}
/// Fails loud on a box with no size or one that leaves its picture.
pub(crate) fn checked(self) -> Self {
let inside =
|start: f32, side: f32| start >= 0.0 && side > 0.0 && start + side <= 1.0 + 1e-4;
assert!(
inside(self.x, self.w) && inside(self.y, self.h),
"a crop past its picture: {self:?}"
);
self
}
}
/// The sides a drag moves: none moves the whole box, one an edge, two a corner.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub(crate) struct Grip {
pub left: bool,
pub right: bool,
pub top: bool,
pub bottom: bool,
}
impl Grip {
pub(crate) fn corner(self) -> bool {
(self.left || self.right) && (self.top || self.bottom)
}
}
/// What a press at `at`, in shares of the picture, holds: a side or corner within `reach` shares across and down, else the body when inside, else nothing. A shaped crop sizes by corners only, so its edges hold the body.
pub(crate) fn grip_at(crop: Crop, at: (f32, f32), reach: (f32, f32), shaped: bool) -> Option<Grip> {
let (x2, y2) = (crop.x + crop.w, crop.y + crop.h);
let near = |a: f32, b: f32, r: f32| (a - b).abs() <= r;
let around = at.0 >= crop.x - reach.0
&& at.0 <= x2 + reach.0
&& at.1 >= crop.y - reach.1
&& at.1 <= y2 + reach.1;
let inside = at.0 >= crop.x && at.0 <= x2 && at.1 >= crop.y && at.1 <= y2;
let (left, top) = (near(at.0, crop.x, reach.0), near(at.1, crop.y, reach.1));
let grip = Grip {
left,
right: !left && near(at.0, x2, reach.0),
top,
bottom: !top && near(at.1, y2, reach.1),
};
let sizes = grip != Grip::default() && (grip.corner() || !shaped);
match (around, sizes, inside) {
(true, true, _) => Some(grip),
(_, _, true) => Some(Grip::default()),
_ => None,
}
}
/// The crop after `grip` moves by `by`, in shares, kept inside the picture with sides of at least `least`. With `shape`, a width over height in shares, only corners resize and the box keeps that shape, anchored at the opposite corner, sized by whichever side the pointer moved further.
pub(crate) fn dragged(
crop: Crop,
grip: Grip,
by: (f32, f32),
least: f32,
shape: Option<f32>,
) -> Crop {
let (mut x1, mut y1, mut x2, mut y2) = (crop.x, crop.y, crop.x + crop.w, crop.y + crop.h);
if grip == Grip::default() {
let dx = by.0.clamp(-x1, 1.0 - x2);
let dy = by.1.clamp(-y1, 1.0 - y2);
return Crop {
x: x1 + dx,
y: y1 + dy,
..crop
};
}
if shape.is_some() && !grip.corner() {
return crop;
}
if grip.left {
x1 = (x1 + by.0).clamp(0.0, x2 - least);
}
if grip.right {
x2 = (x2 + by.0).clamp(x1 + least, 1.0);
}
if grip.top {
y1 = (y1 + by.1).clamp(0.0, y2 - least);
}
if grip.bottom {
y2 = (y2 + by.1).clamp(y1 + least, 1.0);
}
if let Some(shape) = shape {
let tall = if grip.top { y2 } else { 1.0 - y1 };
let wide = if grip.left { x2 } else { 1.0 - x1 };
let (across, down) = (x2 - x1, (y2 - y1) * shape);
let asked = if (across - crop.w).abs() >= (down - crop.w).abs() {
across
} else {
down
};
let w = asked
.max(least * shape.max(1.0))
.min(tall * shape)
.min(wide);
let h = w / shape;
if grip.left {
x1 = x2 - w;
} else {
x2 = x1 + w;
}
if grip.top {
y1 = y2 - h;
} else {
y2 = y1 + h;
}
}
Crop {
x: x1,
y: y1,
w: x2 - x1,
h: y2 - y1,
}
}
/// The largest box of `shape`, a width over height in shares, inside `crop` and centered on it.
pub(crate) fn fitted(crop: Crop, shape: f32) -> Crop {
let (w, h) = if crop.w / crop.h > shape {
(crop.h * shape, crop.h)
} else {
(crop.w, crop.w / shape)
};
Crop {
x: crop.x + (crop.w - w) / 2.0,
y: crop.y + (crop.h - h) / 2.0,
w,
h,
}
}
#[cfg(test)]
mod tests {
use super::{Crop, Grip, dragged, fitted, grip_at};
const HALF: Crop = Crop {
x: 0.25,
y: 0.25,
w: 0.5,
h: 0.5,
};
fn near(a: Crop, b: Crop) -> bool {
[(a.x, b.x), (a.y, b.y), (a.w, b.w), (a.h, b.h)]
.iter()
.all(|(a, b)| (a - b).abs() < 1e-5)
}
#[test]
fn the_body_moves_whole_and_stops_at_the_picture_edge() {
let moved = dragged(HALF, Grip::default(), (0.1, -0.4), 0.05, None);
assert!(
near(
moved,
Crop {
x: 0.35,
y: 0.0,
..HALF
}
),
"{moved:?}"
);
}
#[test]
fn an_edge_or_corner_moves_its_sides_and_keeps_the_least_size() {
let right = Grip {
right: true,
..Grip::default()
};
assert!(near(
dragged(HALF, right, (0.1, 0.3), 0.05, None),
Crop { w: 0.6, ..HALF }
));
assert!(near(
dragged(HALF, right, (-0.9, 0.0), 0.05, None),
Crop { w: 0.05, ..HALF }
));
let top_left = Grip {
left: true,
top: true,
..Grip::default()
};
let grown = dragged(HALF, top_left, (-0.5, -0.1), 0.05, None);
assert!(
near(
grown,
Crop {
x: 0.0,
y: 0.15,
w: 0.75,
h: 0.6
}
),
"{grown:?}"
);
}
#[test]
fn a_shaped_crop_resizes_by_corners_only_and_keeps_its_shape_inside_the_picture() {
let bottom_right = Grip {
right: true,
bottom: true,
..Grip::default()
};
let square = dragged(HALF, bottom_right, (0.1, 0.0), 0.05, Some(1.0));
assert!(
near(
square,
Crop {
w: 0.6,
h: 0.6,
..HALF
}
),
"{square:?}"
);
let stopped = dragged(HALF, bottom_right, (0.25, 0.0), 0.05, Some(2.0));
assert!(
near(
stopped,
Crop {
w: 0.75,
h: 0.375,
..HALF
}
),
"{stopped:?}"
);
let edge = Grip {
top: true,
..Grip::default()
};
assert_eq!(dragged(HALF, edge, (0.0, -0.1), 0.05, Some(1.0)), HALF);
}
#[test]
fn a_shaped_corner_follows_the_pointer_up_and_down_too() {
let bottom_right = Grip {
right: true,
bottom: true,
..Grip::default()
};
let down = dragged(HALF, bottom_right, (0.0, 0.2), 0.05, Some(1.0));
assert!(
near(
down,
Crop {
w: 0.7,
h: 0.7,
..HALF
}
),
"straight down grows it: {down:?}"
);
let up = dragged(HALF, bottom_right, (0.0, -0.2), 0.05, Some(1.0));
assert!(
near(
up,
Crop {
w: 0.3,
h: 0.3,
..HALF
}
),
"straight up shrinks it: {up:?}"
);
}
#[test]
fn a_new_shape_takes_the_largest_centered_box_inside() {
let wide = fitted(HALF, 2.0);
assert!(
near(
wide,
Crop {
x: 0.25,
y: 0.375,
w: 0.5,
h: 0.25
}
),
"{wide:?}"
);
let tall = fitted(Crop::WHOLE, 0.5);
assert!(
near(
tall,
Crop {
x: 0.25,
y: 0.0,
w: 0.5,
h: 1.0
}
),
"{tall:?}"
);
}
#[test]
fn a_press_holds_the_nearest_corner_or_side_else_the_body_else_nothing() {
let reach = (0.02, 0.02);
let corner = grip_at(HALF, (0.26, 0.74), reach, false);
assert_eq!(
corner,
Some(Grip {
left: true,
bottom: true,
..Grip::default()
})
);
let side = grip_at(HALF, (0.5, 0.24), reach, false);
assert_eq!(
side,
Some(Grip {
top: true,
..Grip::default()
})
);
assert_eq!(
grip_at(HALF, (0.5, 0.5), reach, false),
Some(Grip::default())
);
assert_eq!(grip_at(HALF, (0.1, 0.5), reach, false), None);
assert_eq!(
grip_at(HALF, (0.5, 0.26), reach, true),
Some(Grip::default()),
"a shaped crop's edge holds its body"
);
assert_eq!(
grip_at(HALF, (0.5, 0.24), reach, true),
None,
"and just outside it holds nothing"
);
}
#[test]
#[should_panic(expected = "a crop past its picture")]
fn a_crop_past_its_picture_fails_loud() {
Crop {
x: 0.8,
y: 0.0,
w: 0.5,
h: 0.5,
}
.checked();
}
}