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 background_color = background_animation.get().value();
208 let should_render = show || background_color.a() > 0;
209
210 let PopupTheme {
211 background,
212 color,
213 width,
214 height,
215 padding,
216 spacing,
217 } = get_theme!(&self.theme, PopupThemePreference, "popup");
218
219 let request_to_close = {
220 let handler = self.on_close_request.clone();
221 move || {
222 if let Some(h) = &handler {
223 h.call(());
224 }
225 }
226 };
227
228 let on_global_key_down = {
229 let close = self.close_on_escape_key;
230 let req = request_to_close.clone();
231 move |e: Event<KeyboardEventData>| {
232 if close && e.key == Key::Named(NamedKey::Escape) {
233 req();
234 }
235 }
236 };
237
238 rect()
239 .layer(Layer::Overlay)
240 .position(Position::new_global())
241 .maybe_child(should_render.then(|| {
242 let (scale, opacity) = &*content_animation.read();
243
244 let (scale, opacity) = if show {
245 (scale.value(), opacity.value())
246 } else {
247 (1., 0.)
248 };
249
250 PopupBackground::new(
251 rect()
252 .a11y_role(AccessibilityRole::Dialog)
253 .scale((scale, scale))
254 .opacity(opacity)
255 .corner_radius(12.)
256 .background(background)
257 .color(color)
258 .shadow(Shadow::new().y(4.).blur(5.).color((0, 0, 0, 30)))
259 .width(width)
260 .height(height)
261 .spacing(spacing)
262 .padding(padding)
263 .on_global_key_down(on_global_key_down)
264 .children(self.children.clone())
265 .into(),
266 move |_| {
267 request_to_close();
268 },
269 background_color,
270 )
271 }))
272 }
273
274 fn render_key(&self) -> DiffKey {
275 self.key.clone().or(self.default_key())
276 }
277}
278
279#[derive(PartialEq)]
281pub struct PopupTitle {
282 text: Readable<String>,
283}
284
285impl PopupTitle {
286 pub fn new(text: impl Into<Readable<String>>) -> Self {
287 Self { text: text.into() }
288 }
289}
290
291impl Component for PopupTitle {
292 fn render(&self) -> impl IntoElement {
293 rect().font_size(18.).padding(8.).child(
294 label()
295 .a11y_role(AccessibilityRole::TitleBar)
296 .width(Size::fill())
297 .text(self.text.read().to_string()),
298 )
299 }
300}
301
302#[derive(Clone, PartialEq)]
304pub struct PopupContent {
305 children: Vec<Element>,
306}
307impl Default for PopupContent {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313impl PopupContent {
314 pub fn new() -> Self {
315 Self { children: vec![] }
316 }
317}
318
319impl ChildrenExt for PopupContent {
320 fn get_children(&mut self) -> &mut Vec<Element> {
321 &mut self.children
322 }
323}
324
325impl Component for PopupContent {
326 fn render(&self) -> impl IntoElement {
327 rect()
328 .font_size(15.)
329 .padding(8.)
330 .children(self.children.clone())
331 }
332}
333
334#[derive(Clone, PartialEq)]
336pub struct PopupButtons {
337 pub children: Vec<Element>,
338}
339
340impl Default for PopupButtons {
341 fn default() -> Self {
342 Self::new()
343 }
344}
345
346impl PopupButtons {
347 pub fn new() -> Self {
348 Self { children: vec![] }
349 }
350}
351
352impl ChildrenExt for PopupButtons {
353 fn get_children(&mut self) -> &mut Vec<Element> {
354 &mut self.children
355 }
356}
357
358impl Component for PopupButtons {
359 fn render(&self) -> impl IntoElement {
360 rect()
361 .width(Size::fill())
362 .main_align(Alignment::End)
363 .padding(8.)
364 .spacing(4.)
365 .horizontal()
366 .children(self.children.clone())
367 }
368}