freya_components/
popup.rs1use freya_animation::prelude::*;
2use freya_core::prelude::*;
3use torin::{
4 gaps::Gaps,
5 prelude::{
6 Alignment,
7 Position,
8 },
9 size::Size,
10};
11
12use crate::{
13 define_theme,
14 get_theme,
15};
16
17define_theme! {
18 %[component]
19 pub Popup {
20 %[fields]
21 background: Color,
22 color: Color,
23 width: Size,
24 height: Size,
25 padding: Gaps,
26 spacing: f32,
27 }
28}
29
30#[derive(Clone, PartialEq)]
32pub struct PopupBackground {
33 pub children: Element,
34 pub on_press: EventHandler<Event<PressEventData>>,
35 pub background: Color,
36}
37
38impl PopupBackground {
39 pub fn new(
40 children: Element,
41 on_press: impl Into<EventHandler<Event<PressEventData>>>,
42 background: Color,
43 ) -> Self {
44 Self {
45 children,
46 on_press: on_press.into(),
47 background,
48 }
49 }
50}
51
52impl Component for PopupBackground {
53 fn render(&self) -> impl IntoElement {
54 let on_press = self.on_press.clone();
55
56 rect()
57 .child(
58 rect()
59 .on_press(on_press)
60 .position(Position::new_global().top(0.).left(0.))
61 .height(Size::window_percent(100.))
62 .width(Size::window_percent(100.))
63 .background(self.background),
64 )
65 .child(
66 rect()
67 .position(Position::new_global().top(0.).left(0.))
68 .height(Size::window_percent(100.))
69 .width(Size::window_percent(100.))
70 .center()
71 .child(self.children.clone()),
72 )
73 }
74}
75
76#[doc(alias = "alert")]
128#[doc(alias = "dialog")]
129#[doc(alias = "window")]
130#[cfg_attr(feature = "docs",
131 doc = embed_doc_image::embed_image!("popup", "images/gallery_popup.png"),
132)]
133#[derive(Clone, PartialEq)]
134pub struct Popup {
135 pub(crate) theme: Option<PopupThemePartial>,
136 children: Vec<Element>,
137 on_close_request: Option<EventHandler<()>>,
138 close_on_escape_key: bool,
139 key: DiffKey,
140}
141
142impl KeyExt for Popup {
143 fn write_key(&mut self) -> &mut DiffKey {
144 &mut self.key
145 }
146}
147
148impl Default for Popup {
149 fn default() -> Self {
150 Self::new()
151 }
152}
153
154impl Popup {
155 pub fn new() -> Self {
156 Self {
157 theme: None,
158 children: vec![],
159 on_close_request: None,
160 close_on_escape_key: true,
161 key: DiffKey::None,
162 }
163 }
164
165 pub fn on_close_request(mut self, on_close_request: impl Into<EventHandler<()>>) -> Self {
166 self.on_close_request = Some(on_close_request.into());
167 self
168 }
169}
170
171impl ChildrenExt for Popup {
172 fn get_children(&mut self) -> &mut Vec<Element> {
173 &mut self.children
174 }
175}
176
177impl Component for Popup {
178 fn render(&self) -> impl IntoElement {
179 let show = !self.children.is_empty();
180
181 let background_animation = use_animation_with_dependencies(&show, |conf, show| {
182 conf.on_creation(OnCreation::Finish);
183 conf.on_change(OnChange::Rerun);
184
185 let value = AnimColor::new((0, 0, 0, 0), (0, 0, 0, 150)).time(150);
186
187 if *show { value } else { value.into_reversed() }
188 });
189
190 let content_animation = use_animation_with_dependencies(&show, |conf, _| {
192 conf.on_creation(OnCreation::Finish);
193 conf.on_change(OnChange::Rerun);
194
195 (
196 AnimNum::new(0.85, 1.)
197 .time(250)
198 .ease(Ease::Out)
199 .function(Function::Expo),
200 AnimNum::new(0.2, 1.)
201 .time(250)
202 .ease(Ease::Out)
203 .function(Function::Expo),
204 )
205 });
206
207 let should_render = show || *background_animation.is_running().read();
208
209 let PopupTheme {
210 background,
211 color,
212 width,
213 height,
214 padding,
215 spacing,
216 } = get_theme!(&self.theme, PopupThemePreference, "popup");
217
218 let request_to_close = {
219 let handler = self.on_close_request.clone();
220 move || {
221 if let Some(h) = &handler {
222 h.call(());
223 }
224 }
225 };
226
227 let on_global_key_down = {
228 let close = self.close_on_escape_key;
229 let req = request_to_close.clone();
230 move |e: Event<KeyboardEventData>| {
231 if close && e.key == Key::Named(NamedKey::Escape) {
232 req();
233 }
234 }
235 };
236
237 rect()
238 .layer(Layer::Overlay)
239 .position(Position::new_global())
240 .maybe_child(should_render.then(|| {
241 let background_color = background_animation.get().value();
242
243 let (scale, opacity) = &*content_animation.read();
244
245 let (scale, opacity) = if show {
246 (scale.value(), opacity.value())
247 } else {
248 (1., 0.)
249 };
250
251 PopupBackground::new(
252 rect()
253 .a11y_role(AccessibilityRole::Dialog)
254 .scale((scale, scale))
255 .opacity(opacity)
256 .corner_radius(12.)
257 .background(background)
258 .color(color)
259 .shadow(Shadow::new().y(4.).blur(5.).color((0, 0, 0, 30)))
260 .width(width)
261 .height(height)
262 .spacing(spacing)
263 .padding(padding)
264 .on_global_key_down(on_global_key_down)
265 .children(self.children.clone())
266 .into(),
267 move |_| {
268 request_to_close();
269 },
270 background_color,
271 )
272 }))
273 }
274
275 fn render_key(&self) -> DiffKey {
276 self.key.clone().or(self.default_key())
277 }
278}
279
280#[derive(PartialEq)]
282pub struct PopupTitle {
283 text: Readable<String>,
284}
285
286impl PopupTitle {
287 pub fn new(text: impl Into<Readable<String>>) -> Self {
288 Self { text: text.into() }
289 }
290}
291
292impl Component for PopupTitle {
293 fn render(&self) -> impl IntoElement {
294 rect().font_size(18.).padding(8.).child(
295 label()
296 .a11y_role(AccessibilityRole::TitleBar)
297 .width(Size::fill())
298 .text(self.text.read().to_string()),
299 )
300 }
301}
302
303#[derive(Clone, PartialEq)]
305pub struct PopupContent {
306 children: Vec<Element>,
307}
308impl Default for PopupContent {
309 fn default() -> Self {
310 Self::new()
311 }
312}
313
314impl PopupContent {
315 pub fn new() -> Self {
316 Self { children: vec![] }
317 }
318}
319
320impl ChildrenExt for PopupContent {
321 fn get_children(&mut self) -> &mut Vec<Element> {
322 &mut self.children
323 }
324}
325
326impl Component for PopupContent {
327 fn render(&self) -> impl IntoElement {
328 rect()
329 .font_size(15.)
330 .padding(8.)
331 .children(self.children.clone())
332 }
333}
334
335#[derive(Clone, PartialEq)]
337pub struct PopupButtons {
338 pub children: Vec<Element>,
339}
340
341impl Default for PopupButtons {
342 fn default() -> Self {
343 Self::new()
344 }
345}
346
347impl PopupButtons {
348 pub fn new() -> Self {
349 Self { children: vec![] }
350 }
351}
352
353impl ChildrenExt for PopupButtons {
354 fn get_children(&mut self) -> &mut Vec<Element> {
355 &mut self.children
356 }
357}
358
359impl Component for PopupButtons {
360 fn render(&self) -> impl IntoElement {
361 rect()
362 .width(Size::fill())
363 .main_align(Alignment::End)
364 .padding(8.)
365 .spacing(4.)
366 .horizontal()
367 .children(self.children.clone())
368 }
369}