Files
soulseek-rs-modded/soulseek-rs/src/ui/panes/searches_pane.rs
T
Michel de Graaf 478d976776 feat(tui): scroll long names in every list and page through popups and chat
The sideways keys the results had (h/l, arrows, 0 and $) now scroll a
transfer's file name and a query too, each list on its own. The browse tree
and the room list take Home/End, PgUp/PgDn and the vim jumps like the main
lists, and a chat log, room or private, pages back through its history and
holds its place while new messages arrive; what arrives meanwhile counts as
unread until the reader returns to the end.
2026-09-07 08:16:20 +00:00

98 lines
3.1 KiB
Rust

use super::name_scroll::{column_width, end_offset, scroll_text};
use crate::models::{SearchEntry, SearchStatus};
use crate::ui::{
HIGHLIGHT_SYMBOL, body_style, dimmed_style, header_style, pane_block,
pane_title, row_highlight_style, success_style, warning_style,
};
use ratatui::{
Frame,
layout::{Constraint, Rect},
widgets::{Cell, HighlightSpacing, Row, Table, TableState},
};
// The pane shares a row with two others, so the fixed columns are as
// narrow as their widest word: the query gets the rest.
const WIDTHS: [Constraint; 3] = [
Constraint::Length(6),
Constraint::Fill(1),
Constraint::Length(7),
];
/// Query is the second column.
const QUERY_COLUMN: usize = 1;
/// The offset that brings the end of a query into view.
#[must_use]
pub fn query_end_offset(query: &str, area: Rect) -> usize {
end_offset(query, area, &WIDTHS, QUERY_COLUMN)
}
pub fn render_searches_pane(
frame: &mut Frame,
area: Rect,
searches: &[SearchEntry],
table_state: &mut TableState,
focused: bool,
query_offset: usize,
) {
let header = Row::new(vec![
Cell::from("Status").style(header_style()),
Cell::from("Query").style(header_style()),
Cell::from("Results").style(header_style()),
])
.height(1);
let query_width = column_width(area, &WIDTHS, QUERY_COLUMN);
let rows: Vec<Row> = searches
.iter()
.map(|search| {
let status_cell = match &search.status {
SearchStatus::Active => {
Cell::from("Active").style(warning_style())
}
SearchStatus::Completed => {
Cell::from("Done").style(success_style())
}
};
// What this window has fetched, or what the session says it will
// find when it does. A search another window ran has a count long
// before this one has pulled the results across.
let results_count = if search.results.is_empty() {
search.known_files
} else {
search.results.len()
};
// Nothing yet from a search still out: the status column already
// says it is looking.
let results_text = if results_count == 0
&& search.status == SearchStatus::Active
{
"…".to_string()
} else {
format!("{results_count}")
};
Row::new(vec![
status_cell,
Cell::from(scroll_text(
&search.query,
query_offset,
query_width,
))
.style(body_style()),
Cell::from(results_text).style(dimmed_style()),
])
})
.collect();
let table = Table::new(rows, WIDTHS)
.header(header)
.row_highlight_style(row_highlight_style())
.highlight_symbol(HIGHLIGHT_SYMBOL)
.highlight_spacing(HighlightSpacing::Always)
.block(pane_block(focused).title(pane_title("1", "Searches", focused)));
frame.render_stateful_widget(table, area, table_state);
}