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