Skip to main content

a2ui_tui/components/
icon.rs

1//! Icon component — maps icon names to Unicode symbols and renders them.
2
3use ratatui::{
4    Frame,
5    layout::Rect,
6    widgets::Paragraph,
7};
8
9use a2ui_base::model::component_context::ComponentContext;
10use a2ui_base::protocol::common_types::DynamicString;
11use crate::component_impl::TuiComponent;
12
13/// Icon component implementation.
14///
15/// Maps common icon names (e.g. "mail", "settings") to Unicode symbols.
16/// If no mapping is found, shows the first 2 characters of the name in brackets.
17/// Rendered as a `Paragraph`.
18/// Applies a default 1-cell margin.
19pub struct IconComponent;
20
21impl TuiComponent for IconComponent {
22    fn name(&self) -> &'static str {
23        "Icon"
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 icon name via DynamicString (handles literals and {path: ...} bindings).
47        let name = match comp_model.get_property::<DynamicString>("name") {
48            Some(ds) => ctx.data_context.resolve_dynamic_string(&ds),
49            None => return,
50        };
51        let symbol = map_icon(&name);
52
53        let paragraph = Paragraph::new(symbol);
54        frame.render_widget(paragraph, inner);
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        // Single glyph content + 2-cell margin.
64        Some(3)
65    }
66}
67
68/// Map an icon name to a Unicode symbol.
69///
70/// If the name is not recognized, returns the first 2 characters of the name
71/// enclosed in brackets.
72fn map_icon(name: &str) -> String {
73    let symbol = match name {
74        "mail" => "✉",
75        "send" => "➤",
76        "search" => "🔍",
77        "settings" => "⚙",
78        "star" => "★",
79        "accountCircle" => "👤",
80        "home" => "🏠",
81        "heart" => "♥",
82        "check" => "✓",
83        "close" => "✕",
84        "add" => "+",
85        "remove" => "−",
86        "edit" => "✎",
87        "delete" => "🗑",
88        "refresh" => "⟳",
89        "arrowBack" => "←",
90        "arrowForward" => "→",
91        "arrowUp" => "↑",
92        "arrowDown" => "↓",
93        "info" => "ℹ",
94        "warning" => "⚠",
95        "error" => "✗",
96        "success" => "✔",
97        _ => return fallback_icon(name),
98    };
99    symbol.to_string()
100}
101
102/// Generate a fallback icon from an unknown name: first 2 chars in brackets.
103fn fallback_icon(name: &str) -> String {
104    let chars: String = name.chars().take(2).collect();
105    format!("[{}]", chars)
106}