freya_components/
portal.rs1use std::{
2 collections::HashMap,
3 fmt::Debug,
4 hash::{
5 DefaultHasher,
6 Hash,
7 Hasher,
8 },
9 time::Duration,
10};
11
12use freya_animation::prelude::*;
13use freya_core::{
14 prelude::*,
15 scope_id::ScopeId,
16};
17use torin::{
18 prelude::{
19 Area,
20 Point2D,
21 Position,
22 Size2D,
23 },
24 size::Size,
25};
26
27#[derive(PartialEq)]
28pub struct Portal<T> {
29 key: DiffKey,
30 children: Vec<Element>,
31 id: T,
32 function: Function,
33 duration: Duration,
34 ease: Ease,
35 layout: LayoutData,
36 show: bool,
37 dependency: Option<u64>,
38}
39
40impl<T> ChildrenExt for Portal<T> {
41 fn get_children(&mut self) -> &mut Vec<Element> {
42 &mut self.children
43 }
44}
45
46impl<T> Portal<T> {
47 pub fn new(id: T) -> Self {
48 Self {
49 key: DiffKey::None,
50 children: vec![],
51 id,
52 function: Function::default(),
53 duration: Duration::from_millis(750),
54 ease: Ease::default(),
55 layout: LayoutData::default(),
56 show: true,
57 dependency: None,
58 }
59 }
60
61 pub fn function(mut self, function: Function) -> Self {
62 self.function = function;
63 self
64 }
65
66 pub fn duration(mut self, duration: Duration) -> Self {
67 self.duration = duration;
68 self
69 }
70
71 pub fn ease(mut self, ease: Ease) -> Self {
72 self.ease = ease;
73 self
74 }
75
76 pub fn show(mut self, show: bool) -> Self {
77 self.show = show;
78 self
79 }
80
81 pub fn animation_dependency(mut self, dependency: impl Hash) -> Self {
83 let mut hasher = DefaultHasher::default();
84 dependency.hash(&mut hasher);
85 self.dependency = Some(hasher.finish());
86 self
87 }
88}
89
90impl<T> LayoutExt for Portal<T> {
91 fn get_layout(&mut self) -> &mut LayoutData {
92 &mut self.layout
93 }
94}
95
96impl<T> ContainerSizeExt for Portal<T> {}
97
98impl<T> KeyExt for Portal<T> {
99 fn write_key(&mut self) -> &mut DiffKey {
100 &mut self.key
101 }
102}
103
104impl<T: Clone + Eq + Hash + Debug + 'static> Component for Portal<T> {
105 fn render(&self) -> impl IntoElement {
106 let mut positions = use_hook(|| match try_consume_context::<PortalsMap<T>>() {
107 Some(ctx) => ctx,
108 None => {
109 let ctx = PortalsMap {
110 ids: State::create_in_scope(HashMap::default(), ScopeId::ROOT),
111 };
112 provide_context_for_scope_id(ctx.clone(), ScopeId::ROOT);
113 ctx
114 }
115 });
116 let id = self.id.clone();
117 let dependency = self.dependency;
118 let init_size = use_hook(move || {
120 positions
121 .ids
122 .write()
123 .remove(&id)
124 .filter(|(_, last)| dependency.is_none() || *last != dependency)
125 .map(|(area, _)| area)
126 });
127 let mut previous_size = use_state::<Option<Area>>(|| None);
128 let mut current_size = use_state::<Option<Area>>(|| None);
129 let mut last_dependency = use_state::<Option<u64>>(|| None);
130 let mut should_animate = use_state(|| false);
131
132 let mut animation = use_animation_with_dependencies(
133 &(self.function, self.duration, self.ease),
134 move |conf, (function, duration, ease)| {
135 conf.on_change(OnChange::Nothing);
136 let from_size = previous_size
137 .read()
138 .unwrap_or(init_size.unwrap_or_default());
139 let to_size = current_size.read().unwrap_or_default();
140 (
141 AnimNum::new(from_size.origin.x, to_size.origin.x)
142 .duration(*duration)
143 .ease(*ease)
144 .function(*function),
145 AnimNum::new(from_size.origin.y, to_size.origin.y)
146 .duration(*duration)
147 .ease(*ease)
148 .function(*function),
149 AnimNum::new(from_size.size.width, to_size.size.width)
150 .duration(*duration)
151 .ease(*ease)
152 .function(*function),
153 AnimNum::new(from_size.size.height, to_size.size.height)
154 .duration(*duration)
155 .ease(*ease)
156 .function(*function),
157 )
158 },
159 );
160
161 use_side_effect(move || {
163 if !*animation.is_running().read() {
164 should_animate.set_if_modified(false);
165 }
166 });
167
168 let at_rest = !should_animate() && current_size.read().is_some();
170 let area = if at_rest {
171 current_size.read().unwrap_or_default()
172 } else {
173 let (x, y, width, height) = animation.get().value();
174 Area::new(Point2D::new(x, y), Size2D::new(width, height))
175 };
176
177 let is_new = init_size.is_none() && current_size.read().is_none();
179 let is_stacked = self.dependency.is_some()
180 && (is_new || (at_rest && self.dependency == *last_dependency.read()));
181 let global_area = (!is_stacked).then_some(area);
182
183 let id = self.id.clone();
184 let show = self.show;
185
186 rect()
187 .a11y_focusable(false)
188 .on_sized(move |e: Event<SizedEventData>| {
189 if !show || *current_size.peek() == Some(e.area) {
190 return;
191 }
192
193 positions
194 .ids
195 .write()
196 .insert(id.clone(), (e.area, dependency));
197
198 let dependency_changed =
200 dependency.is_none() || *last_dependency.peek() != dependency;
201 last_dependency.set_if_modified(dependency);
202 let animate =
203 dependency_changed && (init_size.is_some() || current_size.peek().is_some());
204
205 previous_size.set(current_size());
206 current_size.set(Some(e.area));
207 should_animate.set_if_modified(animate);
208
209 spawn(async move {
210 if animate {
211 animation.start();
212 } else {
213 animation.finish();
214 }
215 });
216 })
217 .width(self.layout.width.clone())
218 .height(self.layout.height.clone())
219 .child(
220 rect()
221 .map(global_area, |el, area| {
222 el.offset_x(area.min_x())
223 .offset_y(area.min_y())
224 .position(Position::new_global())
225 })
226 .child(
227 rect()
228 .width(global_area.map_or(Size::fill(), |area| Size::px(area.width())))
229 .height(
230 global_area.map_or(Size::fill(), |area| Size::px(area.height())),
231 )
232 .opacity(if is_stacked || !is_new { 1. } else { 0. })
234 .children(if self.show {
235 self.children.clone()
236 } else {
237 vec![]
238 }),
239 ),
240 )
241 }
242
243 fn render_key(&self) -> DiffKey {
244 self.key.clone().or(self.default_key())
245 }
246}
247
248#[derive(Clone)]
249pub struct PortalsMap<T: Clone + PartialEq + 'static> {
250 pub ids: State<HashMap<T, (Area, Option<u64>)>>,
251}