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(Layer::Overlay)
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#[doc(alias = "alert")]
102#[doc(alias = "dialog")]
103#[doc(alias = "window")]
104#[cfg_attr(feature = "docs",
105    doc = embed_doc_image::embed_image!("popup", "images/gallery_popup.png"),
106)]
107#[derive(Clone, PartialEq)]
108pub struct Popup {
109    pub(crate) theme: Option<PopupThemePartial>,
110    children: Vec<Element>,
111    on_close_request: Option<EventHandler<()>>,
112    close_on_escape_key: bool,
113    width: Size,
114    key: DiffKey,
115}
116
117impl KeyExt for Popup {
118    fn write_key(&mut self) -> &mut DiffKey {
119        &mut self.key
120    }
121}
122
123impl Default for Popup {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl Popup {
130    pub fn new() -> Self {
131        Self {
132            theme: None,
133            children: vec![],
134            on_close_request: None,
135            close_on_escape_key: true,
136            width: Size::px(500.),
137            key: DiffKey::None,
138        }
139    }
140
141    pub fn on_close_request(mut self, on_close_request: impl Into<EventHandler<()>>) -> Self {
142        self.on_close_request = Some(on_close_request.into());
143        self
144    }
145
146    pub fn width(mut self, width: impl Into<Size>) -> Self {
147        self.width = width.into();
148        self
149    }
150}
151
152impl ChildrenExt for Popup {
153    fn get_children(&mut self) -> &mut Vec<Element> {
154        &mut self.children
155    }
156}
157
158impl Component for Popup {
159    fn render(&self) -> impl IntoElement {
160        let animations = use_animation(|conf| {
161            conf.on_creation(OnCreation::Run);
162            (
163                AnimNum::new(0.85, 1.)
164                    .time(250)
165                    .ease(Ease::Out)
166                    .function(Function::Expo),
167                AnimNum::new(0.2, 1.)
168                    .time(250)
169                    .ease(Ease::Out)
170                    .function(Function::Expo),
171            )
172        });
173
174        let PopupTheme { background, color } = get_theme!(&self.theme, popup);
175
176        let (scale, opacity) = &*animations.read();
177
178        let request_to_close = {
179            let handler = self.on_close_request.clone();
180            move || {
181                if let Some(h) = &handler {
182                    h.call(());
183                }
184            }
185        };
186
187        let on_global_key_down = {
188            let close = self.close_on_escape_key;
189            let req = request_to_close.clone();
190            move |e: Event<KeyboardEventData>| {
191                if close && e.key == Key::Named(NamedKey::Escape) {
192                    req();
193                }
194            }
195        };
196
197        PopupBackground::new(
198            rect()
199                .a11y_role(AccessibilityRole::Dialog)
200                .scale((scale.value(), scale.value()))
201                .opacity(opacity.value())
202                .corner_radius(12.)
203                .background(background)
204                .color(color)
205                .shadow(Shadow::new().y(4.).blur(5.).color((0, 0, 0, 30)))
206                .width(self.width.clone())
207                .height(Size::auto())
208                .spacing(4.)
209                .padding(8.)
210                .on_global_key_down(on_global_key_down)
211                .children(self.children.clone())
212                .into(),
213            move |_| {
214                request_to_close();
215            },
216        )
217    }
218
219    fn render_key(&self) -> DiffKey {
220        self.key.clone().or(self.default_key())
221    }
222}
223
224/// Popup title.
225#[derive(PartialEq)]
226pub struct PopupTitle {
227    text: Readable<String>,
228}
229
230impl PopupTitle {
231    pub fn new(text: impl Into<Readable<String>>) -> Self {
232        Self { text: text.into() }
233    }
234}
235
236impl Component for PopupTitle {
237    fn render(&self) -> impl IntoElement {
238        rect().font_size(18.).padding(8.).child(
239            label()
240                .a11y_role(AccessibilityRole::TitleBar)
241                .width(Size::fill())
242                .text(self.text.read().to_string()),
243        )
244    }
245}
246
247/// Popup content wrapper.
248#[derive(Clone, PartialEq)]
249pub struct PopupContent {
250    children: Vec<Element>,
251}
252impl Default for PopupContent {
253    fn default() -> Self {
254        Self::new()
255    }
256}
257
258impl PopupContent {
259    pub fn new() -> Self {
260        Self { children: vec![] }
261    }
262}
263
264impl ChildrenExt for PopupContent {
265    fn get_children(&mut self) -> &mut Vec<Element> {
266        &mut self.children
267    }
268}
269
270impl Component for PopupContent {
271    fn render(&self) -> impl IntoElement {
272        rect()
273            .font_size(15.)
274            .padding(8.)
275            .children(self.children.clone())
276    }
277}
278
279/// Popup buttons container.
280#[derive(Clone, PartialEq)]
281pub struct PopupButtons {
282    pub children: Vec<Element>,
283}
284
285impl Default for PopupButtons {
286    fn default() -> Self {
287        Self::new()
288    }
289}
290
291impl PopupButtons {
292    pub fn new() -> Self {
293        Self { children: vec![] }
294    }
295}
296
297impl ChildrenExt for PopupButtons {
298    fn get_children(&mut self) -> &mut Vec<Element> {
299        &mut self.children
300    }
301}
302
303impl Component for PopupButtons {
304    fn render(&self) -> impl IntoElement {
305        rect()
306            .width(Size::fill())
307            .main_align(Alignment::End)
308            .padding(8.)
309            .spacing(4.)
310            .horizontal()
311            .children(self.children.clone())
312    }
313}