use std::any::Any;
use skia_safe::{Color, Paint};
use crate::control::{Control, PaintCx};
use crate::tree::{ControlId, Cx, Mut, Tree, wrong_state};
use crate::types::{Dirty, IntoProp};
pub type Easing = fn(f32) -> f32;
pub mod easing {
use std::f32::consts::FRAC_PI_2;
pub fn linear(x: f32) -> f32 {
x
}
pub fn cubic_in(x: f32) -> f32 {
x * x * x
}
pub fn cubic_out(x: f32) -> f32 {
(x - 1.0).powi(3) + 1.0
}
pub fn cubic_in_out(x: f32) -> f32 {
if x < 0.5 { 4.0 * x * x * x } else { (x - 1.0) * (2.0 * x - 2.0).powi(2) + 1.0 }
}
pub fn sin_out(x: f32) -> f32 {
(x * FRAC_PI_2).sin()
}
pub fn sin_in(x: f32) -> f32 {
1.0 - (x * FRAC_PI_2).cos()
}
pub fn sin_in_out(x: f32) -> f32 {
-(std::f32::consts::PI * x).cos() / 2.0 + 0.5
}
pub fn bounce_out(mut p: f32) -> f32 {
if p < 1.0 / 2.75 {
return 7.5625 * p * p;
}
if p < 2.0 / 2.75 {
p -= 1.5 / 2.75;
return 7.5625 * p * p + 0.75;
}
if p < 2.5 / 2.75 {
p -= 2.25 / 2.75;
return 7.5625 * p * p + 0.9375;
}
p -= 2.625 / 2.75;
7.5625 * p * p + 0.984375
}
pub fn bounce_in(p: f32) -> f32 {
1.0 - bounce_out(1.0 - p)
}
pub fn spring_in(x: f32) -> f32 {
x * x * ((1.70158 + 1.0) * x - 1.70158)
}
pub fn spring_out(x: f32) -> f32 {
(x - 1.0) * (x - 1.0) * ((1.70158 + 1.0) * (x - 1.0) + 1.70158) + 1.0
}
pub fn default(x: f32) -> f32 {
cubic_in_out(x)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ValueAnimator {
pub from: f32,
pub to: f32,
pub duration_ms: f32,
pub easing: Easing,
pub repeat: i32,
pub delay_ms: f32,
pub ping_pong: bool,
}
impl ValueAnimator {
pub fn new(from: f32, to: f32, duration_ms: f32, easing: Easing) -> Self {
Self { from, to, duration_ms, easing, repeat: 0, delay_ms: 0.0, ping_pong: false }
}
pub fn repeat(mut self, repeat: i32) -> Self {
self.repeat = repeat;
self
}
pub fn delay(mut self, ms: f32) -> Self {
self.delay_ms = ms;
self
}
pub fn ping_pong(mut self) -> Self {
self.ping_pong = true;
self
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct AnimationId(u32);
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Fade,
Scale,
Translate,
Rotate,
}
type Update = Box<dyn FnMut(f32, &mut Cx<'_>)>;
type Overlay = Box<dyn Fn(&mut PaintCx<'_>, f32)>;
type Finished = Box<dyn FnOnce(&mut dyn Any, &mut Cx<'_>)>;
pub(crate) struct FrameTick {
pub keep: bool,
pub state_touched: bool,
}
pub(crate) type Frame = fn(ControlId, f64, &mut dyn Any, &mut Cx<'_>) -> FrameTick;
pub(crate) struct Animator {
id: u32,
pub(crate) control: ControlId,
kind: Option<Kind>,
run: ValueAnimator,
start_ms: Option<f64>,
pub(crate) value: f32,
update: Option<Update>,
pub(crate) overlay: Option<Overlay>,
finished: Option<Finished>,
frame: Option<Frame>,
wake_ms: f64,
paused: Option<(f64, f64)>,
}
fn start(
tree: &mut Tree,
control: ControlId,
kind: Option<Kind>,
run: ValueAnimator,
update: Option<Update>,
overlay: Option<Overlay>,
) -> AnimationId {
tree.last_animation += 1;
let id = tree.last_animation;
let (start_ms, value, finished, frame, paused) = (None, run.from, None, None, None);
let wake_ms = if run.delay_ms > 0.0 { tree.time_ms + run.delay_ms as f64 } else { 0.0 };
tree.animators.push(Animator { id, control, kind, run, start_ms, value, update, overlay, finished, frame, wake_ms, paused });
AnimationId(id)
}
pub(crate) fn start_frame(tree: &mut Tree, control: ControlId, frame: Frame) {
start(tree, control, None, ValueAnimator::new(0.0, 0.0, 0.0, easing::linear), None, None);
tree.animators.last_mut().expect("just pushed").frame = Some(frame);
}
pub(crate) fn sleep(tree: &mut Tree, control: ControlId, until_ms: f64) {
for a in tree.animators.iter_mut().filter(|a| a.control == control && a.frame.is_some()) {
a.wake_ms = until_ms;
}
}
pub(crate) fn stop_frames(tree: &mut Tree, control: ControlId) {
tree.animators.retain(|a| a.control != control || a.frame.is_none());
}
pub(crate) fn running(tree: &Tree) -> bool {
tree.animators.iter().any(|a| a.wake_ms <= tree.time_ms)
}
pub(crate) fn next_wake(tree: &Tree) -> Option<f64> {
tree.animators.iter().map(|a| a.wake_ms).filter(|t| *t > tree.time_ms && t.is_finite()).min_by(f64::total_cmp)
}
pub(crate) fn tick(tree: &mut Tree, state: &mut dyn Any, time_ms: f64) -> bool {
let mut state_touched = false;
let newest = tree.last_animation;
let mut last = 0;
loop {
let i = tree.animators.partition_point(|a| a.id <= last);
let Some(&Animator { id, control, wake_ms, .. }) = tree.animators.get(i).filter(|a| a.id <= newest) else { break };
last = id;
if time_ms < wake_ms {
continue;
}
if tree.node(control).is_none() {
tree.animators.remove(i);
continue;
}
if let Some(frame) = tree.animators[i].frame {
let tick = frame(control, time_ms, &mut *state, &mut Cx { tree });
state_touched |= tick.state_touched;
if !tick.keep
&& let Ok(i) = tree.animators.binary_search_by_key(&id, |a| a.id)
{
tree.animators.remove(i);
}
continue;
}
let a = &mut tree.animators[i];
let start = *a.start_ms.get_or_insert(time_ms);
let progress = if a.run.duration_ms > 0.0 { (time_ms - start) / a.run.duration_ms as f64 } else { 1.0 };
let done = progress >= 1.0;
let run = a.run;
let value = if done { run.to } else { run.from + (run.to - run.from) * (run.easing)(progress as f32) };
a.value = value;
let mut update = a.update.take();
if a.overlay.is_some() {
tree.invalidate(control, Dirty::REPAINT);
}
if let Some(update) = update.as_mut() {
update(value, &mut Cx { tree });
}
let Ok(i) = tree.animators.binary_search_by_key(&id, |a| a.id) else { continue };
let a = &mut tree.animators[i];
a.update = update;
if !done {
continue;
}
if a.run.repeat != 0 {
a.run.repeat -= (a.run.repeat > 0) as i32;
a.start_ms = None;
if a.run.ping_pong {
std::mem::swap(&mut a.run.from, &mut a.run.to);
}
continue;
}
if let Some(finished) = tree.animators.remove(i).finished {
finished(state, &mut Cx { tree });
state_touched = true;
}
}
state_touched
}
fn lerp(from: f32, to: f32, v: f32) -> f32 {
if v == 1.0 { to } else { from + (to - from) * v }
}
const RIPPLE_MS: f32 = 500.0;
const RIPPLE_DIAMETER: f32 = 300.0;
const RIPPLE_OPACITY: f32 = 0.2;
impl Cx<'_> {
pub fn start_animator(
&mut self,
id: impl Into<ControlId>,
animator: ValueAnimator,
update: impl FnMut(f32, &mut Cx<'_>) + 'static,
) -> AnimationId {
start(self.tree, id.into(), None, animator, Some(Box::new(update)), None)
}
pub fn animate(
&mut self,
id: impl Into<ControlId>,
ms: impl IntoProp<f32>,
easing: Easing,
update: impl FnMut(f32, &mut Cx<'_>) + 'static,
) -> AnimationId {
self.start_animator(id, ValueAnimator::new(0.0, 1.0, ms.into_prop(), easing), update)
}
fn animate_own(
&mut self,
id: ControlId,
kind: Kind,
ms: f32,
easing: Easing,
mut apply: impl FnMut(&mut Mut<'_, dyn Control>, f32) + 'static,
) -> AnimationId {
self.tree.animators.retain(|a| a.control != id || a.kind != Some(kind));
let update = move |v: f32, cx: &mut Cx<'_>| {
if let Some(mut control) = cx.any_mut(id) {
apply(&mut control, v)
}
};
start(self.tree, id, Some(kind), ValueAnimator::new(0.0, 1.0, ms, easing), Some(Box::new(update)), None)
}
pub fn fade_to(
&mut self,
id: impl Into<ControlId>,
opacity: impl IntoProp<f32>,
ms: impl IntoProp<f32>,
easing: Easing,
) -> AnimationId {
let (to, mut from) = (opacity.into_prop(), None);
self.animate_own(id.into(), Kind::Fade, ms.into_prop(), easing, move |c, v| {
let from = *from.get_or_insert(c.base().p.opacity);
c.set_opacity(lerp(from, to, v));
})
}
pub fn scale_to(
&mut self,
id: impl Into<ControlId>,
x: impl IntoProp<f32>,
y: impl IntoProp<f32>,
ms: impl IntoProp<f32>,
easing: Easing,
) -> AnimationId {
let (x, y, mut from) = (x.into_prop(), y.into_prop(), None);
self.animate_own(id.into(), Kind::Scale, ms.into_prop(), easing, move |c, v| {
let (fx, fy) = *from.get_or_insert((c.base().p.scale_x, c.base().p.scale_y));
c.set_scale_x(lerp(fx, x, v));
c.set_scale_y(lerp(fy, y, v));
})
}
pub fn translate_to(
&mut self,
id: impl Into<ControlId>,
x: impl IntoProp<f32>,
y: impl IntoProp<f32>,
ms: impl IntoProp<f32>,
easing: Easing,
) -> AnimationId {
let (x, y, mut from) = (x.into_prop(), y.into_prop(), None);
self.animate_own(id.into(), Kind::Translate, ms.into_prop(), easing, move |c, v| {
let (fx, fy) = *from.get_or_insert((c.base().p.translation_x, c.base().p.translation_y));
c.set_translation_x(lerp(fx, x, v));
c.set_translation_y(lerp(fy, y, v));
})
}
pub fn rotate_to(
&mut self,
id: impl Into<ControlId>,
degrees: impl IntoProp<f32>,
ms: impl IntoProp<f32>,
easing: Easing,
) -> AnimationId {
let (to, mut from) = (degrees.into_prop(), None);
self.animate_own(id.into(), Kind::Rotate, ms.into_prop(), easing, move |c, v| {
let from = *from.get_or_insert(c.base().p.rotation);
c.set_rotation(lerp(from, to, v));
})
}
pub fn time_ms(&self) -> f64 {
self.tree.time_ms
}
pub fn action_on_tick(
&mut self,
id: impl Into<ControlId>,
mut action: impl FnMut(f64, &mut Cx<'_>) + 'static,
) -> AnimationId {
let every_frame = ValueAnimator::new(0.0, 0.0, 0.0, easing::linear).repeat(-1);
self.start_animator(id, every_frame, move |_, cx| action(cx.time_ms(), cx))
}
pub fn pause_animation(&mut self, animation: AnimationId) {
let Ok(i) = self.tree.animators.binary_search_by_key(&animation.0, |a| a.id) else { return };
let a = &mut self.tree.animators[i];
if a.paused.is_none() {
a.paused = Some((self.tree.time_ms, a.wake_ms));
a.wake_ms = f64::INFINITY;
}
}
pub fn resume_animation(&mut self, animation: AnimationId) {
let Ok(i) = self.tree.animators.binary_search_by_key(&animation.0, |a| a.id) else { return };
let a = &mut self.tree.animators[i];
let Some((paused, wake)) = a.paused.take() else { return };
let now = self.tree.time_ms;
if let Some(start) = a.start_ms.as_mut() {
*start += now - paused;
}
a.wake_ms = if wake > paused { now + (wake - paused) } else { 0.0 };
}
pub fn stop_animation(&mut self, animation: AnimationId) {
let Ok(i) = self.tree.animators.binary_search_by_key(&animation.0, |a| a.id) else { return };
let stopped = self.tree.animators.remove(i);
if stopped.overlay.is_some() {
self.tree.invalidate(stopped.control, Dirty::REPAINT);
}
}
pub fn after<S: Any>(
&mut self,
id: impl Into<ControlId>,
ms: impl IntoProp<f32>,
run: impl FnOnce(&mut S, &mut Cx<'_>) + 'static,
) -> AnimationId {
let animation = start(self.tree, id.into(), None, ValueAnimator::new(0.0, 0.0, 0.0, easing::linear), None, None);
self.tree.animators.last_mut().expect("just pushed").wake_ms = self.tree.time_ms + ms.into_prop() as f64;
self.on_finished(animation, run);
animation
}
pub fn on_finished<S: Any>(
&mut self,
animation: AnimationId,
finished: impl FnOnce(&mut S, &mut Cx<'_>) + 'static,
) {
let Ok(i) = self.tree.animators.binary_search_by_key(&animation.0, |a| a.id) else { return };
self.tree.animators[i].finished = Some(Box::new(move |state, cx| {
let state = state.downcast_mut::<S>().unwrap_or_else(|| wrong_state::<S>());
finished(state, cx)
}));
}
pub fn play_overlay(
&mut self,
id: impl Into<ControlId>,
animator: ValueAnimator,
draw: impl Fn(&mut PaintCx<'_>, f32) + 'static,
) -> AnimationId {
start(self.tree, id.into(), None, animator, None, Some(Box::new(draw)))
}
pub fn play_ripple(
&mut self,
id: impl Into<ControlId>,
color: Color,
x: f32,
y: f32,
speed_ms: f32,
) -> AnimationId {
let ms = if speed_ms > 0.0 { speed_ms } else { RIPPLE_MS };
self.play_overlay(id, ValueAnimator::new(0.0, 1.0, ms, easing::cubic_in), move |cx, progress| {
let opacity = (RIPPLE_OPACITY * (1.0 - progress * 1.15)).max(0.0);
let mut paint = Paint::default();
paint.set_color(color.with_a((opacity * 255.0) as u8));
let center = (cx.rect.left + x * cx.scale, cx.rect.top + y * cx.scale);
cx.canvas.draw_circle(center, RIPPLE_DIAMETER * progress * cx.scale, &paint);
})
}
}