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();
119 let is_hovered = self.interaction_source.collect_is_hovered();
120 let is_focused = self.interaction_source.collect_is_focused();
121
122 if self.config.enable_focus && is_focused && !is_pressed {
123 scene.nodes.push(SceneNode::Rect {
124 rect,
125 brush: base_color.with_alpha_f32(FOCUS_ALPHA * alpha).into(),
126 radius,
127 });
128 }
129 if self.config.enable_hover && is_hovered && !is_pressed {
130 scene.nodes.push(SceneNode::Rect {
131 rect,
132 brush: base_color.with_alpha_f32(HOVER_ALPHA * alpha).into(),
133 radius,
134 });
135 }
136
137 if !self.config.enable_press {
138 return;
139 }
140
141 let base = self.anim_base();
142 let bounded = self.config.bounded;
143 let center_scene = Vec2 {
144 x: rect.x + rect.w * 0.5,
145 y: rect.y + rect.h * 0.5,
146 };
147
148 let target_radius = self.config.radius.unwrap_or_else(|| {
149 let diag = (rect.w * rect.w + rect.h * rect.h).sqrt();
150 if bounded {
151 diag * 0.5 + 10.0
152 } else {
153 diag * 0.5
154 }
155 });
156 let start_radius = rect.w.max(rect.h) * 0.3;
157
158 let current_pid = self.interaction_source.collect_last_press_id();
159 let press_pos = self.interaction_source.collect_last_press_position();
160
161 let k_alpha = format!("{}:a", base);
162 let k_rad = format!("{}:r", base);
163 let k_ctr = format!("{}:c", base);
164
165 let alpha_anim = remember_state_with_key(&k_alpha, || {
166 AnimatedValue::new(
167 0.0f32,
168 AnimationSpec::tween(Duration::from_millis(FADE_IN_MS), Easing::Linear),
169 )
170 });
171 let rad_anim = remember_state_with_key(&k_rad, || {
172 AnimatedValue::new(
173 0.0f32,
174 AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::FastOutSlowIn),
175 )
176 });
177 let ctr_anim = remember_state_with_key(&k_ctr, || {
178 AnimatedValue::new(
179 0.0f32,
180 AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::Linear),
181 )
182 });
183
184 let k_phase = format!("{}:ph", base);
186 let phase = remember_state_with_key(&k_phase, || 0u8);
187
188 let k_last_pid = format!("{}:lpid", base);
190 let last_pid = remember_state_with_key(&k_last_pid, || None::<PressId>);
191
192 let k_release_pending = format!("{}:rpend", base);
194 let release_pending = remember_state_with_key(&k_release_pending, || false);
195
196 let prev_pid = *last_pid.borrow();
197
198 animation_driver::touch(&format!("{}:drv:a", base));
199 animation_driver::touch(&format!("{}:drv:r", base));
200 animation_driver::touch(&format!("{}:drv:c", base));
201
202 let new_press = current_pid.is_some() && current_pid != prev_pid;
205
206 if new_press {
207 *last_pid.borrow_mut() = current_pid;
208 *phase.borrow_mut() = 1;
209 *release_pending.borrow_mut() = false;
210
211 let spec_in = AnimationSpec::tween(Duration::from_millis(FADE_IN_MS), Easing::Linear);
212 let spec_rad =
213 AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::FastOutSlowIn);
214 let spec_ctr = AnimationSpec::tween(Duration::from_millis(RADIUS_MS), Easing::Linear);
215
216 {
217 let mut a = alpha_anim.borrow_mut();
218 a.snap_to(0.0);
219 a.set_spec(spec_in);
220 a.set_target(1.0);
221 }
222 Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
223
224 {
225 let mut r = rad_anim.borrow_mut();
226 r.snap_to(0.0);
227 r.set_spec(spec_rad);
228 r.set_target(1.0);
229 }
230 Self::register_driver(&format!("{}:drv:r", base), rad_anim.clone());
231
232 {
233 let mut c = ctr_anim.borrow_mut();
234 c.snap_to(0.0);
235 c.set_spec(spec_ctr);
236 c.set_target(1.0);
237 }
238 Self::register_driver(&format!("{}:drv:c", base), ctr_anim.clone());
239 }
240
241 if *phase.borrow() != 0
244 && !is_pressed
245 && current_pid.is_some()
246 && current_pid == *last_pid.borrow()
247 {
248 *release_pending.borrow_mut() = true;
249 }
250
251 if *phase.borrow() != 0 && !animation_driver::is_registered(&format!("{}:drv:a", base)) {
252 *phase.borrow_mut() = 0;
253 *last_pid.borrow_mut() = current_pid;
254 *release_pending.borrow_mut() = false;
255 alpha_anim.borrow_mut().snap_to(0.0);
256 rad_anim.borrow_mut().snap_to(0.0);
257 ctr_anim.borrow_mut().snap_to(0.0);
258 return;
259 }
260
261 let fade_pct = *alpha_anim.borrow().get();
262
263 if *phase.borrow() == 1 && fade_pct >= 1.0 {
264 if *release_pending.borrow() {
266 *phase.borrow_mut() = 3;
267 let spec_out =
268 AnimationSpec::tween(Duration::from_millis(FADE_OUT_MS), Easing::Linear);
269 alpha_anim.borrow_mut().set_target(0.0);
270 alpha_anim.borrow_mut().set_spec(spec_out);
271 Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
272 } else {
273 *phase.borrow_mut() = 2;
274 }
275 }
276
277 if *phase.borrow() == 2 && *release_pending.borrow() {
278 *phase.borrow_mut() = 3;
280 let spec_out = AnimationSpec::tween(Duration::from_millis(FADE_OUT_MS), Easing::Linear);
281 alpha_anim.borrow_mut().set_target(0.0);
282 alpha_anim.borrow_mut().set_spec(spec_out);
283 Self::register_driver(&format!("{}:drv:a", base), alpha_anim.clone());
284 }
285
286 if *phase.borrow() == 3 && fade_pct <= 0.01 {
287 *phase.borrow_mut() = 0;
289 *last_pid.borrow_mut() = current_pid;
292 alpha_anim.borrow_mut().snap_to(0.0);
293 rad_anim.borrow_mut().snap_to(0.0);
294 ctr_anim.borrow_mut().snap_to(0.0);
295 return;
296 }
297
298 if *phase.borrow() == 0 {
299 return;
300 }
301 let snap_finish = *release_pending.borrow() && *phase.borrow() == 1;
302 if fade_pct <= 0.01 && !snap_finish {
303 return;
304 }
305
306 let draw_alpha = if snap_finish { 1.0f32 } else { fade_pct };
309
310 let rad_pct = *rad_anim.borrow().get();
311 let ctr_pct = *ctr_anim.borrow().get();
312 let current_radius = start_radius + (target_radius - start_radius) * rad_pct;
313
314 let origin_scene = match press_pos {
315 Some(pos) => {
316 let ox = rect.x + pos.x;
317 let oy = rect.y + pos.y;
318 if bounded {
319 Vec2 {
320 x: ox + (center_scene.x - ox) * ctr_pct,
321 y: oy + (center_scene.y - oy) * ctr_pct,
322 }
323 } else {
324 center_scene
325 }
326 }
327 None => center_scene,
328 };
329
330 let ripple_alpha = PRESS_ALPHA * draw_alpha * alpha;
332 if ripple_alpha <= 0.001 {
333 return;
334 }
335
336 let draw_color = base_color.with_alpha_f32(ripple_alpha);
337
338 if bounded {
339 scene.nodes.push(SceneNode::PushClip {
340 rect,
341 radius,
342 op: repose_core::ClipOp::Intersect,
343 });
344 scene.nodes.push(SceneNode::Ellipse {
345 rect: Rect {
346 x: origin_scene.x - current_radius,
347 y: origin_scene.y - current_radius,
348 w: current_radius * 2.0,
349 h: current_radius * 2.0,
350 },
351 brush: draw_color.into(),
352 });
353 scene.nodes.push(SceneNode::PopClip);
354 } else {
355 scene.nodes.push(SceneNode::Ellipse {
356 rect: Rect {
357 x: origin_scene.x - current_radius,
358 y: origin_scene.y - current_radius,
359 w: current_radius * 2.0,
360 h: current_radius * 2.0,
361 },
362 brush: draw_color.into(),
363 });
364 }
365 }
366}