Skip to main content

a2ui_tui/components/
modal.rs

1//! Modal component — always renders its trigger in-place; the open content is
2//! shown as a floating overlay by the surface renderer (a real modal dialog),
3//! not by swapping in here.
4
5use ratatui::{Frame, layout::Rect};
6
7use a2ui_base::model::component_context::ComponentContext;
8use crate::component_impl::TuiComponent;
9
10/// Modal component implementation.
11///
12/// Always renders the `trigger` child in its layout position. When open, the
13/// `content` child is drawn as a centered overlay on top of the whole surface
14/// by [`crate::surface::SurfaceRenderer`] — so the trigger keeps its place
15/// (and focus) and the dialog actually floats above the UI instead of
16/// replacing the trigger inline.
17pub struct ModalComponent;
18
19impl TuiComponent for ModalComponent {
20    fn name(&self) -> &'static str {
21        "Modal"
22    }
23
24    fn render(
25        &self,
26        ctx: &ComponentContext,
27        area: Rect,
28        frame: &mut Frame,
29        render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
30        _measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
31    ) {
32        let comp_model = match ctx.components.get(&ctx.component_id) {
33            Some(m) => m,
34            None => return,
35        };
36
37        // The trigger always renders in-place; the open content is overlaid by
38        // the surface renderer, not here.
39        if let Some(trigger_id) = comp_model.get_property::<String>("trigger") {
40            if area.width > 0 && area.height > 0 {
41                render_child(&trigger_id, area, frame, "");
42            }
43        }
44    }
45
46    fn natural_height(
47        &self,
48        ctx: &ComponentContext,
49        available_width: u16,
50        measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
51    ) -> Option<u16> {
52        let comp_model = ctx.components.get(&ctx.component_id)?;
53        // Sizing follows the trigger (the in-place element); the overlay content
54        // is sized independently by the surface renderer.
55        let child_id = comp_model.get_property::<String>("trigger")?;
56        measure_child(&child_id, "", available_width)
57    }
58}