Implement basic control footer + small tui refactor

This commit is contained in:
Dreaded_X 2025-04-10 17:57:59 +02:00
parent c637bd7427
commit 3d1d9819b6
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4

View File

@ -4,34 +4,19 @@ use futures::StreamExt;
use indexmap::IndexMap; use indexmap::IndexMap;
use ratatui::{ use ratatui::{
Frame, Frame,
layout::{Constraint, Flex, Rect}, layout::{Constraint, Flex, Layout, Rect},
style::{Style, Stylize as _}, style::{Style, Stylize as _},
text::{Line, Span}, text::{Line, Span, Text},
widgets::{Cell, HighlightSpacing, Row, Table, TableState}, widgets::{Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Table, TableState},
}; };
use unicode_width::UnicodeWidthStr;
use crate::tunnel::{self, Tunnel}; use crate::tunnel::{self, Tunnel};
#[derive(Default)]
pub struct Renderer { pub struct Renderer {
table_state: TableState, table_state: TableState,
table_rows: Vec<Vec<Span<'static>>>, table_rows: Vec<Vec<Span<'static>>>,
table_header: Vec<Span<'static>>,
table_widths: Vec<Constraint>,
}
impl Default for Renderer {
fn default() -> Self {
let mut renderer = Self {
table_state: Default::default(),
table_rows: Default::default(),
table_header: tunnel::tui::header(),
table_widths: Default::default(),
};
renderer.update_widths();
renderer
}
} }
impl Renderer { impl Renderer {
@ -46,11 +31,57 @@ impl Renderer {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.await; .await;
self.update_widths();
self.table_state.select(index); self.table_state.select(index);
} }
fn compute_footer_text<'a>(&self, rect: Rect) -> (u16, Paragraph<'a>) {
let width = rect.width as usize - 2;
let commands = if self.table_state.selected().is_some() {
vec![
"(q) quit",
"(↓/j) move down",
"(↑/k) move up",
"(esc) deselect",
"",
"(p) make private",
"(ctrl-p) make protected",
"(shift-p) make public",
]
} else {
vec![
"(q) quit",
"(↓/j) select first",
"(↑/k) select last",
"",
"(p) make all private",
"(ctrl-p) make all protected",
"(shift-p) make all public",
]
};
let mut text = Text::default();
let mut line = Line::default();
let sep = " | ";
for command in commands {
if !command.is_empty() && line.width() == 0 {
line.push_span(command);
} else if !command.is_empty() && line.width() + sep.width() + command.width() <= width {
line.push_span(sep);
line.push_span(command);
} else {
text.push_line(line);
line = Line::from(command);
}
}
text.push_line(line);
let height = text.lines.len() + 2;
let block = Block::bordered().border_type(BorderType::Plain);
(height as u16, Paragraph::new(text).centered().block(block))
}
pub fn render(&mut self, frame: &mut Frame) { pub fn render(&mut self, frame: &mut Frame) {
self.render_title(frame, frame.area()); self.render_title(frame, frame.area());
@ -58,8 +89,13 @@ impl Renderer {
horizontal: 1, horizontal: 1,
vertical: 1, vertical: 1,
}); });
let (footer_height, footer) = self.compute_footer_text(area);
self.render_table(frame, area); let layout = Layout::vertical([Constraint::Min(5), Constraint::Length(footer_height)]);
let chunks = layout.split(area);
self.render_table(frame, chunks[0]);
frame.render_widget(footer, chunks[1]);
} }
pub fn render_title(&self, frame: &mut Frame, rect: Rect) { pub fn render_title(&self, frame: &mut Frame, rect: Rect) {
@ -73,11 +109,12 @@ impl Renderer {
frame.render_widget(title, rect); frame.render_widget(title, rect);
} }
fn update_widths(&mut self) { fn compute_widths(&mut self) -> Vec<Constraint> {
self.table_widths = std::iter::once(&self.table_header) let table_header = tunnel::tui::header();
std::iter::once(&table_header)
.chain(&self.table_rows) .chain(&self.table_rows)
.map(|row| row.iter().map(|cell| cell.width() as u16)) .map(|row| row.iter().map(|cell| cell.width() as u16))
.fold(vec![0; self.table_header.len()], |acc, row| { .fold(vec![0; table_header.len()], |acc, row| {
acc.into_iter() acc.into_iter()
.zip(row) .zip(row)
.map(|v| cmp::max(v.0, v.1)) .map(|v| cmp::max(v.0, v.1))
@ -85,7 +122,7 @@ impl Renderer {
}) })
.into_iter() .into_iter()
.map(|c| Constraint::Length(c + 1)) .map(|c| Constraint::Length(c + 1))
.collect(); .collect()
} }
pub fn render_table(&mut self, frame: &mut Frame<'_>, rect: Rect) { pub fn render_table(&mut self, frame: &mut Frame<'_>, rect: Rect) {
@ -102,8 +139,7 @@ impl Renderer {
.height(1) .height(1)
}); });
let header = self let header = tunnel::tui::header()
.table_header
.iter() .iter()
.cloned() .cloned()
.map(Cell::from) .map(Cell::from)
@ -116,7 +152,7 @@ impl Renderer {
.rows(rows) .rows(rows)
.flex(Flex::Start) .flex(Flex::Start)
.column_spacing(3) .column_spacing(3)
.widths(&self.table_widths) .widths(self.compute_widths())
.row_highlight_style(highlight_style) .row_highlight_style(highlight_style)
.highlight_symbol(Line::from("> ")) .highlight_symbol(Line::from("> "))
.highlight_spacing(HighlightSpacing::Always); .highlight_spacing(HighlightSpacing::Always);