Make PageUp/PageDown behavior configurable and default to 50%

This commit is contained in:
2023-11-22 14:27:34 +01:00
parent c7124c1191
commit 1c8c6d3e3e
5 changed files with 105 additions and 9 deletions

View File

@ -24,6 +24,7 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter", "serde"] }
clap = { version = "4.4.5", features = ["derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] }
# Config
bounded-integer = { version = "0.5.7", features = ["types"] }
config = "0.13.3"
derive_deref = "1.1.1"
directories = "5.0.1"
@ -31,6 +32,7 @@ hostname = "0.3.1"
json5 = "0.4.1"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
serde_path_to_error = "0.1.14"
# TODO: switch to toml_edit to preserve (and write) doc comments
# Error handling

View File

@ -22,6 +22,7 @@ use enum_dispatch::enum_dispatch;
use ratatui::{prelude::*, widgets::*};
use crate::components::Action;
use crate::config::{Config, ScrollAmount};
use crate::widgets::prerender::{PrerenderInner, PrerenderValue};
use crate::widgets::{
BacklogItemWidget, BottomAlignedParagraph, Divider, EmptyWidget, OverlappableWidget,
@ -38,8 +39,11 @@ struct ScrollPosition {
relative_scroll: i64,
}
#[derive(Default, Debug)]
#[derive(Debug)]
pub struct Backlog {
config: Config,
/// Used to compute scroll on PageUp/PageDown when configured to a percentage.
last_height: u16,
scroll_position: Option<ScrollPosition>,
/// Fallback used if the scroll_position is missing or unusable
absolute_scroll: u64,
@ -58,16 +62,22 @@ impl Component for Backlog {
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: _,
} => {
self.scroll_up(20); // TODO: use the component height
} => match self.config.keyboard.scroll_page {
ScrollAmount::Absolute(n) => self.scroll_up(n.into()),
ScrollAmount::Percentage(n) => {
self.scroll_up(u64::from(n) * u64::from(self.last_height) / 100)
},
},
KeyEvent {
code: KeyCode::PageDown,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: _,
} => {
self.scroll_down(20) // TODO: use the component height
} => match self.config.keyboard.scroll_page {
ScrollAmount::Absolute(n) => self.scroll_down(n.into()),
ScrollAmount::Percentage(n) => {
self.scroll_down(u64::from(n) * u64::from(self.last_height) / 100)
},
},
_ => {},
}
@ -96,6 +106,16 @@ impl Component for Backlog {
}
impl Backlog {
pub fn new(config: Config) -> Self {
Backlog {
config,
last_height: 30, // Arbitrary default, only useful when user scrolls before first render
scroll_position: None,
absolute_scroll: 0,
pending_scroll: 0,
}
}
pub fn scroll_up(&mut self, lines: u64) {
self.pending_scroll = self.pending_scroll.saturating_add(lines as i64);
}
@ -156,6 +176,7 @@ impl Backlog {
let block = Block::new().borders(Borders::ALL);
let mut text_area = block.inner(area);
block.render(area, frame_buffer);
self.last_height = text_area.height;
// Recompute absolute scroll position if we are not at the bottom of the backlog
if self.absolute_scroll != 0 || self.pending_scroll != 0 {
@ -206,7 +227,7 @@ impl Backlog {
};
let expected_height = self.get_item_height(&item, text_area.width);
if scroll.saturating_sub(expected_height) > text_area.height.into() {
if scroll.saturating_sub(expected_height) > u64::from(text_area.height) {
// Paragraph is too far down, not displayed
scroll -= expected_height;
continue;

View File

@ -43,7 +43,7 @@ impl Home {
let mut self_ = Home {
command_tx: None,
buflist: Buflist::new(config.clone()),
backlog: Backlog::default(),
backlog: Backlog::new(config.clone()),
textarea: TextArea::default(),
config,
};

View File

@ -1,5 +1,6 @@
use std::{collections::HashMap, fmt, path::PathBuf};
use bounded_integer::BoundedU8;
use color_eyre::eyre::Result;
use config::Value;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
@ -40,11 +41,77 @@ fn default_device_name() -> String {
}
}
#[derive(Clone, Debug)]
pub enum ScrollAmount {
Percentage(BoundedU8<1, 100>),
Absolute(u16),
}
impl<'de> Deserialize<'de> for ScrollAmount {
fn deserialize<D>(deserializer: D) -> Result<ScrollAmount, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(ScrollAmountVisitor)
}
}
struct ScrollAmountVisitor;
impl<'de> Visitor<'de> for ScrollAmountVisitor {
type Value = ScrollAmount;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a positive integer or a percentage")
}
fn visit_u16<E>(self, value: u16) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(ScrollAmount::Absolute(value))
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect(); // strip whitespaces
match s.strip_suffix('%') {
Some(percent_str) => match percent_str.parse() {
Ok(percent) => {
if 0 < percent && percent <= 100 {
Ok(ScrollAmount::Percentage(percent))
} else {
Err(E::invalid_value(
de::Unexpected::Unsigned(percent.into()),
&"integer between 1 and 100 (inclusive)",
))
}
},
Err(_) => Err(E::invalid_value(
de::Unexpected::Other(percent_str),
&"integer between 1 and 100 (inclusive)",
)),
},
None => Err(E::invalid_value(
de::Unexpected::Str(&s),
&"integer or quoted percentage (ending with '%')",
)),
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct MouseConfig {
pub enable: bool,
}
#[derive(Clone, Debug, Deserialize)]
pub struct KeyboardConfig {
pub scroll_page: ScrollAmount,
}
#[derive(Clone, Debug, Deserialize)]
pub struct BuflistLayoutConfig {
pub column_width: u16,
@ -84,13 +151,14 @@ pub struct Config {
pub accounts: nonempty::NonEmpty<AccountConfig>,
#[serde(default)]
pub keybindings: KeyBindings,
pub keyboard: KeyboardConfig,
pub mouse: MouseConfig,
pub style: StylesConfig,
pub layout: LayoutConfig,
}
impl Config {
pub fn new() -> Result<Self, config::ConfigError> {
pub fn new() -> Result<Self> {
let data_dir = crate::utils::get_data_dir();
let config_dir = crate::utils::get_config_dir();
let mut builder = config::Config::builder()
@ -123,7 +191,7 @@ impl Config {
log::error!("No configuration file found. Application may not behave as expected");
}
builder.build()?.try_deserialize()
Ok(serde_path_to_error::deserialize(builder.build()?)?)
}
}

View File

@ -6,6 +6,11 @@
"<Alt-left>" = "/previous"
"<Alt-right>" = "/next"
[keyboard]
# How much to scroll when pressing PageUp/PageDown. This can be either a number of lines
# eg. 42) or a percentage wrapped in quotes (eg. "100%") of the screen size.
scroll_page = "50%"
[mouse]
enable = true