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 ContainerWithContentExt for Ripple {}
73
74impl Ripple {
75 pub fn new() -> Self {
76 Self {
77 children: Vec::new(),
78 layout: LayoutData::default(),
79 key: DiffKey::None,
80 color: None,
81 duration: Duration::from_millis(800),
82 }
83 }
84
85 pub fn color(mut self, color: impl Into<Color>) -> Self {
88 self.color = Some(color.into());
89 self
90 }
91
92 pub fn duration(mut self, duration: Duration) -> Self {
95 self.duration = duration;
96 self
97 }
98}
99
100impl Component for Ripple {
101 fn render(&self) -> impl IntoElement {
102 let mut container_area = use_state(Area::default);
103 let mut ripples = use_state::<Vec<RippleInstance>>(Vec::new);
104 let mut ripple_counter = use_state(|| 0u64);
105
106 let color = self.color.unwrap_or_else(|| {
107 let theme = get_theme_or_default();
108 theme.read().colors.primary
109 });
110 let duration = self.duration;
111
112 let on_pointer_down = move |e: Event<PointerEventData>| {
113 let location = e.element_location();
114 let id = ripple_counter();
115 *ripple_counter.write() += 1;
116
117 ripples.write().push(RippleInstance {
118 id,
119 x: location.x as f32,
120 y: location.y as f32,
121 });
122 };
123
124 let area = container_area.read();
125 let max_dimension = area.width().max(area.height());
126
127 rect()
128 .layout(self.layout.clone())
129 .interactive(false)
130 .overflow(Overflow::Clip)
131 .on_pointer_down(on_pointer_down)
132 .on_sized(move |e: Event<SizedEventData>| container_area.set(e.area))
133 .children(self.children.clone())
134 .children(ripples.read().iter().map(|ripple| {
135 RippleCircle {
136 id: ripple.id,
137 x: ripple.x,
138 y: ripple.y,
139 color,
140 duration,
141 max_size: max_dimension * 2.5,
142 ripples,
143 key: DiffKey::U64(ripple.id),
144 }
145 .into()
146 }))
147 }
148
149 fn render_key(&self) -> DiffKey {
150 self.key.clone().or(self.default_key())
151 }
152}
153
154#[derive(Clone, PartialEq)]
155struct RippleCircle {
156 id: u64,
157 x: f32,
158 y: f32,
159 color: Color,
160 duration: Duration,
161 max_size: f32,
162 ripples: State<Vec<RippleInstance>>,
163 key: DiffKey,
164}
165
166impl KeyExt for RippleCircle {
167 fn write_key(&mut self) -> &mut DiffKey {
168 &mut self.key
169 }
170}
171
172impl Component for RippleCircle {
173 fn render(&self) -> impl IntoElement {
174 let id = self.id;
175 let mut ripples = self.ripples;
176
177 let animation = use_animation_with_dependencies(
178 &(self.max_size, self.duration),
179 move |conf, (max_size, duration)| {
180 conf.on_creation(OnCreation::Run);
181
182 (
183 AnimNum::new(0., *max_size)
185 .duration(*duration)
186 .function(Function::Expo)
187 .ease(Ease::Out),
188 AnimNum::new(0.35, 0.)
190 .duration(*duration)
191 .function(Function::Linear)
192 .ease(Ease::Out),
193 )
194 },
195 );
196
197 use_side_effect(move || {
199 if !*animation.is_running().read() && *animation.has_run_yet().read() {
200 ripples.write().retain(|r| r.id != id);
201 }
202 });
203
204 let (size, opacity) = animation.get().value();
205
206 let half_size = size / 2.0;
207 let left = self.x - half_size;
208 let top = self.y - half_size;
209
210 rect()
211 .position(Position::new_absolute().left(left).top(top))
212 .width(Size::px(size))
213 .height(Size::px(size))
214 .corner_radius(CornerRadius::new_all(size / 2.0))
215 .layer(1)
216 .background(self.color.with_a((opacity * 255.0) as u8))
217 }
218
219 fn render_key(&self) -> DiffKey {
220 DiffKey::U64(self.id)
221 }
222}