use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use super::{qc, set_cell};
use crate::primitives::text_input::{TextInput, TextInputLayout, TextInputMeasure};
use crate::theme::Theme;
pub fn tui_text_input_layout(ti: &TextInput, rect: Rect) -> TextInputLayout {
let r = crate::event::Rect::new(
rect.x as f32,
rect.y as f32,
rect.width as f32,
rect.height as f32,
);
ti.layout(r, TextInputMeasure::new(1.0, 1.0))
}
pub fn draw_text_input(
buf: &mut Buffer,
rect: Rect,
ti: &TextInput,
theme: &Theme,
) -> TextInputLayout {
let layout = tui_text_input_layout(ti, rect);
if rect.width == 0 || rect.height == 0 {
return layout;
}
let bg = qc(theme.background);
let fg = qc(theme.foreground);
let muted = qc(theme.muted_fg);
let border_fg = if ti.has_focus {
qc(theme.accent_fg)
} else {
qc(theme.border_fg)
};
let x0 = rect.x;
let y0 = rect.y;
let x1 = rect.x + rect.width.saturating_sub(1);
let y1 = rect.y + rect.height.saturating_sub(1);
for y in y0..=y1 {
for x in x0..=x1 {
set_cell(buf, x, y, ' ', fg, bg);
}
}
for x in x0..=x1 {
set_cell(buf, x, y0, '─', border_fg, bg);
if y1 != y0 {
set_cell(buf, x, y1, '─', border_fg, bg);
}
}
for y in y0..=y1 {
set_cell(buf, x0, y, '│', border_fg, bg);
if x1 != x0 {
set_cell(buf, x1, y, '│', border_fg, bg);
}
}
set_cell(buf, x0, y0, '┌', border_fg, bg);
set_cell(buf, x1, y0, '┐', border_fg, bg);
set_cell(buf, x0, y1, '└', border_fg, bg);
set_cell(buf, x1, y1, '┘', border_fg, bg);
if layout.placeholder_active {
if let Some(text) = ti.placeholder.as_ref() {
if let Some(first) = layout.visible_lines.first() {
let row_x = first.bounds.x as u16;
let row_y = first.bounds.y as u16;
let max_w = first.bounds.width as u16;
for (x, ch) in (row_x..).zip(text.chars().take(max_w as usize)) {
set_cell(buf, x, row_y, ch, muted, bg);
}
}
}
} else {
let h_scroll = layout.resolved_scroll_col;
for vis in &layout.visible_lines {
let line = ti.lines.get(vis.line_idx).map(String::as_str).unwrap_or("");
let row_x = vis.bounds.x as u16;
let row_y = vis.bounds.y as u16;
let max_w = vis.bounds.width as u16;
for (x, ch) in (row_x..).zip(line.chars().skip(h_scroll).take(max_w as usize)) {
set_cell(buf, x, row_y, ch, fg, bg);
}
}
}
if ti.has_focus {
if let Some(cb) = layout.cursor_bounds {
let cx = cb.x as u16;
let cy = cb.y as u16;
let area = buf.area;
if cx < area.x + area.width && cy < area.y + area.height {
let cell = &mut buf[(cx, cy)];
let old_fg = cell.fg;
let old_bg = cell.bg;
cell.set_fg(old_bg).set_bg(old_fg);
}
}
}
layout
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::Point;
use crate::primitives::text_input::TextInputHit;
use crate::types::WidgetId;
fn cell_char(buf: &Buffer, x: u16, y: u16) -> char {
buf[(x, y)].symbol().chars().next().unwrap_or(' ')
}
fn mk_input(lines: Vec<&str>) -> TextInput {
let mut ti = TextInput::new(WidgetId::new("ti"));
ti.lines = lines.into_iter().map(String::from).collect();
ti.has_focus = true;
ti
}
fn paint_and_click_round_trip_at(origin_x: u16, origin_y: u16) {
let rect = Rect::new(origin_x, origin_y, 10, 4);
let mut buf = Buffer::empty(rect);
let ti = mk_input(vec!["hello"]);
let layout = draw_text_input(&mut buf, rect, &ti, &Theme::default());
assert_eq!(cell_char(&buf, origin_x, origin_y), '┌');
let content_x = origin_x + 2;
let content_y = origin_y + 1;
assert_eq!(cell_char(&buf, content_x, content_y), 'h');
let p = Point::new(content_x as f32 + 0.5, content_y as f32 + 0.5);
let hit = layout
.hit_regions
.iter()
.find(|(r, _)| r.contains(p))
.map(|(_, h)| h.clone());
assert_eq!(hit, Some(TextInputHit::Line { line_idx: 0 }));
}
#[test]
fn paint_and_click_round_trip() {
paint_and_click_round_trip_at(0, 0);
}
#[test]
fn paint_and_click_round_trip_at_nonzero_origin() {
paint_and_click_round_trip_at(7, 13);
}
}