Skip to main content

a2ui_tui/components/
modal.rs

1//! Modal component — renders either the trigger child or the content child.
2
3use ratatui::{Frame, layout::Rect};
4
5use a2ui_base::model::component_context::ComponentContext;
6use a2ui_base::protocol::common_types::DynamicBoolean;
7use crate::component_impl::TuiComponent;
8
9/// Modal component implementation.
10///
11/// In TUI, rendering a true modal overlay is complex. This component renders
12/// the `trigger` child when closed and the `content` child when open, switching
13/// between them based on the `isOpen` property (a `DynamicBoolean`).
14pub struct ModalComponent;
15
16impl TuiComponent for ModalComponent {
17    fn name(&self) -> &'static str {
18        "Modal"
19    }
20
21    fn render(
22        &self,
23        ctx: &ComponentContext,
24        area: Rect,
25        frame: &mut Frame,
26        render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
27        _measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
28    ) {
29        let comp_model = match ctx.components.get(&ctx.component_id) {
30            Some(m) => m,
31            None => return,
32        };
33
34        // Check if modal is open.
35        let is_open = comp_model
36            .get_property::<DynamicBoolean>("isOpen")
37            .map(|db| ctx.data_context.resolve_dynamic_boolean(&db))
38            .unwrap_or(false);
39
40        if is_open {
41            // Render content child.
42            if let Some(content_id) = comp_model.get_property::<String>("content") {
43                if area.width > 0 && area.height > 0 {
44                    render_child(&content_id, area, frame, "");
45                }
46            }
47        } else {
48            // Render trigger child.
49            if let Some(trigger_id) = comp_model.get_property::<String>("trigger") {
50                if area.width > 0 && area.height > 0 {
51                    render_child(&trigger_id, area, frame, "");
52                }
53            }
54        }
55    }
56
57    fn natural_height(
58        &self,
59        ctx: &ComponentContext,
60        available_width: u16,
61        measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
62    ) -> Option<u16> {
63        let comp_model = ctx.components.get(&ctx.component_id)?;
64        let is_open = comp_model
65            .get_property::<DynamicBoolean>("isOpen")
66            .map(|db| ctx.data_context.resolve_dynamic_boolean(&db))
67            .unwrap_or(false);
68        let child_id = comp_model.get_property::<String>(if is_open { "content" } else { "trigger" })?;
69        measure_child(&child_id, "", available_width)
70    }
71}