freya_components/
popup.rs

1use freya_animation::prelude::*;
2use freya_core::prelude::*;
3use torin::{
4    prelude::{
5        Alignment,
6        Position,
7    },
8    size::Size,
9};
10
11use crate::{
12    get_theme,
13    theming::component_themes::{
14        PopupTheme,
15        PopupThemePartial,
16    },
17};
18
19/// Popup background wrapper.
20#[derive(Clone, PartialEq)]
21pub struct PopupBackground {
22    pub children: Element,
23    pub on_press: EventHandler<Event<PressEventData>>,
24}
25
26impl PopupBackground {
27    pub fn new(
28        children: Element,
29        on_press: impl Into<EventHandler<Event<PressEventData>>>,
30    ) -> Self {
31        Self {
32            children,
33            on_press: on_press.into(),
34        }
35    }
36}
37
38impl Component for PopupBackground {
39    fn render(&self) -> impl IntoElement {
40        let animation = use_animation(|conf| {
41            conf.on_creation(OnCreation::Run);
42            AnimColor::new((0, 0, 0, 0), (0, 0, 0, 150)).time(150)
43        });
44        let background = animation.get().value();
45        let on_press = self.on_press.clone();
46
47        rect()
48            .layer(2000)
49            .position(Position::new_global())
50            .child(
51                rect()
52                    .on_press(on_press)
53                    .position(Position::new_global().top(0.).left(0.))
54                    .height(Size::window_percent(100.))
55                    .width(Size::window_percent(100.))
56                    .background(background),
57            )
58            .child(
59                rect()
60                    .position(Position::new_global().top(0.).left(0.))
61                    .height(Size::window_percent(100.))
62                    .width(Size::window_percent(100.))
63                    .center()
64                    .child(self.children.clone()),
65            )
66    }
67}
68
69/// Floating popup / dialog.
70///
71/// # Example
72///
73/// ```rust
74/// # use freya::prelude::*;
75/// fn app() -> impl IntoElement {
76/// Popup::new()
77///     .width(Size::px(250.))
78///     .child(PopupTitle::new("Title".to_string()))
79///     .child(PopupContent::new().child("Hello, World!"))
80///     .child(
81///         PopupButtons::new().child(
82///             Button::new()
83///                 .expanded()
84///                 .filled()
85///                 .child("Accept"),
86///             ),
87///         )
88/// }
89/// # use freya_testing::prelude::*;
90/// # launch_doc(|| {
91/// #   rect().center().expanded().child(
92/// #      app()
93/// #   )
94/// # }, "./images/gallery_popup.png").with_scale_factor(0.8).with_hook(|test| {
95/// #   test.poll(std::time::Duration::from_millis(10), std::time::Duration::from_millis(500));
96/// # }).render();
97/// ```
98///
99/// # Preview
100/// ![Popup Preview][popup]
101#[cfg_attr(feature = "docs",
102    doc = embed_doc_image::embed_image!("popup", "images/gallery_popup.png"),
103)]
104#[derive(Clone, PartialEq)]
105pub struct Popup {
106    pub(crate) theme: Option<PopupThemePartial>,
107    children: Vec<Element>,
108    on_close_request: Option<EventHandler<()>>,
109    close_on_escape_key: bool,
110    width: Size,
111    key: DiffKey,
112}
113
114impl KeyExt for Popup {
115    fn write_key(&mut self) -> &mut DiffKey {
116        &mut self.key
117    }
118}
119
120impl Default for Popup {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl Popup {
127    pub fn new() -> Self {
128        Self {
129            theme: None,
130            children: vec![],
131            on_close_request: None,
132            close_on_escape_key: true,
133            width: Size::px(500.),
134            key: DiffKey::None,
135        }
136    }
137
138    pub fn on_close_request(mut self, on_close_request: impl Into<EventHandler<()>>) -> Self {
139        self.on_close_request = Some(on_close_request.into());
140        self
141    }
142
143    pub fn width(mut self, width: impl Into<Size>) -> Self {
144        self.width = width.into();
145        self
146    }
147}
148
149impl ChildrenExt for Popup {
150    fn get_children(&mut self) -> &mut Vec<Element> {
151        &mut self.children
152    }
153}
154
155impl Component for Popup {
156    fn render(&self) -> impl IntoElement {
157        let animations = use_animation(|conf| {
158            conf.on_creation(OnCreation::Run);
159            (
160                AnimNum::new(0.85, 1.)
161                    .time(250)
162                    .ease(Ease::Out)
163                    .function(Function::Expo),
164                AnimNum::new(0.2, 1.)
165                    .time(250)
166                    .ease(Ease::Out)
167                    .function(Function::Expo),
168            )
169        });
170
171        let PopupTheme { background, color } = get_theme!(&self.theme, popup);
172
173        let (scale, opacity) = &*animations.read();
174
175        let request_to_close = {
176            let handler = self.on_close_request.clone();
177            move || {
178                if let Some(h) = &handler {
179                    h.call(());
180                }
181            }
182        };
183
184        let on_global_key_down = {
185            let close = self.close_on_escape_key;
186            let req = request_to_close.clone();
187            move |e: Event<KeyboardEventData>| {
188                if close && e.key == Key::Named(NamedKey::Escape) {
189                    req();
190                }
191            }
192        };
193
194        PopupBackground::new(
195            rect()
196                .a11y_role(AccessibilityRole::Dialog)
197                .scale((scale.value(), scale.value()))
198                .opacity(opacity.value())
199                .corner_radius(12.)
200                .background(background)
201                .color(color)
202                .shadow(Shadow::new().y(4.).blur(5.).color((0, 0, 0, 30)))
203                .width(self.width.clone())
204                .height(Size::auto())
205                .spacing(4.)
206                .padding(8.)
207                .on_global_key_down(on_global_key_down)
208                .children(self.children.clone())
209                .into(),
210            move |_| {
211                request_to_close();
212            },
213        )
214    }
215
216    fn render_key(&self) -> DiffKey {
217        self.key.clone().or(self.default_key())
218    }
219}
220
221/// Popup title.
222#[derive(PartialEq)]
223pub struct PopupTitle {
224    text: ReadState<String>,
225}
226
227impl PopupTitle {
228    pub fn new(text: impl Into<ReadState<String>>) -> Self {
229        Self { text: text.into() }
230    }
231}
232
233impl Component for PopupTitle {
234    fn render(&self) -> impl IntoElement {
235        rect().font_size(18.).padding(8.).child(
236            label()
237                .a11y_role(AccessibilityRole::TitleBar)
238                .width(Size::fill())
239                .text(self.text.read().to_string()),
240        )
241    }
242}
243
244/// Popup content wrapper.
245#[derive(Clone, PartialEq)]
246pub struct PopupContent {
247    children: Vec<Element>,
248}
249impl Default for PopupContent {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255impl PopupContent {
256    pub fn new() -> Self {
257        Self { children: vec![] }
258    }
259}
260
261impl ChildrenExt for PopupContent {
262    fn get_children(&mut self) -> &mut Vec<Element> {
263        &mut self.children
264    }
265}
266
267impl Component for PopupContent {
268    fn render(&self) -> impl IntoElement {
269        rect()
270            .font_size(15.)
271            .padding(8.)
272            .children(self.children.clone())
273    }
274}
275
276/// Popup buttons container.
277#[derive(Clone, PartialEq)]
278pub struct PopupButtons {
279    pub children: Vec<Element>,
280}
281
282impl Default for PopupButtons {
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288impl PopupButtons {
289    pub fn new() -> Self {
290        Self { children: vec![] }
291    }
292}
293
294impl ChildrenExt for PopupButtons {
295    fn get_children(&mut self) -> &mut Vec<Element> {
296        &mut self.children
297    }
298}
299
300impl Component for PopupButtons {
301    fn render(&self) -> impl IntoElement {
302        rect()
303            .width(Size::fill())
304            .main_align(Alignment::End)
305            .padding(8.)
306            .spacing(4.)
307            .horizontal()
308            .children(self.children.clone())
309    }
310}