use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color as RatatuiColor, Modifier};
use crate::types::{Color, Decoration, StyledText};
mod activity_bar;
pub mod backend;
mod board;
mod braille;
mod button;
mod canvas;
pub mod caps;
mod chart;
pub mod color;
mod combo_box;
mod command_center;
mod command_line;
mod completions;
mod context_menu;
mod data_table;
mod date_picker;
mod dialog;
mod diff_view;
mod drop_overlay;
mod editor;
pub mod events;
mod find_replace;
mod float;
mod focus_ring;
mod form;
mod image;
mod list;
mod menu_bar;
mod message_list;
mod minimap;
mod multi_section_view;
mod number_input;
mod palette;
mod panel;
mod pipeline_view;
mod progress;
mod radio_group;
mod rich_text_popup;
mod run;
mod scrollbar;
pub mod services;
pub mod shell_runner;
mod sidebar_panel;
mod solid_fill;
mod spinner;
mod split;
mod split_junction;
mod split_tree;
mod status_bar;
mod switch;
mod tab_bar;
mod terminal;
pub mod testing;
pub mod text;
mod text_display;
mod text_input;
mod toast;
mod toolbar;
mod tooltip;
mod tree;
#[cfg(feature = "terminal")]
pub mod vt_testing;
pub use activity_bar::{draw_activity_bar, draw_activity_bar_with_style};
pub use backend::TuiBackend;
pub use board::{draw_board, tui_board_layout};
pub use button::{draw_button, tui_button_layout};
pub use canvas::{draw_canvas, tui_canvas_layout};
pub use chart::{draw_chart, tui_chart_layout};
pub use combo_box::draw_combo_box;
pub use command_center::{draw_command_center, tui_command_center_layout};
pub use completions::draw_completions;
pub use context_menu::{
draw_context_menu, draw_context_menu_with_glyphs, draw_context_menu_with_submenus,
BORDER_INSET as CONTEXT_MENU_BORDER_INSET,
};
pub use data_table::{data_table_layout, draw_data_table, draw_data_table_with_glyphs};
pub use date_picker::draw_date_picker;
pub use dialog::{draw_dialog, tui_dialog_layout};
pub use diff_view::draw_diff_view;
pub use drop_overlay::draw_drop_overlay;
pub use editor::{draw_editor, draw_editor_with_reserve, EditorPaintResult};
pub use find_replace::draw_find_replace;
pub use float::draw_float;
pub use focus_ring::draw_focus_ring;
pub use form::{draw_form, draw_settings_chrome, tui_form_layout};
pub use image::draw_image;
pub use list::{draw_list, tui_list_layout};
pub use menu_bar::{draw_menu_bar, tui_menu_bar_layout};
pub use message_list::draw_message_list;
pub use minimap::{
draw_minimap, draw_minimap_with_scale, draw_minimap_with_sizing, tui_minimap_layout,
tui_minimap_layout_with_sizing, COLS_PER_CELL, LINES_PER_ROW,
};
pub use multi_section_view::{draw_multi_section_view, tui_msv_layout};
pub use number_input::draw_number_input;
pub(crate) use palette::draw_palette_with_style;
pub use palette::{draw_palette, tui_palette_layout};
pub use panel::{draw_panel, tui_panel_layout};
pub use pipeline_view::{draw_pipeline_view, tui_pipeline_view_layout};
pub use progress::{draw_progress, tui_progress_layout};
pub use radio_group::{draw_radio_group, tui_radio_group_layout};
pub use rich_text_popup::draw_rich_text_popup;
pub use run::{run, run_with, RunConfig, StepOutcome, TuiRunner};
pub use scrollbar::draw_scrollbar;
pub use services::TuiPlatformServices;
pub use sidebar_panel::{draw_sidebar_panel, tui_sidebar_panel_layout};
pub use solid_fill::draw_solid_fill;
pub use spinner::{draw_spinner, tui_spinner_layout};
pub use split::{draw_split, tui_split_layout};
pub use split_tree::{draw_split_tree, tui_split_tree_layout};
pub use status_bar::draw_status_bar;
pub use switch::{draw_switch, tui_switch_layout};
pub use tab_bar::{
draw_tab_bar, draw_tab_bar_icons, draw_tab_bar_icons_with_chrome,
draw_tab_bar_icons_with_chrome_and_glyphs, draw_tab_bar_with_chrome, TAB_CLOSE_CHAR,
TAB_CLOSE_COLS, TAB_SEPARATOR_CHAR, TAB_SEPARATOR_COLS,
};
pub use terminal::{draw_terminal, draw_terminal_divider};
pub use text::{char_cell_width, display_width, truncate_to_width, truncate_to_width_ellipsis};
pub use text_display::{draw_text_display, tui_text_display_layout};
pub use text_input::{draw_text_input, tui_text_input_layout};
pub use toast::{draw_toast_stack, tui_toast_stack_layout};
pub use toolbar::{draw_toolbar, draw_toolbar_with_options, tui_toolbar_layout};
pub use tooltip::{
draw_tooltip, draw_tooltip_with_chrome, painted_bounds as tooltip_painted_bounds,
};
pub use tree::{draw_tree, tui_tree_layout};
pub fn ratatui_color(c: Color) -> RatatuiColor {
RatatuiColor::Rgb(c.r, c.g, c.b)
}
fn set_cell(buf: &mut Buffer, x: u16, y: u16, ch: char, fg: RatatuiColor, bg: RatatuiColor) {
let area = buf.area;
if x >= area.x && y >= area.y && x < area.x + area.width && y < area.y + area.height {
let ch = if ch.is_ascii_control() { ' ' } else { ch };
let cell = &mut buf[(x, y)];
cell.set_char(ch).set_fg(fg).set_bg(bg);
cell.modifier = Modifier::empty();
cell.underline_color = RatatuiColor::Reset;
}
}
fn set_cell_wide(buf: &mut Buffer, x: u16, y: u16, ch: char, fg: RatatuiColor, bg: RatatuiColor) {
let area = buf.area;
if x >= area.x && y >= area.y && x < area.x + area.width && y < area.y + area.height {
let mut s = String::with_capacity(4);
s.push(ch);
let cell = &mut buf[(x, y)];
cell.set_symbol(&s).set_fg(fg).set_bg(bg);
cell.modifier = Modifier::empty();
cell.underline_color = RatatuiColor::Reset;
if x + 1 < area.x + area.width {
let cont = &mut buf[(x + 1, y)];
cont.set_symbol("").set_fg(fg).set_bg(bg);
cont.modifier = Modifier::empty();
cont.underline_color = RatatuiColor::Reset;
}
}
}
fn qc(c: Color) -> RatatuiColor {
ratatui_color(c)
}
#[allow(clippy::too_many_arguments)]
fn draw_styled_text(
buf: &mut Buffer,
area: Rect,
y: u16,
start_col: usize,
text: &StyledText,
default_fg: RatatuiColor,
bg: RatatuiColor,
decoration: Decoration,
dim_fg: RatatuiColor,
) -> usize {
let mut col = start_col;
for span in &text.spans {
let span_fg = if let Some(c) = span.fg {
qc(c)
} else if matches!(decoration, Decoration::Muted) {
dim_fg
} else {
default_fg
};
let span_bg = span.bg.map(qc).unwrap_or(bg);
for ch in span.text.chars() {
if col >= area.width as usize {
return col;
}
let w = char_cell_width(ch) as usize;
if w == 2 && col + 1 < area.width as usize {
set_cell_wide(buf, area.x + col as u16, y, ch, span_fg, span_bg);
} else {
set_cell(buf, area.x + col as u16, y, ch, span_fg, span_bg);
}
col += w;
}
}
col
}
#[allow(clippy::too_many_arguments)]
fn set_cell_styled(
buf: &mut Buffer,
x: u16,
y: u16,
ch: char,
fg: RatatuiColor,
bg: RatatuiColor,
modifier: Modifier,
underline_color: Option<RatatuiColor>,
) {
let area = buf.area;
if x >= area.x && y >= area.y && x < area.x + area.width && y < area.y + area.height {
let ch = if ch.is_ascii_control() { ' ' } else { ch };
let cell = &mut buf[(x, y)];
cell.set_char(ch).set_fg(fg).set_bg(bg);
cell.modifier = modifier;
cell.underline_color = underline_color.unwrap_or(RatatuiColor::Reset);
}
}
#[allow(clippy::too_many_arguments)]
fn set_cell_wide_styled(
buf: &mut Buffer,
x: u16,
y: u16,
ch: char,
fg: RatatuiColor,
bg: RatatuiColor,
modifier: Modifier,
underline_color: Option<RatatuiColor>,
) {
let area = buf.area;
if x >= area.x && y >= area.y && x < area.x + area.width && y < area.y + area.height {
let mut s = String::with_capacity(4);
s.push(ch);
let cell = &mut buf[(x, y)];
cell.set_symbol(&s).set_fg(fg).set_bg(bg);
cell.modifier = modifier;
cell.underline_color = underline_color.unwrap_or(RatatuiColor::Reset);
if x + 1 < area.x + area.width {
let cont = &mut buf[(x + 1, y)];
cont.set_symbol("").set_fg(fg).set_bg(bg);
cont.modifier = modifier;
cont.underline_color = underline_color.unwrap_or(RatatuiColor::Reset);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_cell_guards_left_and_top_edge() {
let mut buf = Buffer::empty(Rect::new(5, 5, 10, 10));
set_cell(
&mut buf,
0,
0,
'x',
RatatuiColor::White,
RatatuiColor::Black,
);
set_cell(
&mut buf,
2,
8,
'x',
RatatuiColor::White,
RatatuiColor::Black,
);
set_cell(
&mut buf,
8,
2,
'x',
RatatuiColor::White,
RatatuiColor::Black,
);
set_cell(
&mut buf,
6,
6,
'y',
RatatuiColor::White,
RatatuiColor::Black,
);
assert_eq!(buf[(6, 6)].symbol(), "y");
}
#[test]
fn set_cell_wide_guards_left_and_top_edge() {
let mut buf = Buffer::empty(Rect::new(5, 5, 10, 10));
set_cell_wide(
&mut buf,
0,
0,
'中',
RatatuiColor::White,
RatatuiColor::Black,
);
set_cell_wide(
&mut buf,
2,
8,
'中',
RatatuiColor::White,
RatatuiColor::Black,
);
set_cell_wide(
&mut buf,
8,
2,
'中',
RatatuiColor::White,
RatatuiColor::Black,
);
set_cell_wide(
&mut buf,
6,
6,
'中',
RatatuiColor::White,
RatatuiColor::Black,
);
assert_eq!(buf[(6, 6)].symbol(), "中");
}
#[test]
fn set_cell_styled_guards_left_and_top_edge() {
let mut buf = Buffer::empty(Rect::new(5, 5, 10, 10));
set_cell_styled(
&mut buf,
0,
0,
'x',
RatatuiColor::White,
RatatuiColor::Black,
Modifier::empty(),
None,
);
set_cell_styled(
&mut buf,
6,
6,
'y',
RatatuiColor::White,
RatatuiColor::Black,
Modifier::empty(),
None,
);
assert_eq!(buf[(6, 6)].symbol(), "y");
}
#[test]
fn set_cell_wide_styled_guards_left_and_top_edge() {
let mut buf = Buffer::empty(Rect::new(5, 5, 10, 10));
set_cell_wide_styled(
&mut buf,
0,
0,
'中',
RatatuiColor::White,
RatatuiColor::Black,
Modifier::empty(),
None,
);
set_cell_wide_styled(
&mut buf,
6,
6,
'中',
RatatuiColor::White,
RatatuiColor::Black,
Modifier::empty(),
None,
);
assert_eq!(buf[(6, 6)].symbol(), "中");
}
#[test]
fn draw_styled_text_strides_by_display_width_for_cjk() {
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
let area = Rect::new(0, 0, 20, 3);
let text = StyledText::plain("日本語");
assert_eq!(text.visible_width(), 6, "CJK: 3 chars, 2 cols each");
let end_col = draw_styled_text(
&mut buf,
area,
0,
1,
&text,
RatatuiColor::White,
RatatuiColor::Black,
Decoration::Normal,
RatatuiColor::Gray,
);
assert_eq!(end_col, 1 + text.visible_width());
assert_eq!(buf[(1, 0)].symbol(), "日");
assert_eq!(buf[(2, 0)].symbol(), "");
assert_eq!(buf[(3, 0)].symbol(), "本");
assert_eq!(buf[(4, 0)].symbol(), "");
assert_eq!(buf[(5, 0)].symbol(), "語");
assert_eq!(buf[(6, 0)].symbol(), "");
assert_eq!(buf[(7, 0)].symbol(), " ");
}
}