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