From 65f5f454be7ab01346ca43eecb85539548bba1ad Mon Sep 17 00:00:00 2001 From: Gene Wood Date: Mon, 22 Jun 2026 14:37:41 -0700 Subject: [PATCH] feat: add PageUp and PageDown bindings --- src/app.rs | 4 ++ src/key_bind.rs | 4 ++ src/tab.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/src/app.rs b/src/app.rs index ac0996a..2622a24 100644 --- a/src/app.rs +++ b/src/app.rs @@ -155,6 +155,8 @@ pub enum Action { HistoryPrevious, ItemDown, ItemLeft, + ItemPageDown, + ItemPageUp, ItemRight, ItemUp, LocationUp, @@ -228,6 +230,8 @@ impl Action { Self::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious), Self::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown), Self::ItemLeft => Message::TabMessage(entity_opt, tab::Message::ItemLeft), + Self::ItemPageDown => Message::TabMessage(entity_opt, tab::Message::ItemPageDown), + Self::ItemPageUp => Message::TabMessage(entity_opt, tab::Message::ItemPageUp), Self::ItemRight => Message::TabMessage(entity_opt, tab::Message::ItemRight), Self::ItemUp => Message::TabMessage(entity_opt, tab::Message::ItemUp), Self::LocationUp => Message::TabMessage(entity_opt, tab::Message::LocationUp), diff --git a/src/key_bind.rs b/src/key_bind.rs index f47d403..d3ddd59 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -30,12 +30,16 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap { bind!([], Key::Named(Named::F5), Reload); bind!([], Key::Named(Named::Home), SelectFirst); bind!([], Key::Named(Named::End), SelectLast); + bind!([], Key::Named(Named::PageDown), ItemPageDown); + bind!([], Key::Named(Named::PageUp), ItemPageUp); bind!([Shift], Key::Named(Named::ArrowDown), ItemDown); bind!([Shift], Key::Named(Named::ArrowLeft), ItemLeft); bind!([Shift], Key::Named(Named::ArrowRight), ItemRight); bind!([Shift], Key::Named(Named::ArrowUp), ItemUp); bind!([Shift], Key::Named(Named::Home), SelectFirst); bind!([Shift], Key::Named(Named::End), SelectLast); + bind!([Shift], Key::Named(Named::PageDown), ItemPageDown); + bind!([Shift], Key::Named(Named::PageUp), ItemPageUp); bind!([Ctrl, Shift], Key::Character("n".into()), NewFolder); bind!([], Key::Named(Named::Enter), Open); bind!([Ctrl], Key::Character(" ".into()), Preview); diff --git a/src/tab.rs b/src/tab.rs index 9c1c2e5..ebeaa63 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1724,6 +1724,8 @@ pub enum Message { GoPrevious, ItemDown, ItemLeft, + ItemPageDown, + ItemPageUp, ItemRight, ItemUp, Location(Location), @@ -3173,6 +3175,23 @@ impl Tab { } } + fn rows_per_page(&self) -> usize { + let viewport_height = self + .item_view_size_opt + .get() + .map_or(0.0, |s| s.height); + let row_height = self + .select_focus + .and_then(|i| self.items_opt.as_ref()?.get(i)) + .and_then(|item| item.rect_opt.get()) + .map_or(0.0, |r| r.height); + if row_height > 0.0 && viewport_height > 0.0 { + (viewport_height / row_height).floor() as usize + } else { + 1 + } + } + fn select_range_start_pos_opt(&self) -> Option<(usize, usize)> { let items = self.items_opt.as_ref()?; let item = items.get(self.select_range.map(|r| r.0)?)?; @@ -3870,6 +3889,100 @@ impl Tab { } } } + Message::ItemPageDown => { + self.dehighlight_all(); + if let Some((row, col)) = + self.select_focus_pos_opt().or(self.select_last_pos_opt()) + { + if self.select_focus.is_none() { + self.select_position(row, col, mod_shift); + } + + let rows_per_page = self.rows_per_page().max(1); + let target_row = row.saturating_add(rows_per_page); + + if !self.select_position(target_row, col, mod_shift) { + // Fall back to the last item at or before target_row + let best = self + .items_opt + .as_ref() + .and_then(|items| { + items + .iter() + .filter_map(|item| item.pos_opt.get()) + .filter(|(r, _)| *r <= target_row) + .max() + }); + if let Some((best_row, best_col)) = best { + self.select_position(best_row, best_col, mod_shift); + } + } + } else { + self.select_position(0, 0, mod_shift); + } + if let Some(offset) = self.select_focus_scroll() { + commands.push(Command::Iced( + scrollable::scroll_to( + self.scrollable_id.clone(), + AbsoluteOffset { + x: Some(offset.x), + y: Some(offset.y), + }, + ) + .into(), + )); + } + if let Some(id) = self.select_focus_id() { + commands.push(Command::Iced(widget::button::focus(id).into())); + } + } + Message::ItemPageUp => { + self.dehighlight_all(); + if let Some((row, col)) = + self.select_focus_pos_opt().or(self.select_first_pos_opt()) + { + if self.select_focus.is_none() { + self.select_position(row, col, mod_shift); + } + + let rows_per_page = self.rows_per_page().max(1); + let target_row = row.saturating_sub(rows_per_page); + + if !self.select_position(target_row, col, mod_shift) { + // Fall back to the first item at or after target_row + let best = self + .items_opt + .as_ref() + .and_then(|items| { + items + .iter() + .filter_map(|item| item.pos_opt.get()) + .filter(|(r, _)| *r >= target_row) + .min() + }); + if let Some((best_row, best_col)) = best { + self.select_position(best_row, best_col, mod_shift); + } + } + } else { + self.select_position(0, 0, mod_shift); + } + if let Some(offset) = self.select_focus_scroll() { + commands.push(Command::Iced( + scrollable::scroll_to( + self.scrollable_id.clone(), + AbsoluteOffset { + x: Some(offset.x), + y: Some(offset.y), + }, + ) + .into(), + )); + } + if let Some(id) = self.select_focus_id() { + commands.push(Command::Iced(widget::button::focus(id).into())); + } + } Message::ItemLeft => { self.dehighlight_all(); if self.gallery {