Skip to main content

a2ui_tui/components/
checkbox.rs

1//! CheckBox component — renders a labeled checkbox with a checked/unchecked indicator.
2
3use ratatui::{
4    Frame,
5    layout::Rect,
6    style::{Color, Modifier, Style},
7    widgets::Paragraph,
8};
9
10use a2ui_base::model::component_context::ComponentContext;
11use a2ui_base::protocol::common_types::{DynamicBoolean, DynamicString};
12use crate::component_impl::TuiComponent;
13
14/// CheckBox component implementation.
15///
16/// Displays `[☑] Label text` or `[☐] Label text` based on the resolved
17/// boolean value. Rendered as a `Paragraph`.
18/// Applies a default 1-cell margin.
19pub struct CheckBoxComponent;
20
21impl TuiComponent for CheckBoxComponent {
22    fn name(&self) -> &'static str {
23        "CheckBox"
24    }
25
26    fn render(
27        &self,
28        ctx: &ComponentContext,
29        area: Rect,
30        frame: &mut Frame,
31        _render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
32        _measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
33    ) {
34        let comp_model = match ctx.components.get(&ctx.component_id) {
35            Some(m) => m,
36            None => return,
37        };
38
39        // Apply default 1-cell margin on all sides (never collapses to zero).
40        let inner = crate::layout_engine::padded_content(area);
41
42        if inner.width == 0 || inner.height == 0 {
43            return;
44        }
45
46        // Resolve the label text.
47        let label = match comp_model.get_property::<DynamicString>("label") {
48            Some(ds) => ctx.data_context.resolve_dynamic_string(&ds),
49            None => String::new(),
50        };
51
52        // Resolve the checked state.
53        let checked = match comp_model.get_property::<DynamicBoolean>("value") {
54            Some(db) => ctx.data_context.resolve_dynamic_boolean(&db),
55            None => false,
56        };
57
58        // Format the display: [☑] Label or [☐] Label.
59        let indicator = if checked { "☑" } else { "☐" };
60        let display_text = format!("[{}] {}", indicator, label);
61
62        // Determine if this checkbox has keyboard focus.
63        let is_focused = ctx.focused_id.as_deref() == Some(ctx.component_id.as_str());
64        let style = if is_focused {
65            Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
66        } else {
67            Style::default()
68        };
69
70        let paragraph = Paragraph::new(display_text).style(style);
71        frame.render_widget(paragraph, inner);
72    }
73
74    fn natural_height(
75        &self,
76        _ctx: &ComponentContext,
77        _available_width: u16,
78        _measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
79    ) -> Option<u16> {
80        // Single-line `[☑] label` content + 2-cell margin.
81        Some(3)
82    }
83
84    fn handle_event(
85        &self,
86        ctx: &ComponentContext,
87        event: &a2ui_base::event::InputEvent,
88    ) -> Option<a2ui_base::event::EventResult> {
89        a2ui_base::components::checkbox::handle_event(ctx, event)
90    }
91}