diff options
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/tui/app.rs | 27 | ||||
| -rw-r--r-- | src/tui/ui.rs | 42 |
3 files changed, 57 insertions, 14 deletions
diff --git a/src/main.rs b/src/main.rs index d955dc2..f66ce13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -147,6 +147,8 @@ async fn run( (KeyModifiers::NONE | KeyModifiers::SHIFT, KeyCode::Char(c)) => { app.input_insert(c); } + (KeyModifiers::SHIFT, KeyCode::Up) => app.members_scroll_up(), + (KeyModifiers::SHIFT, KeyCode::Down) => app.members_scroll_down(), (_, KeyCode::Backspace) => app.input_backspace(), (_, KeyCode::Left) => app.cursor_left(), (_, KeyCode::Right) => app.cursor_right(), diff --git a/src/tui/app.rs b/src/tui/app.rs index 879d762..52fc85a 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,3 +1,5 @@ +use ratatui::widgets::{ListState, ScrollbarState}; + /// A single chat message in the log #[derive(Clone)] pub struct ChatLine { @@ -15,7 +17,9 @@ pub struct AppState { pub members: Vec<String>, pub input: String, pub cursor: usize, - pub scroll_offset: usize, + pub chat_scroll: usize, + pub members_scroll: ScrollbarState, + pub members_list_state: ListState, pub status: String, pub connected: bool, } @@ -29,7 +33,9 @@ impl AppState { members: Vec::new(), input: String::new(), cursor: 0, - scroll_offset: 0, + chat_scroll: 0, + members_scroll: ScrollbarState::new(0), + members_list_state: ListState::default(), status: "Set nick with /nick to connect.".into(), connected: false, } @@ -88,11 +94,24 @@ impl AppState { } pub fn scroll_up(&mut self) { - self.scroll_offset += 1; + self.chat_scroll = self.chat_scroll.saturating_add(1); } pub fn scroll_down(&mut self) { - self.scroll_offset = self.scroll_offset.saturating_sub(1); + self.chat_scroll = self.chat_scroll.saturating_sub(1); + } + + pub fn members_scroll_up(&mut self) { + let pos = self.members_scroll.get_position().saturating_sub(5); + self.members_scroll = self.members_scroll.position(pos); + *self.members_list_state.offset_mut() = pos; + } + + pub fn members_scroll_down(&mut self) { + let max = self.members.len().saturating_sub(1); + let pos = (self.members_scroll.get_position().saturating_add(5)).min(max); + self.members_scroll = self.members_scroll.position(pos); + *self.members_list_state.offset_mut() = pos; } pub fn take_input(&mut self) -> String { diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 0fc1ec9..05b3c9f 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -4,7 +4,10 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span, Text}, - widgets::{Block, BorderType, Borders, List, ListItem, Paragraph, Wrap}, + widgets::{ + Block, BorderType, Borders, List, ListItem, Paragraph, Scrollbar, ScrollbarOrientation, + Wrap, + }, }; use unicode_width::UnicodeWidthStr; @@ -113,8 +116,8 @@ fn draw_chat_log(f: &mut Frame, area: Rect, state: &mut AppState) { let max_offset = base_scroll as usize; // Clamp the offset and write it back so app.rs stays in sync - state.scroll_offset = state.scroll_offset.clamp(0, max_offset); - let final_scroll = (base_scroll as i32 - state.scroll_offset as i32) as u16; + state.chat_scroll = state.chat_scroll.clamp(0, max_offset); + let final_scroll = (base_scroll as i32 - state.chat_scroll as i32) as u16; f.render_widget( Paragraph::new(Text::from(padded_lines)) @@ -129,6 +132,7 @@ fn count_wrapped_lines(lines: &[Line], width: usize) -> usize { if width == 0 { return lines.len(); } + lines .iter() .map(|line| { @@ -146,13 +150,20 @@ fn count_wrapped_lines(lines: &[Line], width: usize) -> usize { let mut current_width = 0; for word in full_text.split_inclusive(' ') { - let word_width = UnicodeWidthStr::width(word); - if current_width > 0 && current_width + word_width > width { - row_count += 1; - current_width = word_width; - } else { + let mut word_width = UnicodeWidthStr::width(word); + if current_width + word_width <= width { current_width += word_width; + continue; } + + row_count += 1; + + if word_width >= width { + row_count += word_width / width; + word_width %= width; + } + + current_width = word_width; } row_count }) @@ -238,7 +249,7 @@ fn draw_input(f: &mut Frame, area: Rect, state: &AppState) { ); } -fn draw_members_panel(f: &mut Frame, area: Rect, state: &AppState) { +fn draw_members_panel(f: &mut Frame, area: Rect, state: &mut AppState) { let items: Vec<ListItem> = state .members .iter() @@ -273,7 +284,18 @@ fn draw_members_panel(f: &mut Frame, area: Rect, state: &AppState) { let title = format!(" users ({}) ", state.members.len()); let block = panel_block(&title); - f.render_widget(List::new(items).block(block), area); + state.members_scroll = state.members_scroll.content_length(state.members.len()); + + f.render_stateful_widget( + List::new(items).block(block), + area, + &mut state.members_list_state, + ); + f.render_stateful_widget( + Scrollbar::new(ScrollbarOrientation::VerticalRight), + area, + &mut state.members_scroll, + ); } fn draw_statusbar(f: &mut Frame, area: Rect, state: &AppState) { |
