Skip to main content

a2ui_tui/components/
card.rs

1//! Card component — renders a rounded-border container with a single child.
2
3use ratatui::{
4    Frame,
5    layout::Rect,
6    style::{Color, Style},
7    widgets::{Block, BorderType, Borders},
8};
9
10use a2ui_base::model::component_context::ComponentContext;
11use crate::component_impl::TuiComponent;
12
13/// Card component implementation.
14///
15/// Renders a `Block` with rounded borders and padding, then renders the
16/// single child inside the block's inner area.
17/// Applies a default 1-cell margin.
18pub struct CardComponent;
19
20impl TuiComponent for CardComponent {
21    fn name(&self) -> &'static str {
22        "Card"
23    }
24
25    fn render(
26        &self,
27        ctx: &ComponentContext,
28        area: Rect,
29        frame: &mut Frame,
30        render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
31        _measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
32    ) {
33        let comp_model = match ctx.components.get(&ctx.component_id) {
34            Some(m) => m,
35            None => return,
36        };
37
38        // Apply default 1-cell margin on all sides (never collapses to zero).
39        let inner = crate::layout_engine::padded_content(area);
40
41        if inner.width == 0 || inner.height == 0 {
42            return;
43        }
44
45        // Build the card block with rounded borders and a subtle style.
46        let block = Block::default()
47            .borders(Borders::ALL)
48            .border_type(BorderType::Rounded)
49            .border_style(Style::default().fg(Color::DarkGray))
50            .style(Style::default());
51
52        // Compute the inner content area (inside the block borders).
53        let child_area = block.inner(inner);
54
55        // Render the block itself.
56        frame.render_widget(block, inner);
57
58        // Render the single child inside the card, if present.
59        if let Some(child_id) = comp_model.child() {
60            if child_area.width > 0 && child_area.height > 0 {
61                render_child(&child_id, child_area, frame, "");
62            }
63        }
64    }
65
66    /// Natural height = child's natural height + chrome (margin 2 + border 2 = 4).
67    fn natural_height(
68        &self,
69        ctx: &ComponentContext,
70        available_width: u16,
71        measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
72    ) -> Option<u16> {
73        let comp_model = ctx.components.get(&ctx.component_id)?;
74        let child_id = comp_model.child()?;
75        // The child renders inside margin(2) + border(2); give it the reduced width.
76        let inner_width = available_width.saturating_sub(4);
77        let child_h = measure_child(&child_id, "", inner_width)?;
78        Some(child_h.saturating_add(4))
79    }
80}