use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GeometryError;
impl fmt::Display for GeometryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid motion geometry")
}
}
impl std::error::Error for GeometryError {}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LayoutRect {
origin: [f64; 2],
size: [f64; 2],
}
impl LayoutRect {
pub fn new(origin: [f64; 2], size: [f64; 2]) -> Result<Self, GeometryError> {
if origin.iter().any(|v| !v.is_finite() || v.abs() > 1e12)
|| size.iter().any(|v| !v.is_finite() || *v < 0.0 || *v > 1e12)
{
return Err(GeometryError);
}
Ok(Self { origin, size })
}
pub fn origin(self) -> [f64; 2] {
self.origin
}
pub fn size(self) -> [f64; 2] {
self.size
}
pub fn center(self) -> [f64; 2] {
std::array::from_fn(|i| self.origin[i] + self.size[i] * 0.5)
}
pub fn intersection(self, other: Self) -> Self {
let origin = std::array::from_fn(|i| self.origin[i].max(other.origin[i]));
Self {
origin,
size: std::array::from_fn(|i| {
((self.origin[i] + self.size[i]).min(other.origin[i] + other.size[i]) - origin[i])
.max(0.0)
}),
}
}
pub fn inset(self, margins: [f64; 4]) -> Self {
assert!(
margins.iter().all(|v| v.is_finite() && v.abs() <= 1e12),
"invalid viewport margin"
);
Self {
origin: [self.origin[0] + margins[3], self.origin[1] + margins[0]],
size: [
(self.size[0] - margins[1] - margins[3]).max(0.0),
(self.size[1] - margins[0] - margins[2]).max(0.0),
],
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DragBounds {
min: [f64; 2],
max: [f64; 2],
}
impl DragBounds {
pub fn within(container: LayoutRect, element: LayoutRect) -> Self {
let min = std::array::from_fn(|i| container.origin[i] - element.origin[i]);
Self {
min,
max: std::array::from_fn(|i| min[i] + (container.size[i] - element.size[i]).max(0.0)),
}
}
pub fn min(self) -> [f64; 2] {
self.min
}
pub fn max(self) -> [f64; 2] {
self.max
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollEdge {
target: f64,
container: f64,
pixels: f64,
}
impl ScrollEdge {
pub fn new(target: f64, container: f64, pixels: f64) -> Self {
assert!(
[target, container, pixels]
.iter()
.all(|v| v.is_finite() && v.abs() <= 1e12),
"invalid scroll intersection"
);
Self {
target,
container,
pixels,
}
}
fn distance(self, container: LayoutRect, target: LayoutRect, axis: usize) -> f64 {
target.origin[axis] + target.size[axis] * self.target
- container.origin[axis]
- container.size[axis] * self.container
+ self.pixels
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollOffsets {
start: ScrollEdge,
end: ScrollEdge,
clamp: bool,
}
impl Default for ScrollOffsets {
fn default() -> Self {
Self::new(
ScrollEdge::new(0.0, 1.0, 0.0),
ScrollEdge::new(1.0, 0.0, 0.0),
)
}
}
impl ScrollOffsets {
pub fn new(start: ScrollEdge, end: ScrollEdge) -> Self {
Self {
start,
end,
clamp: true,
}
}
pub fn unclamped(mut self) -> Self {
self.clamp = false;
self
}
pub fn progress(self, container: LayoutRect, target: LayoutRect, axis: usize) -> f64 {
assert!(axis < 2, "scroll axis must be zero or one");
let start = self.start.distance(container, target, axis);
let end = self.end.distance(container, target, axis);
let progress = if (start - end).abs() <= f64::EPSILON {
0.0
} else {
start / (start - end)
};
if self.clamp {
progress.clamp(0.0, 1.0)
} else {
progress
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct AutoScroll {
edge: f64,
speed: f64,
}
impl Default for AutoScroll {
fn default() -> Self {
Self {
edge: 48.0,
speed: 600.0,
}
}
}
impl AutoScroll {
pub fn new(edge: f64, speed: f64) -> Result<Self, GeometryError> {
if !edge.is_finite() || edge <= 0.0 || !speed.is_finite() || speed < 0.0 {
return Err(GeometryError);
}
Ok(Self { edge, speed })
}
pub fn velocity(self, pointer: f64, start: f64, length: f64) -> f64 {
assert!(
[pointer, start, length].iter().all(|v| v.is_finite()) && length >= 0.0,
"invalid scroll metrics"
);
if length == 0.0 {
return 0.0;
}
let edge = self.edge.min(length * 0.5);
let leading = ((start + edge - pointer) / edge).clamp(0.0, 1.0);
let trailing = ((pointer - (start + length - edge)) / edge).clamp(0.0, 1.0);
(trailing * trailing - leading * leading) * self.speed
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ReorderAxis {
#[default]
Auto,
X,
Y,
XY,
}
impl ReorderAxis {
pub fn resolve(self, rects: &[LayoutRect]) -> Self {
if self != Self::Auto {
return self;
}
if rects.len() < 2 {
return Self::Y;
}
let overlap = |axis: usize| {
let leading = rects
.iter()
.map(|r| r.origin[axis])
.fold(f64::NEG_INFINITY, f64::max);
let trailing = rects
.iter()
.map(|r| r.origin[axis] + r.size[axis])
.fold(f64::INFINITY, f64::min);
leading < trailing
};
if overlap(1) {
Self::X
} else if overlap(0) {
Self::Y
} else {
Self::XY
}
}
pub fn destination(self, position: [f64; 2], rects: &[LayoutRect]) -> Option<usize> {
assert!(
position.iter().all(|v| v.is_finite()),
"invalid reorder position"
);
let axis = self.resolve(rects);
rects
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
let distance = |rect: LayoutRect| {
let c = rect.center();
let x = if axis == Self::Y {
0.0
} else {
c[0] - position[0]
};
let y = if axis == Self::X {
0.0
} else {
c[1] - position[1]
};
x.hypot(y)
};
distance(**a).total_cmp(&distance(**b))
})
.map(|(i, _)| i)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ScrollFrame {
pub progress: f64,
pub velocity: f64,
pub offset: [f64; 2],
pub ready: bool,
pub active: bool,
}
impl ScrollFrame {
pub fn target(self, from: crate::Target, to: crate::Target) -> crate::Target {
let from = from.inherit(to);
let to = to.inherit(from);
let a = from.values();
let b = to.values();
crate::Target::from_values(
std::array::from_fn(|i| crate::value::finite(a[i] + (b[i] - a[i]) * self.progress)),
from.mask() | to.mask(),
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GesturePhase {
Started,
Moved,
Ended,
Cancelled,
}
#[derive(Clone, Copy, Debug)]
pub struct DragEvent {
pub phase: GesturePhase,
pub point: [f64; 2],
pub delta: [f64; 2],
pub offset: [f64; 2],
pub velocity: [f64; 2],
pub locked_axis: Option<usize>,
}
#[derive(Clone, Copy, Debug)]
pub struct PinchGesture {
pub phase: GesturePhase,
pub center: [f64; 2],
pub scale: f64,
pub delta: f64,
}