use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
Frame,
};
use tuiserial_core::{i18n::t, AppState, FocusedField, TxMode};
use crate::areas::{update_area, UiAreaField};
pub fn draw_tx_area(f: &mut Frame, app: &AppState, area: Rect) {
update_area(UiAreaField::TxArea, area);
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(30), Constraint::Length(12)])
.split(area);
draw_tx_input(f, app, chunks[0]);
draw_append_selector(f, app, chunks[1]);
}
fn draw_tx_input(f: &mut Frame, app: &AppState, area: Rect) {
let focused = app.focused_field == FocusedField::TxInput;
let mode_str = match app.tx_mode {
TxMode::Hex => t("tx.hex", app.language),
TxMode::Ascii => t("tx.ascii", app.language),
};
let mode_icon = match app.tx_mode {
TxMode::Hex => "🔢",
TxMode::Ascii => "📝",
};
let title = if focused {
format!(
" {} {} - {} [↑↓ {} | Enter {} | Esc {}] ",
mode_icon,
t("label.send", app.language),
mode_str,
t("hint.toggle", app.language),
t("button.send", app.language),
t("hint.clear", app.language)
)
} else {
format!(
" {} {} - {} ",
mode_icon,
t("label.send", app.language),
mode_str
)
};
let style = if focused {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let prompt_text = t("label.input_prompt", app.language);
let cursor_line = if app.tx_input.is_empty() {
if focused {
Line::from(vec![
Span::styled("|", Style::default().fg(Color::Yellow)),
Span::styled(
format!(" {}", prompt_text),
Style::default().fg(Color::DarkGray),
),
])
} else {
Line::from(Span::styled(
prompt_text,
Style::default().fg(Color::DarkGray),
))
}
} else {
if focused {
let chars: Vec<char> = app.tx_input.chars().collect();
let cursor_pos = app.tx_cursor.min(chars.len());
let before_cursor: String = chars[..cursor_pos].iter().collect();
let after_cursor: String = chars[cursor_pos..].iter().collect();
Line::from(vec![
Span::styled(before_cursor, Style::default().fg(Color::White)),
Span::styled("|", Style::default().fg(Color::Yellow)),
Span::styled(after_cursor, Style::default().fg(Color::White)),
])
} else {
Line::from(Span::styled(
app.tx_input.clone(),
Style::default().fg(Color::White),
))
}
};
let help_text = match app.tx_mode {
TxMode::Hex => {
if app.language == tuiserial_core::Language::Chinese {
"HEX: 按空格分隔字节 (例: 48 65 6C 6C 6F)"
} else {
"HEX: Space-separated bytes (e.g., 48 65 6C 6C 6F)"
}
}
TxMode::Ascii => {
if app.language == tuiserial_core::Language::Chinese {
"ASCII: 直接输入文本内容"
} else {
"ASCII: Enter text directly"
}
}
};
let text = vec![
Line::from(""),
cursor_line,
Line::from(""),
Line::from(Span::styled(
help_text,
Style::default().fg(Color::DarkGray),
)),
];
let para = Paragraph::new(text)
.block(
Block::default()
.borders(Borders::ALL)
.title(title)
.border_style(style),
)
.wrap(Wrap { trim: false });
f.render_widget(para, area);
}
fn draw_append_selector(f: &mut Frame, app: &AppState, area: Rect) {
let items: Vec<ListItem> = app
.append_mode_options
.iter()
.map(|mode| {
let display = mode.name(app.language);
ListItem::new(display)
})
.collect();
let list = List::new(items)
.block(
Block::default()
.borders(Borders::ALL)
.title(format!(" {} ", t("label.append_mode", app.language)))
.title_alignment(Alignment::Left),
)
.highlight_style(
Style::default()
.bg(Color::DarkGray)
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol("> ");
f.render_stateful_widget(list, area, &mut app.append_mode_state.clone());
}