use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use repose_core::animation::{AnimatedValue, AnimationSpec, Easing};
use repose_core::animation_driver;
use repose_core::{
Color, Indication, IndicationDrawNode, IndicationNodeFactory, InteractionSource, PressId, Rect,
Scene, SceneNode, Vec2, remember_state_with_key, request_frame,
};
const FADE_IN_MS: u64 = 75;
const RADIUS_MS: u64 = 225;
const FADE_OUT_MS: u64 = 150;
const PRESS_ALPHA: f32 = 0.10;
#[derive(Clone, Debug)]
pub struct RippleConfig {
pub bounded: bool,
pub radius: Option<f32>,
pub color: Option<Color>,
pub enable_press: bool,
}
impl Default for RippleConfig {
fn default() -> Self {
Self {
bounded: true,
radius: None,
color: None,
enable_press: true,
}
}
}
pub fn ripple(config: RippleConfig) -> Rc<dyn IndicationNodeFactory> {
Rc::new(RippleNodeFactory { config })
}
#[derive(Clone, Debug)]
pub struct RippleNodeFactory {
pub config: RippleConfig,
}
impl Indication for RippleNodeFactory {}
impl IndicationNodeFactory for RippleNodeFactory {
fn create(&self, interaction_source: &InteractionSource) -> Box<dyn IndicationDrawNode> {
Box::new(RippleDrawNode::new(
interaction_source.clone(),
self.config.clone(),
))
}
}
struct RippleDrawNode {
interaction_source: InteractionSource,
config: RippleConfig,
}
impl RippleDrawNode {
fn new(interaction_source: InteractionSource, config: RippleConfig) -> Self {
Self {
interaction_source,
config,
}
}
fn anim_base(&self) -> String {
format!("rp:{:p}", self.interaction_source.stable_id())
}
fn register_driver(key: &str, anim: Rc<RefCell<AnimatedValue<f32>>>) {
animation_driver::register(
key.to_string(),
Rc::new(RefCell::new(move || anim.borrow_mut().update())),
);
request_frame();
}
}
impl IndicationDrawNode for RippleDrawNode {
fn draw(&self, scene: &mut Scene, rect: Rect, alpha: f32) {
if !self.config.enable_press {
return;
}
let base = self.anim_base();
let bounded = self.config.bounded;
let center_scene = Vec2 {
x: rect.x + rect.w * 0.5,
y: rect.y + rect.h * 0.5,
};
let target_radius = self.config.radius.unwrap_or_else(|| {
let diag = (rect.w * rect.w + rect.h * rect.h).sqrt();
if bounded {
diag * 0.5 + 10.0
} else {
diag * 0.5
}
});
let start_radius = rect.w.max(rect.h) * 0.3;
let base_color = self.config.color.unwrap_or(Color(0, 0, 0, 255));
let is_pressed = self.interaction_source.collect_is_pressed();
let press_pos = self.interaction_source.collect_last_press_position();
let current_pid = self.interaction_source.collect_last_press_id();
let k_alpha = format!("{}:a", base);
let k_rad = format!("{}:r", base);
let k_ctr = format!("{}:c", base);
let alpha_anim = remember_state_with_key(&k_alpha, || {
AnimatedValue::new(
0.0f32,
AnimationSpec::tween(Duration::from_millis(FADE_IN_MS), Easing::Linear),
)
});
let rad_anim = remember_state_with_key(&k_rad, || {
AnimatedValue::new(
0.0f32,
AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::FastOutSlowIn),
)
});
let ctr_anim = remember_state_with_key(&k_ctr, || {
AnimatedValue::new(
0.0f32,
AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::Linear),
)
});
let k_phase = format!("{}:ph", base);
let phase = remember_state_with_key(&k_phase, || 0u8);
let k_last_pid = format!("{}:lpid", base);
let last_pid = remember_state_with_key(&k_last_pid, || None::<PressId>);
let k_release_pending = format!("{}:rpend", base);
let release_pending = remember_state_with_key(&k_release_pending, || false);
let prev_pid = *last_pid.borrow();
let new_press = current_pid.is_some() && current_pid != prev_pid;
if new_press {
*last_pid.borrow_mut() = current_pid;
*phase.borrow_mut() = 1;
*release_pending.borrow_mut() = false;
let spec_in = AnimationSpec::tween(Duration::from_millis(FADE_IN_MS), Easing::Linear);
let spec_rad =
AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::FastOutSlowIn);
let spec_ctr = AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::Linear);
alpha_anim.borrow_mut().set_target(1.0);
alpha_anim.borrow_mut().set_spec(spec_in);
Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
rad_anim.borrow_mut().set_target(1.0);
rad_anim.borrow_mut().set_spec(spec_rad);
Self::register_driver(&format!("{}:drv:r", base), rad_anim.clone());
ctr_anim.borrow_mut().set_target(1.0);
ctr_anim.borrow_mut().set_spec(spec_ctr);
Self::register_driver(&format!("{}:drv:c", base), ctr_anim.clone());
}
if *phase.borrow() != 0
&& !is_pressed
&& current_pid.is_some()
&& current_pid == *last_pid.borrow()
{
*release_pending.borrow_mut() = true;
}
let fade_pct = *alpha_anim.borrow().get();
if *phase.borrow() == 1 && fade_pct >= 1.0 {
if *release_pending.borrow() {
*phase.borrow_mut() = 3;
let spec_out =
AnimationSpec::tween(Duration::from_millis(FADE_OUT_MS), Easing::Linear);
alpha_anim.borrow_mut().set_target(0.0);
alpha_anim.borrow_mut().set_spec(spec_out);
Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
} else {
*phase.borrow_mut() = 2;
}
}
if *phase.borrow() == 2 && *release_pending.borrow() {
*phase.borrow_mut() = 3;
let spec_out = AnimationSpec::tween(Duration::from_millis(FADE_OUT_MS), Easing::Linear);
alpha_anim.borrow_mut().set_target(0.0);
alpha_anim.borrow_mut().set_spec(spec_out);
Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
}
if *phase.borrow() == 3 && fade_pct <= 0.01 {
*phase.borrow_mut() = 0;
*last_pid.borrow_mut() = current_pid;
return;
}
if fade_pct <= 0.01 || *phase.borrow() == 0 {
return;
}
let draw_alpha = if *release_pending.borrow() && *phase.borrow() == 1 {
1.0f32
} else {
fade_pct
};
let rad_pct = *rad_anim.borrow().get();
let ctr_pct = *ctr_anim.borrow().get();
let current_radius = start_radius + (target_radius - start_radius) * rad_pct;
let origin_scene = match press_pos {
Some(pos) => {
let ox = rect.x + pos.x;
let oy = rect.y + pos.y;
if bounded {
Vec2 {
x: ox + (center_scene.x - ox) * ctr_pct,
y: oy + (center_scene.y - oy) * ctr_pct,
}
} else {
center_scene
}
}
None => center_scene,
};
let ripple_alpha = PRESS_ALPHA * draw_alpha * alpha;
if ripple_alpha <= 0.001 {
return;
}
let draw_color = base_color.with_alpha_f32(ripple_alpha);
if bounded {
scene.nodes.push(SceneNode::PushClip {
rect,
radius: [0.0; 4],
});
scene.nodes.push(SceneNode::Ellipse {
rect: Rect {
x: origin_scene.x - current_radius,
y: origin_scene.y - current_radius,
w: current_radius * 2.0,
h: current_radius * 2.0,
},
brush: draw_color.into(),
});
scene.nodes.push(SceneNode::PopClip);
} else {
scene.nodes.push(SceneNode::Ellipse {
rect: Rect {
x: origin_scene.x - current_radius,
y: origin_scene.y - current_radius,
w: current_radius * 2.0,
h: current_radius * 2.0,
},
brush: draw_color.into(),
});
}
}
}