1use std::cell::RefCell;
14use std::rc::Rc;
15use std::time::Duration;
16
17use repose_core::animation::{AnimatedValue, AnimationSpec, Easing};
18use repose_core::animation_driver;
19use repose_core::{
20 Color, Indication, IndicationDrawNode, IndicationNodeFactory, InteractionSource, PressId, Rect,
21 Scene, SceneNode, Vec2, remember_state_with_key, request_frame,
22};
23
24const FADE_IN_MS: u64 = 75;
25const RADIUS_MS: u64 = 225;
26const FADE_OUT_MS: u64 = 150;
27
28const PRESS_ALPHA: f32 = 0.10;
30
31#[derive(Clone, Debug)]
32pub struct RippleConfig {
33 pub bounded: bool,
34 pub radius: Option<f32>,
35 pub color: Option<Color>,
36 pub enable_press: bool,
37}
38
39impl Default for RippleConfig {
40 fn default() -> Self {
41 Self {
42 bounded: true,
43 radius: None,
44 color: None,
45 enable_press: true,
46 }
47 }
48}
49
50pub fn ripple(config: RippleConfig) -> Rc<dyn IndicationNodeFactory> {
51 Rc::new(RippleNodeFactory { config })
52}
53
54#[derive(Clone, Debug)]
55pub struct RippleNodeFactory {
56 pub config: RippleConfig,
57}
58
59impl Indication for RippleNodeFactory {}
60
61impl IndicationNodeFactory for RippleNodeFactory {
62 fn create(&self, interaction_source: &InteractionSource) -> Box<dyn IndicationDrawNode> {
63 Box::new(RippleDrawNode::new(
64 interaction_source.clone(),
65 self.config.clone(),
66 ))
67 }
68}
69
70struct RippleDrawNode {
71 interaction_source: InteractionSource,
72 config: RippleConfig,
73}
74
75impl RippleDrawNode {
76 fn new(interaction_source: InteractionSource, config: RippleConfig) -> Self {
77 Self {
78 interaction_source,
79 config,
80 }
81 }
82
83 fn anim_base(&self) -> String {
84 format!("rp:{:p}", self.interaction_source.stable_id())
85 }
86
87 fn register_driver(key: &str, anim: Rc<RefCell<AnimatedValue<f32>>>) {
88 animation_driver::register(
89 key.to_string(),
90 Rc::new(RefCell::new(move || anim.borrow_mut().update())),
91 );
92 request_frame();
93 }
94}
95
96impl IndicationDrawNode for RippleDrawNode {
97 fn draw(&self, scene: &mut Scene, rect: Rect, alpha: f32) {
98 if !self.config.enable_press {
99 return;
100 }
101
102 let base = self.anim_base();
103 let bounded = self.config.bounded;
104 let center_scene = Vec2 {
105 x: rect.x + rect.w * 0.5,
106 y: rect.y + rect.h * 0.5,
107 };
108
109 let target_radius = self.config.radius.unwrap_or_else(|| {
110 let diag = (rect.w * rect.w + rect.h * rect.h).sqrt();
111 if bounded {
112 diag * 0.5 + 10.0
113 } else {
114 diag * 0.5
115 }
116 });
117 let start_radius = rect.w.max(rect.h) * 0.3;
118
119 let base_color = self.config.color.unwrap_or(Color(0, 0, 0, 255));
120
121 let is_pressed = self.interaction_source.collect_is_pressed();
122 let press_pos = self.interaction_source.collect_last_press_position();
123 let current_pid = self.interaction_source.collect_last_press_id();
124
125 let k_alpha = format!("{}:a", base);
126 let k_rad = format!("{}:r", base);
127 let k_ctr = format!("{}:c", base);
128
129 let alpha_anim = remember_state_with_key(&k_alpha, || {
130 AnimatedValue::new(
131 0.0f32,
132 AnimationSpec::tween(Duration::from_millis(FADE_IN_MS), Easing::Linear),
133 )
134 });
135 let rad_anim = remember_state_with_key(&k_rad, || {
136 AnimatedValue::new(
137 0.0f32,
138 AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::FastOutSlowIn),
139 )
140 });
141 let ctr_anim = remember_state_with_key(&k_ctr, || {
142 AnimatedValue::new(
143 0.0f32,
144 AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::Linear),
145 )
146 });
147
148 let k_phase = format!("{}:ph", base);
150 let phase = remember_state_with_key(&k_phase, || 0u8);
151
152 let k_last_pid = format!("{}:lpid", base);
154 let last_pid = remember_state_with_key(&k_last_pid, || None::<PressId>);
155
156 let k_release_pending = format!("{}:rpend", base);
158 let release_pending = remember_state_with_key(&k_release_pending, || false);
159
160 let prev_pid = *last_pid.borrow();
161
162 animation_driver::touch(&format!("{}:drv:a", base));
163 animation_driver::touch(&format!("{}:drv:r", base));
164 animation_driver::touch(&format!("{}:drv:c", base));
165
166 let new_press = current_pid.is_some() && current_pid != prev_pid;
169
170 if new_press {
171 *last_pid.borrow_mut() = current_pid;
172 *phase.borrow_mut() = 1;
173 *release_pending.borrow_mut() = false;
174
175 let spec_in = AnimationSpec::tween(Duration::from_millis(FADE_IN_MS), Easing::Linear);
176 let spec_rad =
177 AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::FastOutSlowIn);
178 let spec_ctr = AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::Linear);
179
180 {
181 let mut a = alpha_anim.borrow_mut();
182 a.snap_to(0.0);
183 a.set_spec(spec_in);
184 a.set_target(1.0);
185 }
186 Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
187
188 {
189 let mut r = rad_anim.borrow_mut();
190 r.snap_to(0.0);
191 r.set_spec(spec_rad);
192 r.set_target(1.0);
193 }
194 Self::register_driver(&format!("{}:drv:r", base), rad_anim.clone());
195
196 {
197 let mut c = ctr_anim.borrow_mut();
198 c.snap_to(0.0);
199 c.set_spec(spec_ctr);
200 c.set_target(1.0);
201 }
202 Self::register_driver(&format!("{}:drv:c", base), ctr_anim.clone());
203 }
204
205 if *phase.borrow() != 0
208 && !is_pressed
209 && current_pid.is_some()
210 && current_pid == *last_pid.borrow()
211 {
212 *release_pending.borrow_mut() = true;
213 }
214
215 let fade_pct = *alpha_anim.borrow().get();
216
217 if *phase.borrow() == 1 && fade_pct >= 1.0 {
218 if *release_pending.borrow() {
220 *phase.borrow_mut() = 3;
221 let spec_out =
222 AnimationSpec::tween(Duration::from_millis(FADE_OUT_MS), Easing::Linear);
223 alpha_anim.borrow_mut().set_target(0.0);
224 alpha_anim.borrow_mut().set_spec(spec_out);
225 Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
226 } else {
227 *phase.borrow_mut() = 2;
228 }
229 }
230
231 if *phase.borrow() == 2 && *release_pending.borrow() {
232 *phase.borrow_mut() = 3;
234 let spec_out = AnimationSpec::tween(Duration::from_millis(FADE_OUT_MS), Easing::Linear);
235 alpha_anim.borrow_mut().set_target(0.0);
236 alpha_anim.borrow_mut().set_spec(spec_out);
237 Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
238 }
239
240 if *phase.borrow() == 3 && fade_pct <= 0.01 {
241 *phase.borrow_mut() = 0;
243 *last_pid.borrow_mut() = current_pid;
246 alpha_anim.borrow_mut().snap_to(0.0);
247 rad_anim.borrow_mut().snap_to(0.0);
248 ctr_anim.borrow_mut().snap_to(0.0);
249 return;
250 }
251
252 if *phase.borrow() == 0 {
253 return;
254 }
255 let snap_finish = *release_pending.borrow() && *phase.borrow() == 1;
256 if fade_pct <= 0.01 && !snap_finish {
257 return;
258 }
259
260 let draw_alpha = if snap_finish { 1.0f32 } else { fade_pct };
263
264 let rad_pct = *rad_anim.borrow().get();
265 let ctr_pct = *ctr_anim.borrow().get();
266 let current_radius = start_radius + (target_radius - start_radius) * rad_pct;
267
268 let origin_scene = match press_pos {
269 Some(pos) => {
270 let ox = rect.x + pos.x;
271 let oy = rect.y + pos.y;
272 if bounded {
273 Vec2 {
274 x: ox + (center_scene.x - ox) * ctr_pct,
275 y: oy + (center_scene.y - oy) * ctr_pct,
276 }
277 } else {
278 center_scene
279 }
280 }
281 None => center_scene,
282 };
283
284 let ripple_alpha = PRESS_ALPHA * draw_alpha * alpha;
286 if ripple_alpha <= 0.001 {
287 return;
288 }
289
290 let draw_color = base_color.with_alpha_f32(ripple_alpha);
291
292 if bounded {
293 scene.nodes.push(SceneNode::PushClip {
294 rect,
295 radius: [0.0; 4],
296 op: repose_core::ClipOp::Intersect,
297 });
298 scene.nodes.push(SceneNode::Ellipse {
299 rect: Rect {
300 x: origin_scene.x - current_radius,
301 y: origin_scene.y - current_radius,
302 w: current_radius * 2.0,
303 h: current_radius * 2.0,
304 },
305 brush: draw_color.into(),
306 });
307 scene.nodes.push(SceneNode::PopClip);
308 } else {
309 scene.nodes.push(SceneNode::Ellipse {
310 rect: Rect {
311 x: origin_scene.x - current_radius,
312 y: origin_scene.y - current_radius,
313 w: current_radius * 2.0,
314 h: current_radius * 2.0,
315 },
316 brush: draw_color.into(),
317 });
318 }
319 }
320}