tessera_ui_basic_components/
dialog.rs

1use std::sync::Arc;
2
3use derive_builder::Builder;
4use tessera_ui::{Color, DimensionValue, winit};
5use tessera_ui_macros::tessera;
6
7use crate::surface::{SurfaceArgsBuilder, surface};
8
9/// Arguments for the `dialog_provider` component.
10#[derive(Builder)]
11pub struct DialogProviderArgs {
12    /// Determines whether the dialog is currently visible.
13    pub is_open: bool,
14    /// Callback function triggered when a close request is made (e.g., by clicking the scrim or pressing ESC).
15    pub on_close_request: Arc<dyn Fn() + Send + Sync>,
16}
17
18/// A provider component that manages the rendering and event flow for a modal dialog.
19///
20/// This component should be used as one of the outermost layers of the application.
21/// It renders the main content, and when `is_open` is true, it overlays a modal
22/// dialog, intercepting all input.
23#[tessera]
24pub fn dialog_provider(
25    args: DialogProviderArgs,
26    main_content: impl FnOnce(),
27    dialog_content: impl FnOnce(),
28) {
29    // 1. Render the main application content unconditionally.
30    main_content();
31
32    // 2. If the dialog is open, render the modal overlay.
33    if args.is_open {
34        let on_close_for_keyboard = args.on_close_request.clone();
35
36        // 2a. Scrim
37        // This Surface covers the entire screen, consuming all mouse clicks
38        // and triggering the close request.
39        surface(
40            SurfaceArgsBuilder::default()
41                .color(Color::BLACK)
42                .on_click(Some(args.on_close_request))
43                .width(DimensionValue::Fill {
44                    min: None,
45                    max: None,
46                })
47                .height(DimensionValue::Fill {
48                    min: None,
49                    max: None,
50                })
51                .build()
52                .unwrap(),
53            None,
54            || {},
55        );
56
57        // 2b. State Handler for intercepting keyboard events.
58        state_handler(Box::new(move |input| {
59            // Atomically consume all keyboard events to prevent them from propagating
60            // to the main content underneath.
61            let events = input.keyboard_events.drain(..).collect::<Vec<_>>();
62
63            // Check the consumed events for the 'Escape' key press.
64            for event in events {
65                if event.state == winit::event::ElementState::Pressed {
66                    if let winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::Escape) =
67                        event.physical_key
68                    {
69                        (on_close_for_keyboard)();
70                    }
71                }
72            }
73        }));
74
75        // 2c. Dialog Content
76        // The user-defined dialog content is rendered on top of everything.
77        dialog_content();
78    }
79}