freya_material_design/
ripple.rs1use std::time::Duration;
2
3use freya_animation::prelude::*;
4use freya_components::theming::hooks::get_theme_or_default;
5use freya_core::prelude::*;
6use torin::prelude::{
7 Area,
8 Position,
9 Size,
10};
11
12#[derive(Clone, PartialEq)]
14struct RippleInstance {
15 id: u64,
16 x: f32,
17 y: f32,
18}
19
20#[derive(Clone, PartialEq)]
38pub struct Ripple {
39 children: Vec<Element>,
40 layout: LayoutData,
41 key: DiffKey,
42 color: Option<Color>,
43 duration: Duration,
44}
45
46impl Default for Ripple {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl ChildrenExt for Ripple {
53 fn get_children(&mut self) -> &mut Vec<Element> {
54 &mut self.children
55 }
56}
57
58impl KeyExt for Ripple {
59 fn write_key(&mut self) -> &mut DiffKey {
60 &mut self.key
61 }
62}
63
64impl LayoutExt for Ripple {
65 fn get_layout(&mut self) -> &mut LayoutData {
66 &mut self.layout
67 }
68}
69
70impl ContainerExt for Ripple {}
71
72impl Ripple {
73 pub fn new() -> Self {
74 Self {
75 children: Vec::new(),
76 layout: LayoutData::default(),
77 key: DiffKey::None,
78 color: None,
79 duration: Duration::from_millis(800),
80 }
81 }
82
83 pub fn color(mut self, color: impl Into<Color>) -> Self {
86 self.color = Some(color.into());
87 self
88 }
89
90 pub fn duration(mut self, duration: Duration) -> Self {
93 self.duration = duration;
94 self
95 }
96}
97
98impl Render for Ripple {
99 fn render(&self) -> impl IntoElement {
100 let mut container_area = use_state(Area::default);
101 let mut ripples = use_state::<Vec<RippleInstance>>(Vec::new);
102 let mut ripple_counter = use_state(|| 0u64);
103
104 let color = self.color.unwrap_or_else(|| {
105 let theme = get_theme_or_default();
106 theme.read().colors.primary
107 });
108 let duration = self.duration;
109
110 let on_mouse_down = move |e: Event<MouseEventData>| {
111 let location = e.element_location;
112 let id = ripple_counter();
113 *ripple_counter.write() += 1;
114
115 ripples.write().push(RippleInstance {
116 id,
117 x: location.x as f32,
118 y: location.y as f32,
119 });
120 };
121
122 let area = container_area.read();
123 let max_dimension = area.width().max(area.height());
124
125 rect()
126 .layout(self.layout.clone())
127 .interactive(false)
128 .overflow(Overflow::Clip)
129 .on_mouse_down(on_mouse_down)
130 .on_sized(move |e: Event<SizedEventData>| container_area.set(e.area))
131 .children(self.children.clone())
132 .children_iter(ripples.read().iter().map(|ripple| {
133 RippleCircle {
134 id: ripple.id,
135 x: ripple.x,
136 y: ripple.y,
137 color,
138 duration,
139 max_size: max_dimension * 2.5,
140 ripples,
141 key: DiffKey::U64(ripple.id),
142 }
143 .into()
144 }))
145 }
146
147 fn render_key(&self) -> DiffKey {
148 self.key.clone().or(self.default_key())
149 }
150}
151
152#[derive(Clone, PartialEq)]
153struct RippleCircle {
154 id: u64,
155 x: f32,
156 y: f32,
157 color: Color,
158 duration: Duration,
159 max_size: f32,
160 ripples: State<Vec<RippleInstance>>,
161 key: DiffKey,
162}
163
164impl KeyExt for RippleCircle {
165 fn write_key(&mut self) -> &mut DiffKey {
166 &mut self.key
167 }
168}
169
170impl Render for RippleCircle {
171 fn render(&self) -> impl IntoElement {
172 let id = self.id;
173 let mut ripples = self.ripples;
174
175 let animation = use_animation_with_dependencies(
176 &(self.max_size, self.duration),
177 move |conf, (max_size, duration)| {
178 conf.on_creation(OnCreation::Run);
179
180 (
181 AnimNum::new(0., *max_size)
183 .duration(*duration)
184 .function(Function::Expo)
185 .ease(Ease::Out),
186 AnimNum::new(0.35, 0.)
188 .duration(*duration)
189 .function(Function::Linear)
190 .ease(Ease::Out),
191 )
192 },
193 );
194
195 use_side_effect(move || {
197 if !*animation.is_running().read() && *animation.has_run_yet().read() {
198 ripples.write().retain(|r| r.id != id);
199 }
200 });
201
202 let (size, opacity) = animation.get().value();
203
204 let half_size = size / 2.0;
205 let left = self.x - half_size;
206 let top = self.y - half_size;
207
208 rect()
209 .position(Position::new_absolute().left(left).top(top))
210 .width(Size::px(size))
211 .height(Size::px(size))
212 .corner_radius(CornerRadius::new_all(size / 2.0))
213 .layer(1)
214 .background(self.color.with_a((opacity * 255.0) as u8))
215 }
216
217 fn render_key(&self) -> DiffKey {
218 DiffKey::U64(self.id)
219 }
220}