use agb::fixnum::{Num, Vector2D, num, vec2};
pub type HighlightSpeed = Num<i32, 8>;
const DEFAULT_SPEED: HighlightSpeed = num!(0.35);
const SNAP_DISTANCE: HighlightSpeed = num!(0.5);
#[derive(Debug, Clone)]
pub struct ButtonHighlight {
pos: Vector2D<HighlightSpeed>,
target: Vector2D<HighlightSpeed>,
speed: HighlightSpeed,
}
impl ButtonHighlight {
pub fn new(pos: impl Into<Vector2D<i32>>) -> Self {
let pos = to_fixed(pos.into());
Self {
pos,
target: pos,
speed: DEFAULT_SPEED,
}
}
pub fn with_speed(mut self, speed: HighlightSpeed) -> Self {
debug_assert!(
speed > num!(0) && speed <= num!(1),
"speed must be in (0, 1]"
);
self.speed = speed;
self
}
pub fn set_target(&mut self, target: impl Into<Vector2D<i32>>) {
self.target = to_fixed(target.into());
}
pub fn jump_to(&mut self, pos: impl Into<Vector2D<i32>>) {
self.pos = to_fixed(pos.into());
self.target = self.pos;
}
pub fn update(&mut self) {
self.pos += (self.target - self.pos) * self.speed;
if (self.target.x - self.pos.x).abs() < SNAP_DISTANCE {
self.pos.x = self.target.x;
}
if (self.target.y - self.pos.y).abs() < SNAP_DISTANCE {
self.pos.y = self.target.y;
}
}
#[inline]
pub fn pos(&self) -> Vector2D<i32> {
self.pos.round()
}
#[inline]
pub fn target(&self) -> Vector2D<i32> {
self.target.round()
}
#[inline]
pub fn is_at_target(&self) -> bool {
self.pos == self.target
}
}
#[inline]
fn to_fixed(v: Vector2D<i32>) -> Vector2D<HighlightSpeed> {
vec2(Num::from(v.x), Num::from(v.y))
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn converges_and_snaps(_gba: &mut agb::Gba) {
let mut highlight = ButtonHighlight::new((24, 24));
assert!(highlight.is_at_target());
highlight.set_target(vec2(40, 24));
assert!(!highlight.is_at_target());
assert_eq!(highlight.target(), vec2(40, 24));
let mut steps = 0;
while !highlight.is_at_target() {
highlight.update();
steps += 1;
assert!(steps < 60, "did not converge");
}
assert_eq!(highlight.pos(), vec2(40, 24));
assert!(steps > 1, "should animate rather than snap instantly");
}
#[test_case]
fn jump_to_is_instant(_gba: &mut agb::Gba) {
let mut highlight = ButtonHighlight::new((0, 0)).with_speed(num!(0.5));
highlight.jump_to((100, 50));
assert_eq!(highlight.pos(), vec2(100, 50));
assert!(highlight.is_at_target());
}
}