use std::cmp::Ordering;
use std::fmt::{Debug, Display};
use std::ops::{ControlFlow, Deref, Div, DivAssign, Mul, MulAssign, Sub};
use std::str::FromStr;
use std::sync::OnceLock;
use std::thread;
use std::time::{Duration, Instant};
use alot::{LotId, Lots};
pub use easing_function::*;
use figures::units::{Lp, Px, UPx};
use figures::{Angle, Fraction, Point, Ranged, Rect, Size, UnscaledUnit, Zero};
use intentional::Cast;
use kempt::Set;
use kludgine::Color;
use parking_lot::{Condvar, Mutex, MutexGuard};
use crate::animation::easings::Linear;
use crate::styles::{Component, RequireInvalidation};
use crate::utils::run_in_bg;
use crate::value::{Destination, Dynamic, Source};
use crate::widget::SharedCallback;
use crate::Cushy;
static ANIMATIONS: Mutex<Animating> = Mutex::new(Animating::new());
static NEW_ANIMATIONS: Condvar = Condvar::new();
pub(crate) fn spawn(app: Cushy) {
let _ignored = thread_state(Some(app));
}
fn thread_state(app: Option<Cushy>) -> MutexGuard<'static, Animating> {
static THREAD: OnceLock<()> = OnceLock::new();
THREAD.get_or_init(move || {
thread::spawn(move || animation_thread(app.as_ref()));
});
ANIMATIONS.lock()
}
fn animation_thread(app: Option<&Cushy>) {
let _guard = app.as_ref().map(|app| app.enter_runtime());
let mut state = thread_state(None);
loop {
if state.running.is_empty() {
state.last_updated = None;
NEW_ANIMATIONS.wait(&mut state);
} else {
let start = Instant::now();
let last_tick = state.last_updated.unwrap_or(start);
let elapsed = start - last_tick;
state.last_updated = Some(start);
let mut index = 0;
while index < state.running.len() {
let animation_id = *state.running.member(index).expect("index in bounds");
let animation_state = &mut state.animations[animation_id];
if animation_state.animation.animate(elapsed).is_break() {
if !animation_state.handle_attached {
state.animations.remove(animation_id);
}
state.running.remove_member(index);
} else {
index += 1;
}
}
drop(state);
let next_tick = last_tick + Duration::from_millis(16);
std::thread::sleep(
next_tick
.checked_duration_since(Instant::now())
.unwrap_or(Duration::from_millis(16)),
);
state = thread_state(None);
}
}
}
struct AnimationState {
animation: Box<dyn Animate>,
handle_attached: bool,
}
struct Animating {
animations: Lots<AnimationState>,
running: Set<LotId>,
last_updated: Option<Instant>,
}
impl Animating {
const fn new() -> Self {
Self {
animations: Lots::new(),
running: Set::new(),
last_updated: None,
}
}
fn spawn(&mut self, animation: Box<dyn Animate>) -> AnimationHandle {
let id = self.animations.push(AnimationState {
animation,
handle_attached: true,
});
if self.running.is_empty() {
NEW_ANIMATIONS.notify_one();
}
self.running.insert(id);
AnimationHandle(Some(id))
}
fn remove_animation(&mut self, id: LotId) {
self.animations.remove(id);
self.running.remove(&id);
}
fn run_unattached(&mut self, id: LotId) {
if self.running.contains(&id) {
self.animations[id].handle_attached = false;
} else {
self.animations.remove(id);
}
}
}
pub trait Animate: Send + Sync {
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration>;
}
#[derive(Clone)]
pub struct DynamicTransition<T> {
pub dynamic: Dynamic<T>,
pub new_value: T,
}
impl<T> AnimationTarget for DynamicTransition<T>
where
T: LinearInterpolate + Clone + Send + Sync,
{
type Running = TransitioningDynamic<T>;
fn begin(self) -> Self::Running {
self.into()
}
}
pub struct TransitioningDynamic<T> {
change: DynamicTransition<T>,
start: T,
}
impl<T> From<DynamicTransition<T>> for TransitioningDynamic<T>
where
T: Clone,
{
fn from(change: DynamicTransition<T>) -> Self {
Self {
start: change.dynamic.get(),
change,
}
}
}
impl<T> AnimateTarget for TransitioningDynamic<T>
where
T: LinearInterpolate + Clone + Send + Sync,
{
fn update(&self, percent: f32) {
self.change
.dynamic
.set(self.start.lerp(&self.change.new_value, percent));
}
fn finish(&self) {
self.change.dynamic.set(self.change.new_value.clone());
}
}
#[must_use = "animations are not performed until they are spawned"]
#[derive(Clone)]
pub struct Animation<Target, Easing = Linear>
where
Target: AnimationTarget,
{
value: Target,
duration: Duration,
easing: Easing,
}
impl<T> Animation<T, Linear>
where
T: AnimationTarget,
{
fn new(value: T, duration: Duration) -> Self {
Self {
value,
duration,
easing: Linear,
}
}
pub fn with_easing<Easing: self::Easing>(self, easing: Easing) -> Animation<T, Easing> {
Animation {
value: self.value,
duration: self.duration,
easing,
}
}
}
impl<T, Easing> IntoAnimate for Animation<T, Easing>
where
T: AnimationTarget,
Easing: self::Easing,
{
type Animate = RunningAnimation<T::Running, Easing>;
fn into_animate(self) -> Self::Animate {
RunningAnimation {
target: self.value.begin(),
duration: self.duration,
elapsed: Duration::ZERO,
easing: self.easing,
}
}
}
pub trait AnimationTarget: Sized + Send + Sync {
type Running: AnimateTarget;
fn begin(self) -> Self::Running;
fn over(self, duration: Duration) -> Animation<Self, Linear> {
Animation::new(self, duration)
}
fn immediately(self) -> Animation<Self, Linear> {
self.over(Duration::ZERO)
}
}
pub trait AnimateTarget: Send + Sync {
fn update(&self, percent: f32);
fn finish(&self);
}
macro_rules! impl_tuple_animate {
($($type:ident $field:tt $var:ident),+) => {
impl<$($type),+> AnimationTarget for ($($type,)+) where $($type: AnimationTarget),+ {
type Running = ($(<$type>::Running,)+);
fn begin(self) -> Self::Running {
($(self.$field.begin(),)+)
}
}
impl<$($type),+> AnimateTarget for ($($type,)+) where $($type: AnimateTarget),+ {
fn update(&self, percent: f32) {
$(self.$field.update(percent);)+
}
fn finish(&self) {
$(self.$field.finish();)+
}
}
}
}
impl_all_tuples!(impl_tuple_animate);
pub trait BoxAnimate {
fn boxed(self) -> Box<dyn Animate>;
}
pub trait IntoAnimate: Sized + Send + Sync {
type Animate: Animate;
fn into_animate(self) -> Self::Animate;
fn to_animate(&self) -> Self::Animate
where
Self: Clone,
{
self.clone().into_animate()
}
fn and_then<Other: IntoAnimate>(self, other: Other) -> Chain<Self, Other> {
Chain::new(self, other)
}
fn cycle(self) -> Cycle<Self>
where
Self: Clone,
{
Cycle::forever(self)
}
fn repeat(self, times: usize) -> Cycle<Self>
where
Self: Clone,
{
Cycle::n_times(times, self)
}
fn on_complete<F>(self, on_complete: F) -> OnCompleteAnimation<Self>
where
F: FnOnce() + Send + Sync + 'static,
{
OnCompleteAnimation::new(self, on_complete)
}
}
macro_rules! impl_tuple_into_animate {
($($type:ident $field:tt $var:ident),+) => {
impl<$($type),+> IntoAnimate for ($($type,)+) where $($type: IntoAnimate),+ {
type Animate = ($(<$type>::Animate,)+);
fn into_animate(self) -> Self::Animate {
($(self.$field.into_animate(),)+)
}
}
impl<$($type),+> Animate for ($($type,)+) where $($type: Animate),+ {
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
let mut min_remaining = Duration::MAX;
let mut completely_done = true;
$(
match self.$field.animate(elapsed) {
ControlFlow::Break(remaining) => {
min_remaining = min_remaining.min(remaining);
}
ControlFlow::Continue(()) => {
completely_done = false;
}
}
)+
if completely_done {
ControlFlow::Break(min_remaining)
} else {
ControlFlow::Continue(())
}
}
}
}
}
impl_all_tuples!(impl_tuple_into_animate);
impl<T> BoxAnimate for T
where
T: IntoAnimate + 'static,
{
fn boxed(self) -> Box<dyn Animate> {
Box::new(self.into_animate())
}
}
pub trait Spawn {
fn spawn(self) -> AnimationHandle;
fn launch(self)
where
Self: Sized,
{
self.spawn().detach();
}
}
impl<T> Spawn for T
where
T: BoxAnimate,
{
fn spawn(self) -> AnimationHandle {
self.boxed().spawn()
}
}
impl Spawn for Box<dyn Animate> {
fn spawn(self) -> AnimationHandle {
thread_state(None).spawn(self)
}
}
impl<T, Easing> Animate for RunningAnimation<T, Easing>
where
T: AnimateTarget,
Easing: self::Easing,
{
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
self.elapsed = self.elapsed.checked_add(elapsed).unwrap_or(Duration::MAX);
if let Some(remaining_elapsed) = self.elapsed.checked_sub(self.duration) {
self.target.finish();
ControlFlow::Break(remaining_elapsed)
} else {
let progress = self
.easing
.ease(self.elapsed.as_secs_f32() / self.duration.as_secs_f32());
self.target.update(progress);
ControlFlow::Continue(())
}
}
}
pub struct RunningAnimation<T, Easing> {
target: T,
duration: Duration,
elapsed: Duration,
easing: Easing,
}
#[derive(Default, Debug, PartialEq, Eq)]
#[must_use]
pub struct AnimationHandle(Option<LotId>);
impl AnimationHandle {
pub const fn new() -> Self {
Self(None)
}
pub fn clear(&mut self) {
if let Some(id) = self.0.take() {
thread_state(None).remove_animation(id);
}
}
pub fn detach(mut self) {
if let Some(id) = self.0.take() {
thread_state(None).run_unattached(id);
}
}
#[must_use]
pub fn is_running(&self) -> bool {
let Some(id) = self.0 else { return false };
thread_state(None).running.contains(&id)
}
#[must_use]
pub fn is_complete(&self) -> bool {
!self.is_running()
}
}
impl Drop for AnimationHandle {
fn drop(&mut self) {
self.clear();
}
}
#[derive(Clone)]
pub struct Chain<A: IntoAnimate, B: IntoAnimate>(A, B);
pub struct RunningChain<A: IntoAnimate, B: IntoAnimate>(Option<ChainState<A, B>>);
enum ChainState<A: IntoAnimate, B: IntoAnimate> {
AnimatingFirst(A::Animate, B),
AnimatingSecond(B::Animate),
}
impl<A, B> Chain<A, B>
where
A: IntoAnimate,
B: IntoAnimate,
{
pub const fn new(first: A, second: B) -> Self {
Self(first, second)
}
}
impl<A, B> IntoAnimate for Chain<A, B>
where
A: IntoAnimate,
B: IntoAnimate,
{
type Animate = RunningChain<A, B>;
fn into_animate(self) -> Self::Animate {
let a = self.0.into_animate();
RunningChain(Some(ChainState::AnimatingFirst(a, self.1)))
}
}
impl<A, B> Animate for RunningChain<A, B>
where
A: IntoAnimate,
B: IntoAnimate,
{
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
match self.0.as_mut().expect("invalid state") {
ChainState::AnimatingFirst(a, _) => match a.animate(elapsed) {
ControlFlow::Continue(()) => ControlFlow::Continue(()),
ControlFlow::Break(remaining) => {
let Some(ChainState::AnimatingFirst(_, b)) = self.0.take() else {
unreachable!("invalid state")
};
self.0 = Some(ChainState::AnimatingSecond(b.into_animate()));
self.animate(remaining)
}
},
ChainState::AnimatingSecond(b) => b.animate(elapsed),
}
}
}
pub struct Cycle<A>
where
A: IntoAnimate + Clone,
{
cycles: Option<usize>,
animation: A,
running: Option<A::Animate>,
}
impl<A> Cycle<A>
where
A: IntoAnimate + Clone,
{
pub fn forever(animation: A) -> Self {
Self {
animation,
cycles: None,
running: None,
}
}
pub fn n_times(cycles: usize, animation: A) -> Self {
Self {
animation,
cycles: Some(cycles),
running: None,
}
}
fn keep_cycling(&mut self) -> bool {
match &mut self.cycles {
Some(0) => false,
Some(cycles) => {
*cycles -= 1;
true
}
None => true,
}
}
}
impl<A> IntoAnimate for Cycle<A>
where
A: IntoAnimate + Clone,
{
type Animate = Self;
fn into_animate(self) -> Self::Animate {
self
}
}
impl<A> Animate for Cycle<A>
where
A: IntoAnimate + Clone,
{
fn animate(&mut self, mut elapsed: Duration) -> ControlFlow<Duration> {
while !elapsed.is_zero() {
if let Some(running) = &mut self.running {
match running.animate(elapsed) {
ControlFlow::Break(remaining) => elapsed = remaining,
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
}
}
if self.keep_cycling() {
self.running = Some(self.animation.to_animate());
} else {
self.running = None;
return ControlFlow::Break(elapsed);
}
}
ControlFlow::Continue(())
}
}
pub struct OnCompleteAnimation<A> {
animation: A,
callback: Option<Box<dyn FnOnce() + Send + Sync + 'static>>,
completed: bool,
}
impl<A> OnCompleteAnimation<A> {
pub fn new<F>(animation: A, on_complete: F) -> Self
where
F: FnOnce() + Send + Sync + 'static,
{
Self {
animation,
callback: Some(Box::new(on_complete)),
completed: false,
}
}
}
impl<A> IntoAnimate for OnCompleteAnimation<A>
where
A: IntoAnimate,
{
type Animate = OnCompleteAnimation<A::Animate>;
fn into_animate(self) -> Self::Animate {
OnCompleteAnimation {
animation: self.animation.into_animate(),
callback: self.callback,
completed: false,
}
}
}
impl<A> Animate for OnCompleteAnimation<A>
where
A: Animate,
{
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
if self.completed {
ControlFlow::Break(elapsed)
} else {
match self.animation.animate(elapsed) {
ControlFlow::Break(remaining) => {
self.completed = true;
if let Some(callback) = self.callback.take() {
run_in_bg(callback);
}
ControlFlow::Break(remaining)
}
ControlFlow::Continue(()) => ControlFlow::Continue(()),
}
}
}
}
impl IntoAnimate for Duration {
type Animate = Self;
fn into_animate(self) -> Self::Animate {
self
}
}
impl Animate for Duration {
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
if let Some(remaining) = self.checked_sub(elapsed) {
*self = remaining;
ControlFlow::Continue(())
} else {
ControlFlow::Break(elapsed - *self)
}
}
}
impl IntoAnimate for SharedCallback {
type Animate = Self;
fn into_animate(self) -> Self::Animate {
self
}
}
impl Animate for SharedCallback {
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
self.invoke(());
ControlFlow::Break(elapsed)
}
}
impl IntoAnimate for SharedCallback<Duration, ControlFlow<Duration>> {
type Animate = Self;
fn into_animate(self) -> Self::Animate {
self
}
}
impl Animate for SharedCallback<Duration, ControlFlow<Duration>> {
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
self.invoke(elapsed)
}
}
impl<F> IntoAnimate for F
where
F: FnMut(Duration) -> ControlFlow<Duration> + Send + Sync + 'static,
{
type Animate = Self;
fn into_animate(self) -> Self::Animate {
self
}
}
impl<F> Animate for F
where
F: FnMut(Duration) -> ControlFlow<Duration> + Send + Sync + 'static,
{
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
self(elapsed)
}
}
pub trait LinearInterpolate: PartialEq {
#[must_use]
fn lerp(&self, target: &Self, percent: f32) -> Self;
}
pub use cushy_macros::LinearInterpolate;
macro_rules! impl_lerp_for_int {
($type:ident, $unsigned:ident, $float:ident) => {
impl LinearInterpolate for $type {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_lossless
)]
fn lerp(&self, target: &Self, percent: f32) -> Self {
let percent = $float::from(percent);
let delta = target.abs_diff(*self);
let delta = (delta as $float * percent).round() as $unsigned;
if target > self {
self.checked_add_unsigned(delta).expect("direction checked")
} else {
self.checked_sub_unsigned(delta).expect("direction checked")
}
}
}
};
}
macro_rules! impl_lerp_for_uint {
($type:ident, $float:ident) => {
impl LinearInterpolate for $type {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_lossless
)]
fn lerp(&self, target: &Self, percent: f32) -> Self {
let percent = $float::from(percent);
if let Some(delta) = target.checked_sub(*self) {
self.saturating_add((delta as $float * percent).round() as $type)
} else {
self.saturating_sub(((*self - *target) as $float * percent).round() as $type)
}
}
}
};
}
impl_lerp_for_uint!(u8, f32);
impl_lerp_for_uint!(u16, f32);
impl_lerp_for_uint!(u32, f32);
impl_lerp_for_uint!(u64, f32);
impl_lerp_for_uint!(u128, f64);
impl_lerp_for_uint!(usize, f64);
impl_lerp_for_int!(i8, u8, f32);
impl_lerp_for_int!(i16, u16, f32);
impl_lerp_for_int!(i32, u32, f32);
impl_lerp_for_int!(i64, u64, f32);
impl_lerp_for_int!(i128, u128, f64);
impl_lerp_for_int!(isize, usize, f64);
impl LinearInterpolate for f32 {
fn lerp(&self, target: &Self, percent: f32) -> Self {
let delta = *target - *self;
*self + delta * percent
}
}
impl LinearInterpolate for f64 {
fn lerp(&self, target: &Self, percent: f32) -> Self {
let delta = *target - *self;
*self + delta * f64::from(percent)
}
}
impl LinearInterpolate for Fraction {
fn lerp(&self, target: &Self, percent: f32) -> Self {
Fraction::from(self.into_f32().lerp(&target.into_f32(), percent))
}
}
impl LinearInterpolate for Angle {
fn lerp(&self, target: &Self, percent: f32) -> Self {
let this = self.into_degrees::<f32>();
let delta = target.into_degrees::<f32>() - this;
Self::degrees_f(this + delta * percent)
}
}
impl LinearInterpolate for bool {
fn lerp(&self, target: &Self, percent: f32) -> Self {
if percent >= 0.5 {
*target
} else {
*self
}
}
}
impl PercentBetween for bool {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
if *min == *max || *self == *min {
ZeroToOne::ZERO
} else {
ZeroToOne::ONE
}
}
}
macro_rules! impl_unscaled_lerp {
($wrapper:ident) => {
impl LinearInterpolate for $wrapper {
fn lerp(&self, target: &Self, percent: f32) -> Self {
Self::from_unscaled(self.into_unscaled().lerp(&target.into_unscaled(), percent))
}
}
impl PercentBetween for $wrapper {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
self.into_unscaled()
.percent_between(&min.into_unscaled(), &max.into_unscaled())
}
}
};
}
impl_unscaled_lerp!(Px);
impl_unscaled_lerp!(Lp);
impl_unscaled_lerp!(UPx);
impl<Unit> LinearInterpolate for Point<Unit>
where
Unit: LinearInterpolate,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
Self::new(
self.x.lerp(&target.x, percent),
self.y.lerp(&target.y, percent),
)
}
}
impl<Unit> LinearInterpolate for Size<Unit>
where
Unit: LinearInterpolate,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
Self::new(
self.width.lerp(&target.width, percent),
self.height.lerp(&target.height, percent),
)
}
}
impl<Unit> LinearInterpolate for Rect<Unit>
where
Unit: LinearInterpolate,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
Self::new(
self.origin.lerp(&target.origin, percent),
self.size.lerp(&target.size, percent),
)
}
}
#[test]
fn integer_lerps() {
#[track_caller]
fn test_lerps<T: LinearInterpolate + Debug + Eq>(a: &T, b: &T, mid: &T) {
assert_eq!(&b.lerp(a, 1.), a);
assert_eq!(&a.lerp(b, 1.), b);
assert_eq!(&a.lerp(b, 0.), a);
assert_eq!(&b.lerp(a, 0.), b);
assert_eq!(&a.lerp(b, 0.5), mid);
}
test_lerps(&u8::MIN, &u8::MAX, &128);
test_lerps(&u16::MIN, &u16::MAX, &32_768);
test_lerps(&u32::MIN, &u32::MAX, &2_147_483_648);
test_lerps(&i8::MIN, &i8::MAX, &0);
test_lerps(&i16::MIN, &i16::MAX, &0);
test_lerps(&i32::MIN, &i32::MAX, &0);
test_lerps(&i64::MIN, &i64::MAX, &0);
test_lerps(&i128::MIN, &i128::MAX, &0);
test_lerps(&isize::MIN, &isize::MAX, &0);
}
impl LinearInterpolate for Color {
fn lerp(&self, target: &Self, percent: f32) -> Self {
Color::new(
self.red().lerp(&target.red(), percent),
self.green().lerp(&target.green(), percent),
self.blue().lerp(&target.blue(), percent),
self.alpha().lerp(&target.alpha(), percent),
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct BinaryLerp<T>(pub T);
impl<T> LinearInterpolate for BinaryLerp<T>
where
T: Clone + PartialEq,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
if false.lerp(&true, percent) {
target.clone()
} else {
self.clone()
}
}
}
impl<T> From<T> for BinaryLerp<T> {
fn from(value: T) -> Self {
Self(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ImmediateLerp<T>(T);
impl<T> LinearInterpolate for ImmediateLerp<T>
where
T: Clone + PartialEq,
{
fn lerp(&self, target: &Self, percent: f32) -> Self {
if percent > 0. {
target.clone()
} else {
self.clone()
}
}
}
impl<T> From<T> for ImmediateLerp<T> {
fn from(value: T) -> Self {
Self(value)
}
}
pub trait PercentBetween {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne;
}
macro_rules! impl_percent_between {
($type:ident, $float:ident, $sub:ident) => {
impl PercentBetween for $type {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_lossless
)]
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
assert!(min <= max, "percent_between requires min <= max");
assert!(
self >= min && self <= max,
"self must satisfy min <= self <= max"
);
let range = max.$sub(*min);
ZeroToOne::from(self.$sub(*min) as $float / range as $float)
}
}
};
}
impl_percent_between!(u8, f32, saturating_sub);
impl_percent_between!(u16, f32, saturating_sub);
impl_percent_between!(u32, f32, saturating_sub);
impl_percent_between!(u64, f32, saturating_sub);
impl_percent_between!(u128, f64, saturating_sub);
impl_percent_between!(usize, f64, saturating_sub);
impl_percent_between!(i8, f32, saturating_sub);
impl_percent_between!(i16, f32, saturating_sub);
impl_percent_between!(i32, f32, saturating_sub);
impl_percent_between!(i64, f32, saturating_sub);
impl_percent_between!(i128, f64, saturating_sub);
impl_percent_between!(isize, f64, saturating_sub);
impl_percent_between!(f32, f32, sub);
impl_percent_between!(f64, f64, sub);
impl PercentBetween for Color {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
fn channel_percent(
value: Color,
min: Color,
max: Color,
func: impl Fn(Color) -> u8,
) -> Option<ZeroToOne> {
let value = func(value);
let min = func(min);
let max = func(max);
match min.cmp(&max) {
Ordering::Less => Some(value.percent_between(&min, &max)),
Ordering::Equal => None,
Ordering::Greater => Some(value.percent_between(&max, &min).one_minus()),
}
}
let mut total_percent_change = 0.;
let mut different_channels = 0_u8;
for func in [Color::red, Color::green, Color::blue, Color::alpha] {
if let Some(red) = channel_percent(*self, *min, *max, func) {
total_percent_change += *red;
different_channels += 1;
}
}
if different_channels > 0 {
ZeroToOne::new(total_percent_change / f32::from(different_channels))
} else {
ZeroToOne::ZERO
}
}
}
impl PercentBetween for Fraction {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
self.into_f32()
.percent_between(&min.into_f32(), &max.into_f32())
}
}
#[test]
fn int_percent_between() {
assert_eq!(1_u8.percent_between(&1_u8, &2_u8), ZeroToOne::ZERO);
}
#[test]
fn color_lerp() {
let gray = Color::new(51, 51, 51, 51);
let percent_gray = gray.percent_between(&Color::CLEAR_BLACK, &Color::WHITE);
assert_eq!(gray, Color::CLEAR_BLACK.lerp(&Color::WHITE, *percent_gray));
let gray = Color::new(51, 51, 51, 255);
let percent_gray = gray.percent_between(&Color::BLACK, &Color::WHITE);
assert_eq!(gray, Color::BLACK.lerp(&Color::WHITE, *percent_gray));
let red_green = Color::RED.lerp(&Color::GREEN, 0.5);
let percent_between = red_green.percent_between(&Color::RED, &Color::GREEN);
assert!((*percent_between - 0.5).abs() < 1. / 255. / 4.);
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct ZeroToOne(f32);
impl ZeroToOne {
pub const ONE: Self = Self(1.);
#[must_use]
pub fn new(value: f32) -> Self {
assert!(!value.is_nan());
Self(value.clamp(0., 1.))
}
#[must_use]
pub fn difference_between(self, other: Self) -> Self {
Self((self.0 - other.0).abs())
}
#[must_use]
pub fn into_f32(self) -> f32 {
self.0
}
#[must_use]
pub fn one_minus(self) -> Self {
Self(1. - self.0)
}
fn checked_div<Float: PossiblyNaN>(&mut self, rhs: Float) {
let rhs = rhs.into();
if Float::CAN_BE_NAN {
assert!(!rhs.is_nan());
}
if rhs > 0. {
self.0 = (self.0 / rhs).clamp(0., 1.);
} else if *self > 0. {
*self = Self::ONE;
}
}
}
impl Zero for ZeroToOne {
const ZERO: Self = Self(0.);
fn is_zero(&self) -> bool {
*self == 0.
}
}
impl Display for ZeroToOne {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl FromStr for ZeroToOne {
type Err = std::num::ParseFloatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(Self)
}
}
impl From<f32> for ZeroToOne {
fn from(value: f32) -> Self {
Self::new(value)
}
}
impl From<ZeroToOne> for f32 {
fn from(value: ZeroToOne) -> Self {
value.0
}
}
impl From<f64> for ZeroToOne {
fn from(value: f64) -> Self {
Self::new(value.cast())
}
}
impl Default for ZeroToOne {
fn default() -> Self {
Self::ZERO
}
}
impl Deref for ZeroToOne {
type Target = f32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Eq for ZeroToOne {}
impl PartialEq for ZeroToOne {
fn eq(&self, other: &Self) -> bool {
*self == other.0
}
}
impl PartialEq<f32> for ZeroToOne {
fn eq(&self, other: &f32) -> bool {
(self.0 - *other).abs() < f32::EPSILON
}
}
impl Ord for ZeroToOne {
fn cmp(&self, other: &Self) -> Ordering {
self.0.total_cmp(&other.0)
}
}
impl PartialOrd for ZeroToOne {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<f32> for ZeroToOne {
fn partial_cmp(&self, other: &f32) -> Option<Ordering> {
Some(self.0.total_cmp(other))
}
}
impl LinearInterpolate for ZeroToOne {
fn lerp(&self, target: &Self, percent: f32) -> Self {
let delta = **target - **self;
ZeroToOne::new(**self + delta * percent)
}
}
impl PercentBetween for ZeroToOne {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
self.0.percent_between(&min.0, &max.0)
}
}
impl Mul for ZeroToOne {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.0 * rhs.0)
}
}
impl MulAssign for ZeroToOne {
fn mul_assign(&mut self, rhs: Self) {
self.0 *= rhs.0;
}
}
impl Div for ZeroToOne {
type Output = Self;
fn div(mut self, rhs: Self) -> Self::Output {
self /= rhs;
self
}
}
impl DivAssign for ZeroToOne {
fn div_assign(&mut self, rhs: Self) {
self.checked_div(rhs);
}
}
impl Div<f32> for ZeroToOne {
type Output = Self;
fn div(mut self, rhs: f32) -> Self::Output {
self /= rhs;
self
}
}
impl DivAssign<f32> for ZeroToOne {
fn div_assign(&mut self, rhs: f32) {
self.checked_div(rhs);
}
}
trait PossiblyNaN: Into<f32> {
const CAN_BE_NAN: bool;
}
impl PossiblyNaN for ZeroToOne {
const CAN_BE_NAN: bool = false;
}
impl PossiblyNaN for f32 {
const CAN_BE_NAN: bool = true;
}
impl Ranged for ZeroToOne {
const MAX: Self = Self::ONE;
const MIN: Self = Self::ZERO;
}
impl RequireInvalidation for ZeroToOne {
fn requires_invalidation(&self) -> bool {
false
}
}
impl TryFrom<Component> for ZeroToOne {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Percent(value) => Ok(value),
other => Err(other),
}
}
}
impl From<ZeroToOne> for Component {
fn from(value: ZeroToOne) -> Self {
Component::Percent(value)
}
}
#[test]
fn zero_to_one_div() {
std::panic::catch_unwind(|| ZeroToOne::ONE / f32::NAN).expect_err("div by nan");
let mut value = ZeroToOne::ONE;
std::panic::catch_unwind(move || value /= f32::NAN).expect_err("div by nan");
assert_eq!(ZeroToOne::ZERO / ZeroToOne::ZERO, ZeroToOne::ZERO);
assert_eq!(ZeroToOne::new(0.5) / ZeroToOne::ZERO, ZeroToOne::ONE);
assert_eq!(ZeroToOne::ZERO / 0., ZeroToOne::ZERO);
assert_eq!(ZeroToOne::new(0.5) / 0., ZeroToOne::ONE);
assert_eq!(ZeroToOne::ONE / 0.5, ZeroToOne::ONE);
assert_eq!(ZeroToOne::ONE / -0.5, ZeroToOne::ONE);
}
impl From<EasingFunction> for Component {
fn from(value: EasingFunction) -> Self {
Component::Easing(value)
}
}
impl TryFrom<Component> for EasingFunction {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
match value {
Component::Easing(easing) => Ok(easing),
other => Err(other),
}
}
}
impl RequireInvalidation for EasingFunction {
fn requires_invalidation(&self) -> bool {
false
}
}