use rio_backend::sugarloaf::Sugarloaf;
use std::time::Instant;
const ANIMATION_LENGTH: f32 = 0.15;
const SHORT_ANIMATION_LENGTH: f32 = 0.04;
const TRAIL_SIZE: f32 = 1.0;
const DEPTH: f32 = 0.0;
const ORDER: u8 = 10;
#[derive(Clone)]
struct Spring {
position: f32,
velocity: f32,
}
impl Spring {
#[inline]
fn new() -> Self {
Self {
position: 0.0,
velocity: 0.0,
}
}
#[inline]
fn reset(&mut self) {
self.position = 0.0;
self.velocity = 0.0;
}
#[inline]
fn update(&mut self, dt: f32, animation_length: f32) -> bool {
if animation_length <= dt {
self.reset();
return false;
}
if self.position == 0.0 {
return false;
}
let omega = 4.0 / animation_length;
let a = self.position;
let b = a * omega + self.velocity;
let c = (-omega * dt).exp();
self.position = (a + b * dt) * c;
self.velocity = c * (-a * omega - b * dt * omega + b);
if self.position.abs() < 0.01 {
self.reset();
false
} else {
true
}
}
}
#[derive(Clone)]
struct Corner {
spring_x: Spring,
spring_y: Spring,
x: f32,
y: f32,
rel_x: f32,
rel_y: f32,
prev_dest_x: f32,
prev_dest_y: f32,
anim_length: f32,
}
impl Corner {
fn new(rel_x: f32, rel_y: f32) -> Self {
Self {
spring_x: Spring::new(),
spring_y: Spring::new(),
x: 0.0,
y: 0.0,
rel_x,
rel_y,
prev_dest_x: -1e6,
prev_dest_y: -1e6,
anim_length: 0.0,
}
}
#[inline]
fn destination(
&self,
center_x: f32,
center_y: f32,
cell_w: f32,
cell_h: f32,
) -> (f32, f32) {
(
center_x + self.rel_x * cell_w,
center_y + self.rel_y * cell_h,
)
}
#[inline]
fn update(
&mut self,
center_x: f32,
center_y: f32,
cell_w: f32,
cell_h: f32,
dt: f32,
immediate_movement: bool,
) -> bool {
let (dest_x, dest_y) = self.destination(center_x, center_y, cell_w, cell_h);
if (dest_x - self.prev_dest_x).abs() > 0.01
|| (dest_y - self.prev_dest_y).abs() > 0.01
{
self.spring_x.position = dest_x - self.x;
self.spring_y.position = dest_y - self.y;
self.prev_dest_x = dest_x;
self.prev_dest_y = dest_y;
}
if immediate_movement {
self.x = dest_x;
self.y = dest_y;
self.spring_x.reset();
self.spring_y.reset();
return false;
}
let mut animating = self.spring_x.update(dt, self.anim_length);
animating |= self.spring_y.update(dt, self.anim_length);
self.x = dest_x - self.spring_x.position;
self.y = dest_y - self.spring_y.position;
animating
}
#[inline]
fn direction_alignment(
&self,
center_x: f32,
center_y: f32,
cell_w: f32,
cell_h: f32,
) -> f32 {
let (dest_x, dest_y) = self.destination(center_x, center_y, cell_w, cell_h);
let rel_len = (self.rel_x * self.rel_x + self.rel_y * self.rel_y)
.sqrt()
.max(1e-6);
let corner_dir_x = self.rel_x / rel_len;
let corner_dir_y = self.rel_y / rel_len;
let dx = dest_x - self.x;
let dy = dest_y - self.y;
let travel_len = (dx * dx + dy * dy).sqrt().max(1e-6);
(dx / travel_len) * corner_dir_x + (dy / travel_len) * corner_dir_y
}
}
pub struct TrailCursor {
corners: [Corner; 4],
last_frame: Instant,
dest_cx: f32,
dest_cy: f32,
prev_dest_cx: f32,
prev_dest_cy: f32,
jump_from_cx: f32,
jump_from_cy: f32,
jumped: bool,
first_frame: bool,
animating: bool,
}
impl TrailCursor {
pub fn new() -> Self {
Self {
corners: [
Corner::new(-0.5, -0.5), Corner::new(0.5, -0.5), Corner::new(0.5, 0.5), Corner::new(-0.5, 0.5), ],
last_frame: Instant::now(),
dest_cx: 0.0,
dest_cy: 0.0,
prev_dest_cx: -1e6,
prev_dest_cy: -1e6,
jump_from_cx: -1e6,
jump_from_cy: -1e6,
jumped: false,
first_frame: true,
animating: false,
}
}
pub fn set_destination(
&mut self,
cursor_x: f32,
cursor_y: f32,
cell_width: f32,
cell_height: f32,
) {
let cx = cursor_x + cell_width * 0.5;
let cy = cursor_y + cell_height * 0.5;
self.dest_cx = cx;
self.dest_cy = cy;
if (cx - self.prev_dest_cx).abs() > 0.01 || (cy - self.prev_dest_cy).abs() > 0.01
{
self.jump_from_cx = self.prev_dest_cx;
self.jump_from_cy = self.prev_dest_cy;
self.prev_dest_cx = cx;
self.prev_dest_cy = cy;
self.jumped = true;
}
}
pub fn animate(&mut self, cell_width: f32, cell_height: f32) {
let now = Instant::now();
let dt = now.duration_since(self.last_frame).as_secs_f32().min(0.1);
self.last_frame = now;
let cx = self.dest_cx;
let cy = self.dest_cy;
let immediate = self.first_frame;
if self.first_frame {
self.first_frame = false;
}
if self.jumped && !immediate {
self.compute_jump(cx, cy, cell_width, cell_height);
}
self.jumped = false;
let mut still_animating = false;
for corner in &mut self.corners {
if corner.update(cx, cy, cell_width, cell_height, dt, immediate) {
still_animating = true;
}
}
self.animating = still_animating;
}
fn compute_jump(&mut self, cx: f32, cy: f32, cell_width: f32, cell_height: f32) {
let jump_x = if cell_width > 0.0 {
((cx - self.jump_from_cx) / cell_width).abs()
} else {
0.0
};
let jump_y = if cell_height > 0.0 {
((cy - self.jump_from_cy) / cell_height).abs()
} else {
0.0
};
let is_short = jump_x <= 2.001 && jump_y < 0.001;
if is_short {
let t = ANIMATION_LENGTH.min(SHORT_ANIMATION_LENGTH);
for c in &mut self.corners {
c.anim_length = t;
}
return;
}
let mut alignments: [(usize, f32); 4] = [
(
0,
self.corners[0].direction_alignment(cx, cy, cell_width, cell_height),
),
(
1,
self.corners[1].direction_alignment(cx, cy, cell_width, cell_height),
),
(
2,
self.corners[2].direction_alignment(cx, cy, cell_width, cell_height),
),
(
3,
self.corners[3].direction_alignment(cx, cy, cell_width, cell_height),
),
];
alignments.sort_by(|a, b| {
a.1.partial_cmp(&b.1)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.0.cmp(&b.0))
});
let mut ranks = [0usize; 4];
for (rank, &(corner_idx, _)) in alignments.iter().enumerate() {
ranks[corner_idx] = rank;
}
let leading = ANIMATION_LENGTH * (1.0 - TRAIL_SIZE).clamp(0.0, 1.0);
let trailing = ANIMATION_LENGTH;
let mid = (leading + trailing) / 2.0;
for (i, corner) in self.corners.iter_mut().enumerate() {
corner.anim_length = match ranks[i] {
0 => trailing,
1 => mid,
_ => leading,
};
}
}
pub fn draw(
&self,
sugarloaf: &mut Sugarloaf,
scale_factor: f32,
cursor_color: [f32; 4],
) {
if !self.animating {
return;
}
let inv = 1.0 / scale_factor;
let pts: [(f32, f32); 4] = [
(self.corners[0].x, self.corners[0].y),
(self.corners[1].x, self.corners[1].y),
(self.corners[2].x, self.corners[2].y),
(self.corners[3].x, self.corners[3].y),
];
const EDGES: [(usize, usize); 4] = [(0, 1), (1, 2), (2, 3), (3, 0)];
let min_y = pts.iter().map(|p| p.1).fold(f32::INFINITY, f32::min);
let max_y = pts.iter().map(|p| p.1).fold(f32::NEG_INFINITY, f32::max);
let height = max_y - min_y;
if height < 0.5 {
return;
}
let steps = (height as usize).clamp(1, 640);
let step_h = height / steps as f32;
for s in 0..steps {
let y = min_y + (s as f32 + 0.5) * step_h;
let mut x_min = f32::INFINITY;
let mut x_max = f32::NEG_INFINITY;
for &(a, b) in &EDGES {
let (ax, ay) = pts[a];
let (bx, by) = pts[b];
let (lo_y, hi_y) = if ay < by { (ay, by) } else { (by, ay) };
if y < lo_y || y > hi_y || (hi_y - lo_y).abs() < 1e-6 {
continue;
}
let t = (y - ay) / (by - ay);
let x = ax + (bx - ax) * t;
x_min = x_min.min(x);
x_max = x_max.max(x);
}
if x_max - x_min < 0.1 {
continue;
}
sugarloaf.rect(
None,
x_min * inv,
(min_y + s as f32 * step_h) * inv,
(x_max - x_min) * inv,
step_h * inv,
cursor_color,
DEPTH,
ORDER,
);
}
}
#[inline]
pub fn is_animating(&self) -> bool {
self.animating
}
}