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 min_width: Size,
26 min_height: Size,
27 max_width: Size,
28 max_height: Size,
29 padding: Gaps,
30 spacing: f32,
31 }
32}
33
34#[derive(Clone, PartialEq)]
36pub struct PopupBackground {
37 pub children: Element,
38 pub on_press: EventHandler<Event<PressEventData>>,
39 pub background: Color,
40}
41
42impl PopupBackground {
43 pub fn new(
44 children: Element,
45 on_press: impl Into<EventHandler<Event<PressEventData>>>,
46 background: Color,
47 ) -> Self {
48 Self {
49 children,
50 on_press: on_press.into(),
51 background,
52 }
53 }
54}
55
56impl Component for PopupBackground {
57 fn render(&self) -> impl IntoElement {
58 let on_press = self.on_press.clone();
59
60 rect()
61 .child(
62 rect()
63 .on_press(on_press)
64 .position(Position::new_global().top(0.).left(0.))
65 .height(Size::window_percent(100.))
66 .width(Size::window_percent(100.))
67 .background(self.background),
68 )
69 .child(
70 rect()
71 .position(Position::new_global().top(0.).left(0.))
72 .height(Size::window_percent(100.))
73 .width(Size::window_percent(100.))
74 .center()
75 .child(self.children.clone()),
76 )
77 }
78}
79
80#[doc(alias = "alert")]
123#[doc(alias = "dialog")]
124#[doc(alias = "window")]
125#[derive(Clone, PartialEq)]
126pub struct Popup {
127 pub(crate) theme: Option<PopupThemePartial>,
128 children: Vec<Element>,
129 on_close_request: Option<EventHandler<()>>,
130 close_on_escape_key: bool,
131 key: DiffKey,
132}
133
134impl KeyExt for Popup {
135 fn key_mut(&mut self) -> &mut DiffKey {
136 &mut self.key
137 }
138}
139
140impl Default for Popup {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl Popup {
147 pub fn new() -> Self {
148 Self {
149 theme: None,
150 children: vec![],
151 on_close_request: None,
152 close_on_escape_key: true,
153 key: DiffKey::None,
154 }
155 }
156
157 pub fn on_close_request(mut self, on_close_request: impl Into<EventHandler<()>>) -> Self {
158 self.on_close_request = Some(on_close_request.into());
159 self
160 }
161}
162
163impl ChildrenExt for Popup {
164 fn children_mut(&mut self) -> &mut Vec<Element> {
165 &mut self.children
166 }
167}
168
169impl Component for Popup {
170 fn render(&self) -> impl IntoElement {
171 let show = !self.children.is_empty();
172
173 let background_animation = use_animation_with_dependencies(&show, |conf, show| {
174 conf.on_creation(OnCreation::Finish);
175 conf.on_change(OnChange::Rerun);
176
177 let value = AnimColor::new((0, 0, 0, 0), (0, 0, 0, 150)).time(150);
178
179 if *show { value } else { value.into_reversed() }
180 });
181
182 let content_animation = use_animation_with_dependencies(&show, |conf, _| {
184 conf.on_creation(OnCreation::Finish);
185 conf.on_change(OnChange::Rerun);
186
187 (
188 AnimNum::new(0.85, 1.)
189 .time(250)
190 .ease(Ease::Out)
191 .function(Function::Expo),
192 AnimNum::new(0.2, 1.)
193 .time(250)
194 .ease(Ease::Out)
195 .function(Function::Expo),
196 )
197 });
198
199 let background_color = background_animation.get().value();
200 let should_render = show || background_color.a() > 0;
201
202 let PopupTheme {
203 background,
204 color,
205 width,
206 height,
207 min_width,
208 min_height,
209 max_width,
210 max_height,
211 padding,
212 spacing,
213 } = get_theme!(&self.theme, PopupThemePreference, "popup");
214
215 let request_to_close = {
216 let handler = self.on_close_request.clone();
217 move || {
218 if let Some(h) = &handler {
219 h.call(());
220 }
221 }
222 };
223
224 let on_global_key_down = {
225 let close = self.close_on_escape_key;
226 let req = request_to_close.clone();
227 move |e: Event<KeyboardEventData>| {
228 if close && e.key == Key::Named(NamedKey::Escape) {
229 req();
230 }
231 }
232 };
233
234 rect()
235 .layer(Layer::Overlay)
236 .position(Position::new_global())
237 .maybe_child(should_render.then(|| {
238 let (scale, opacity) = &*content_animation.read();
239
240 let (scale, opacity) = if show {
241 (scale.value(), opacity.value())
242 } else {
243 (1., 0.)
244 };
245
246 PopupBackground::new(
247 rect()
248 .a11y_role(AccessibilityRole::Dialog)
249 .scale((scale, scale))
250 .opacity(opacity)
251 .corner_radius(12.)
252 .background(background)
253 .color(color)
254 .shadow(Shadow::new().y(4.).blur(5.).color((0, 0, 0, 30)))
255 .width(width)
256 .height(height)
257 .min_width(min_width)
258 .min_height(min_height)
259 .max_width(max_width)
260 .max_height(max_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 children_mut(&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 children_mut(&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}