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}