use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use crate::ui::{gradient, theme};
pub fn centered_rect(w: u16, h: u16, area: Rect) -> Rect {
Rect::new(
area.x + area.width.saturating_sub(w) / 2,
area.y + area.height.saturating_sub(h) / 2,
w.min(area.width),
h.min(area.height),
)
}
#[inline]
pub fn in_bounds(x: u16, y: u16, r: &Rect) -> bool {
x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height
}
#[inline]
pub fn set_cell(buf: &mut Buffer, bounds: &Rect, x: u16, y: u16, ch: char, style: Style) {
if in_bounds(x, y, bounds) {
let cell = &mut buf[(x, y)];
cell.set_char(ch);
cell.set_style(style);
cell.skip = false;
}
}
pub fn write_str(buf: &mut Buffer, bounds: &Rect, x: u16, y: u16, text: &str, style: Style) {
for (i, ch) in text.chars().enumerate() {
set_cell(buf, bounds, x + i as u16, y, ch, style);
}
}
pub fn fill_bg(buf: &mut Buffer, bounds: &Rect, popup: Rect) {
for y in popup.y..popup.y + popup.height {
for x in popup.x..popup.x + popup.width {
if in_bounds(x, y, bounds) {
let cell = &mut buf[(x, y)];
cell.set_char(' ');
cell.set_style(Style::default().bg(theme::BG));
cell.skip = false;
}
}
}
}
pub fn truncate_str(s: &str, max: usize) -> String {
if s.len() > max {
format!("{}…", &s[..max.saturating_sub(1)])
} else {
s.to_string()
}
}
pub fn render_gradient_border(buf: &mut Buffer, bounds: &Rect, area: Rect, phase: f32) {
let x1 = area.x;
let x2 = area.x + area.width - 1;
let y1 = area.y;
let y2 = area.y + area.height - 1;
gradient::walk_perimeter(area, |x, y, i, total| {
let ch = match (x == x1, x == x2, y == y1, y == y2) {
(true, _, true, _) => '╔',
(_, true, true, _) => '╗',
(true, _, _, true) => '╚',
(_, true, _, true) => '╝',
(_, _, true, _) | (_, _, _, true) => '═',
_ => '║',
};
let color = gradient::perimeter_color(
i,
total,
theme::GRADIENT_BORDER_START,
theme::GRADIENT_BORDER_END,
phase,
);
set_cell(
buf,
bounds,
x,
y,
ch,
Style::default().fg(color).bg(theme::BG),
);
});
}
pub fn render_title(buf: &mut Buffer, bounds: &Rect, area: Rect, color: Color, title: &str) {
let border_s = Style::default().fg(color).bg(theme::BG);
let title_s = border_s.add_modifier(Modifier::BOLD);
let bracket_l = area.x + 3;
let title_start = bracket_l + 2;
set_cell(buf, bounds, bracket_l, area.y, '╡', border_s);
set_cell(buf, bounds, bracket_l + 1, area.y, ' ', border_s);
for (i, ch) in title.chars().enumerate() {
let x = title_start + i as u16;
if x >= area.x + area.width - 1 {
break;
}
set_cell(buf, bounds, x, area.y, ch, title_s);
}
let bracket_r_space = title_start + title.len() as u16;
let bracket_r = bracket_r_space + 1;
set_cell(buf, bounds, bracket_r_space, area.y, ' ', border_s);
if bracket_r < area.x + area.width - 1 {
set_cell(buf, bounds, bracket_r, area.y, '╞', border_s);
}
}
pub fn render_hint(buf: &mut Buffer, bounds: &Rect, popup: Rect, hint: &str) {
let hint_row = popup.y + popup.height.saturating_sub(2);
let inner_w = popup.width.saturating_sub(6) as usize;
let left = popup.x + 3;
let hx = left + (inner_w.saturating_sub(hint.chars().count())) as u16 / 2;
write_str(
buf,
bounds,
hx,
hint_row,
hint,
Style::default().fg(theme::DIM).bg(theme::BG),
);
}
pub fn render_popup_chrome(buf: &mut Buffer, bounds: &Rect, popup: Rect, title: &str, phase: f32) {
fill_bg(buf, bounds, popup);
render_gradient_border(buf, bounds, popup, phase);
render_title(buf, bounds, popup, theme::CYAN, title);
}
pub fn history_visibility_description(value: &str) -> &'static str {
match value {
"shared" => "All members see full history",
"invited" => "See history from when invited",
"joined" => "See history from when joined",
"world_readable" => "Anyone can read full history",
_ => "",
}
}