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