use ratatui::{
Frame,
layout::Rect,
style::{Color, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
};
use a2ui_base::model::component_context::ComponentContext;
use a2ui_base::protocol::common_types::DynamicString;
use crate::component_impl::TuiComponent;
pub struct TextFieldComponent;
impl TuiComponent for TextFieldComponent {
fn name(&self) -> &'static str {
"TextField"
}
fn render(
&self,
ctx: &ComponentContext,
area: Rect,
frame: &mut Frame,
_render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
_measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
) {
let comp_model = match ctx.components.get(&ctx.component_id) {
Some(m) => m,
None => return,
};
let inner = crate::layout_engine::padded_content(area);
if inner.width == 0 || inner.height == 0 {
return;
}
let label = match comp_model.get_property::<DynamicString>("label") {
Some(ds) => ctx.data_context.resolve_dynamic_string(&ds),
None => String::new(),
};
let raw_value = match comp_model.get_property::<DynamicString>("value") {
Some(ds) => ctx.data_context.resolve_dynamic_string(&ds),
None => String::new(),
};
let variant: Option<String> = comp_model.get_property("variant");
let display_value = match variant.as_deref() {
Some("obscured") => obscure_value(&raw_value),
_ => raw_value.clone(),
};
let placeholder = comp_model
.get_property::<DynamicString>("placeholder")
.map(|ds| ctx.data_context.resolve_dynamic_string(&ds));
let (display_text, is_placeholder) = if raw_value.is_empty() {
match &placeholder {
Some(p) if !p.is_empty() => (p.clone(), true),
_ => ("\u{2588}".to_string(), false), }
} else {
(format!("{}\u{2588}", display_value), false)
};
let is_focused = ctx.focused_id.as_deref() == Some(ctx.component_id.as_str());
let block_style = if is_focused {
Style::default().fg(Color::Yellow)
} else {
Style::default()
};
let block = Block::default()
.borders(Borders::ALL)
.title(label)
.style(block_style);
let content_area = block.inner(inner);
frame.render_widget(block, inner);
if content_area.width == 0 || content_area.height == 0 {
return;
}
let paragraph_style = if is_placeholder {
Style::default().fg(Color::DarkGray)
} else {
Style::default()
};
let paragraph = Paragraph::new(Line::from(Span::styled(display_text, paragraph_style)));
frame.render_widget(paragraph, content_area);
}
fn natural_height(
&self,
_ctx: &ComponentContext,
_available_width: u16,
_measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
) -> Option<u16> {
Some(5)
}
fn handle_event(
&self,
ctx: &ComponentContext,
event: &a2ui_base::event::InputEvent,
) -> Option<a2ui_base::event::EventResult> {
a2ui_base::components::text_field::handle_event(ctx, event)
}
}
fn obscure_value(value: &str) -> String {
value.chars().map(|_| '\u{2022}').collect()
}