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::{display_width, i18n::t, AppState, FocusedField, TxMode};
use crate::areas::{update_area, update_cursor_state, 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 inner_width = area.width.saturating_sub(2) as usize;
let (visible_text, cursor_visual_x) = if app.tx_input.is_empty() {
(prompt_text.to_string(), 0)
} else {
let chars: Vec<char> = app.tx_input.chars().collect();
let cursor_pos = app.tx_cursor.min(chars.len());
let text_before_cursor: String = chars[..cursor_pos].iter().collect();
let cursor_x = display_width(&text_before_cursor);
let text_width = display_width(&app.tx_input);
if text_width <= inner_width {
(app.tx_input.clone(), cursor_x)
} else {
let max_scroll = text_width.saturating_sub(inner_width);
let scroll = if cursor_x < inner_width {
0
} else {
cursor_x
.saturating_sub(inner_width.saturating_sub(1))
.min(max_scroll)
};
let mut acc = 0;
let mut start = 0;
for (i, c) in chars.iter().enumerate() {
let w = if c.is_ascii() { 1 } else { 2 };
if acc + w > scroll {
start = i;
break;
}
acc += w;
}
let mut acc = 0;
let mut end = chars.len();
for (i, c) in chars.iter().enumerate().skip(start) {
let w = if c.is_ascii() { 1 } else { 2 };
if acc + w > inner_width {
end = i;
break;
}
acc += w;
}
let visible: String = chars[start..end].iter().collect();
let visual_x = cursor_x.saturating_sub(scroll);
(visible, visual_x)
}
};
let cursor_line = if app.tx_input.is_empty() {
Line::from(Span::styled(
visible_text,
Style::default().fg(Color::DarkGray),
))
} else {
Line::from(Span::styled(
visible_text,
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);
if focused {
let cursor_x = area.x + 1 + cursor_visual_x.min(inner_width) as u16;
let cursor_y = area.y + 2; f.set_cursor_position((cursor_x, cursor_y));
update_cursor_state(cursor_x, cursor_y, true);
} else {
update_cursor_state(0, 0, false);
}
}
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());
}