freya_components/
portal.rs

1use std::{
2    collections::HashMap,
3    fmt::Debug,
4    time::Duration,
5};
6
7use freya_animation::prelude::*;
8use freya_core::{
9    prelude::*,
10    scope_id::ScopeId,
11};
12use torin::{
13    prelude::{
14        Area,
15        Position,
16    },
17    size::Size,
18};
19
20#[derive(PartialEq)]
21pub struct Portal<T> {
22    key: DiffKey,
23    children: Vec<Element>,
24    id: T,
25    function: Function,
26    duration: Duration,
27    ease: Ease,
28    layout: LayoutData,
29    show: bool,
30}
31
32impl<T> ChildrenExt for Portal<T> {
33    fn get_children(&mut self) -> &mut Vec<Element> {
34        &mut self.children
35    }
36}
37
38impl<T> Portal<T> {
39    pub fn new(id: T) -> Self {
40        Self {
41            key: DiffKey::None,
42            children: vec![],
43            id,
44            function: Function::default(),
45            duration: Duration::from_millis(750),
46            ease: Ease::default(),
47            layout: LayoutData::default(),
48            show: true,
49        }
50    }
51
52    pub fn function(mut self, function: Function) -> Self {
53        self.function = function;
54        self
55    }
56
57    pub fn duration(mut self, duration: Duration) -> Self {
58        self.duration = duration;
59        self
60    }
61
62    pub fn ease(mut self, ease: Ease) -> Self {
63        self.ease = ease;
64        self
65    }
66
67    pub fn show(mut self, show: bool) -> Self {
68        self.show = show;
69        self
70    }
71}
72
73impl<T> LayoutExt for Portal<T> {
74    fn get_layout(&mut self) -> &mut LayoutData {
75        &mut self.layout
76    }
77}
78
79impl<T> ContainerSizeExt for Portal<T> {}
80
81impl<T> KeyExt for Portal<T> {
82    fn write_key(&mut self) -> &mut DiffKey {
83        &mut self.key
84    }
85}
86
87impl<T: PartialEq + 'static + Clone + std::hash::Hash + Eq + Debug> Component for Portal<T> {
88    fn render(&self) -> impl IntoElement {
89        let mut positions = use_hook(|| match try_consume_context::<PortalsMap<T>>() {
90            Some(ctx) => ctx,
91            None => {
92                let ctx = PortalsMap {
93                    ids: State::create_in_scope(HashMap::default(), ScopeId::ROOT),
94                };
95                provide_context_for_scope_id(ctx.clone(), ScopeId::ROOT);
96                ctx
97            }
98        });
99        let id = self.id.clone();
100        let init_size = use_hook(move || positions.ids.write().remove(&id));
101        let mut previous_size = use_state::<Option<Area>>(|| None);
102        let mut current_size = use_state::<Option<Area>>(|| None);
103
104        let mut animation = use_animation_with_dependencies(
105            &(self.function, self.duration, self.ease),
106            move |conf, (function, duration, ease)| {
107                conf.on_change(OnChange::Nothing);
108                let from_size = previous_size
109                    .read()
110                    .unwrap_or(init_size.unwrap_or_default());
111                let to_size = current_size.read().unwrap_or_default();
112                (
113                    AnimNum::new(from_size.origin.x, to_size.origin.x)
114                        .duration(*duration)
115                        .ease(*ease)
116                        .function(*function),
117                    AnimNum::new(from_size.origin.y, to_size.origin.y)
118                        .duration(*duration)
119                        .ease(*ease)
120                        .function(*function),
121                    AnimNum::new(from_size.size.width, to_size.size.width)
122                        .duration(*duration)
123                        .ease(*ease)
124                        .function(*function),
125                    AnimNum::new(from_size.size.height, to_size.size.height)
126                        .duration(*duration)
127                        .ease(*ease)
128                        .function(*function),
129                )
130            },
131        );
132
133        let (offset_x, offset_y, width, height) = animation.get().value();
134        let id = self.id.clone();
135        let show = self.show;
136
137        rect()
138            .a11y_focusable(false)
139            .on_sized(move |e: Event<SizedEventData>| {
140                if *current_size.peek() != Some(e.area) && show {
141                    previous_size.set(current_size());
142                    current_size.set(Some(e.area));
143                    positions.ids.write().insert(id.clone(), e.area);
144
145                    spawn(async move {
146                        let has_init_size = init_size.is_some();
147                        let has_previous_size = previous_size.peek().is_some();
148
149                        if !*animation.has_run_yet().read() && !has_init_size {
150                            // Mark the animation as finished if the component was just created and has no init size
151                            animation.finish();
152                        } else if has_init_size || has_previous_size {
153                            // Start the animation if the component size changed and has a previous size
154                            animation.start();
155                        }
156                    });
157                }
158            })
159            .width(self.layout.width.clone())
160            .height(self.layout.height.clone())
161            .child(
162                rect()
163                    .offset_x(offset_x)
164                    .offset_y(offset_y)
165                    .position(Position::new_global())
166                    .child(
167                        rect()
168                            .width(Size::px(width))
169                            .height(Size::px(height))
170                            // Only show the element after it has been sized
171                            .opacity(
172                                if init_size.is_some()
173                                    || previous_size.read().is_some()
174                                    || current_size.read().is_some()
175                                {
176                                    1.
177                                } else {
178                                    0.
179                                },
180                            )
181                            .children(if self.show {
182                                self.children.clone()
183                            } else {
184                                vec![]
185                            }),
186                    ),
187            )
188    }
189
190    fn render_key(&self) -> DiffKey {
191        self.key.clone().or(self.default_key())
192    }
193}
194
195#[derive(Clone)]
196pub struct PortalsMap<T: Clone + PartialEq + 'static> {
197    pub ids: State<HashMap<T, Area>>,
198}