From f3b804347d148c2e0cec109631e89d854b754b32 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 1 Aug 2022 01:41:43 +0200 Subject: [PATCH] Make room list behave more like chat Accomplished by adding the same cursor movement and scrolling key bindings, as well as moving the cursor so it is visible when scrolling. --- src/ui/rooms.rs | 14 ++++++--- src/ui/widgets/list.rs | 67 +++++++++++++++++++++++++++++++++++------- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 54cafe7..910f87e 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::iter; use std::sync::Arc; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::style::{ContentStyle, Stylize}; use parking_lot::FairMutex; use tokio::sync::mpsc; @@ -213,10 +213,16 @@ impl Rooms { self.state = State::ShowRoom(name); } } - KeyCode::Char('j') | KeyCode::Down => self.list.move_cursor_down(), KeyCode::Char('k') | KeyCode::Up => self.list.move_cursor_up(), - KeyCode::Char('J') => self.list.scroll_down(), // TODO Replace by Ctrl+E and mouse scroll - KeyCode::Char('K') => self.list.scroll_up(), // TODO Replace by Ctrl+Y and mouse scroll + KeyCode::Char('j') | KeyCode::Down => self.list.move_cursor_down(), + KeyCode::Char('g') | KeyCode::Home => self.list.move_cursor_to_top(), + KeyCode::Char('G') | KeyCode::End => self.list.move_cursor_to_bottom(), + KeyCode::Char('y') if event.modifiers == KeyModifiers::CONTROL => { + self.list.scroll_up(1) + } + KeyCode::Char('e') if event.modifiers == KeyModifiers::CONTROL => { + self.list.scroll_down(1) + } KeyCode::Char('c') => { if let Some(name) = self.list.cursor() { self.get_or_insert_room(name).connect(); diff --git a/src/ui/widgets/list.rs b/src/ui/widgets/list.rs index e38f1ec..0935332 100644 --- a/src/ui/widgets/list.rs +++ b/src/ui/widgets/list.rs @@ -58,6 +58,14 @@ impl InnerListState { .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) } + fn last_selectable(&self) -> Option> { + self.rows + .iter() + .enumerate() + .rev() + .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) + } + fn selectable_at_or_before_index(&self, i: usize) -> Option> { self.rows .iter() @@ -92,7 +100,7 @@ impl InnerListState { .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) } - fn make_cursor_visible(&mut self, height: usize) { + fn scroll_so_cursor_is_visible(&mut self, height: usize) { if height == 0 { // Cursor can't be visible because nothing is visible return; @@ -106,6 +114,25 @@ impl InnerListState { } } + fn move_cursor_to_make_it_visible(&mut self, height: usize) { + if let Some(cursor) = &self.cursor { + let min_idx = self.offset; + let max_idx = self.offset.saturating_add(height).saturating_sub(1); + + let new_cursor = if cursor.idx < min_idx { + self.selectable_at_or_after_index(min_idx) + } else if cursor.idx > max_idx { + self.selectable_at_or_before_index(max_idx) + } else { + return; + }; + + if let Some(new_cursor) = new_cursor { + self.cursor = Some(new_cursor); + } + } + } + fn clamp_scrolling(&mut self, height: usize) { let min = 0; let max = self.rows.len().saturating_sub(height); @@ -137,11 +164,13 @@ impl InnerListState { self.fix_cursor(); if self.make_cursor_visible { - self.make_cursor_visible(height); - self.make_cursor_visible = false; + self.scroll_so_cursor_is_visible(height); + self.clamp_scrolling(height); + } else { + self.clamp_scrolling(height); + self.move_cursor_to_make_it_visible(height); } - - self.clamp_scrolling(height); + self.make_cursor_visible = false; } } @@ -156,14 +185,14 @@ impl ListState { List::new(self.0.clone()) } - pub fn scroll_up(&mut self) { + pub fn scroll_up(&mut self, amount: usize) { let mut guard = self.0.lock(); - guard.offset = guard.offset.saturating_sub(1); + guard.offset = guard.offset.saturating_sub(amount); } - pub fn scroll_down(&mut self) { + pub fn scroll_down(&mut self, amount: usize) { let mut guard = self.0.lock(); - guard.offset = guard.offset.saturating_add(1); + guard.offset = guard.offset.saturating_add(amount); } } @@ -177,9 +206,9 @@ impl ListState { if let Some(cursor) = &guard.cursor { if let Some(new_cursor) = guard.selectable_before_index(cursor.idx) { guard.cursor = Some(new_cursor); - guard.make_cursor_visible = true; } } + guard.make_cursor_visible = true; } pub fn move_cursor_down(&mut self) { @@ -187,9 +216,25 @@ impl ListState { if let Some(cursor) = &guard.cursor { if let Some(new_cursor) = guard.selectable_after_index(cursor.idx) { guard.cursor = Some(new_cursor); - guard.make_cursor_visible = true; } } + guard.make_cursor_visible = true; + } + + pub fn move_cursor_to_top(&mut self) { + let mut guard = self.0.lock(); + if let Some(new_cursor) = guard.first_selectable() { + guard.cursor = Some(new_cursor); + } + guard.make_cursor_visible = true; + } + + pub fn move_cursor_to_bottom(&mut self) { + let mut guard = self.0.lock(); + if let Some(new_cursor) = guard.last_selectable() { + guard.cursor = Some(new_cursor); + } + guard.make_cursor_visible = true; } }