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