Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: improve help popup #105

Merged
merged 6 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 58 additions & 4 deletions rm-config/src/keymap/actions/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,39 @@ pub enum GeneralAction {
MoveToColumnRight,
}

pub enum GeneralActionMergable {
MoveUpDown,
MoveLeftRight,
ScrollPageUpDown,
MoveColumnLeftRight,
SwitchToTorrentsSearch,
}

impl UserAction for GeneralAction {
fn is_mergable_with(&self, other: &GeneralAction) -> bool {
let other = *other;
match self {
GeneralAction::SwitchToTorrents => other == Self::SwitchToSearch,
GeneralAction::SwitchToSearch => other == Self::SwitchToTorrents,
GeneralAction::Left => other == Self::Right,
GeneralAction::Right => other == Self::Left,
GeneralAction::Down => other == Self::Up,
GeneralAction::Up => other == Self::Down,
GeneralAction::ScrollPageDown => other == Self::ScrollPageUp,
GeneralAction::ScrollPageUp => other == Self::ScrollPageDown,
GeneralAction::GoToBeginning => other == Self::GoToEnd,
GeneralAction::GoToEnd => other == Self::GoToBeginning,
GeneralAction::MoveToColumnLeft => other == Self::MoveToColumnRight,
GeneralAction::MoveToColumnRight => other == Self::MoveToColumnLeft,
_ => false,
}
}

fn desc(&self) -> &'static str {
match self {
GeneralAction::ShowHelp => "toggle help",
GeneralAction::Quit => "quit Rustmission / a popup",
GeneralAction::Close => "close a popup / task",
GeneralAction::Quit => "quit Rustmission, a popup",
GeneralAction::Close => "close a popup, a task",
GeneralAction::SwitchToTorrents => "switch to torrents tab",
GeneralAction::SwitchToSearch => "switch to search tab",
GeneralAction::Left => "switch to tab left",
Expand All @@ -45,13 +72,40 @@ impl UserAction for GeneralAction {
GeneralAction::Select => "select",
GeneralAction::ScrollPageDown => "scroll page down",
GeneralAction::ScrollPageUp => "scroll page up",
GeneralAction::GoToBeginning => "scroll to the beginning",
GeneralAction::GoToEnd => "scroll to the end",
GeneralAction::GoToBeginning => "scroll to beginning",
GeneralAction::GoToEnd => "scroll to end",
GeneralAction::XdgOpen => "open with xdg-open",
GeneralAction::MoveToColumnRight => "move to right column",
GeneralAction::MoveToColumnLeft => "move to left column",
}
}

fn merged_desc(&self, other: &GeneralAction) -> Option<&'static str> {
match (&self, other) {
(Self::Left, Self::Right) => Some("switch to tab left / right"),
(Self::Right, Self::Left) => Some("switch to tab right / left"),
(Self::Down, Self::Up) => Some("move down / up"),
(Self::Up, Self::Down) => Some("move up / down"),
(Self::SwitchToTorrents, Self::SwitchToSearch) => {
Some("switch to torrents / search tab")
}
(Self::SwitchToSearch, Self::SwitchToTorrents) => {
Some("switch to search / torrents tab")
}
(Self::MoveToColumnLeft, Self::MoveToColumnRight) => {
Some("move to column left / right")
}
(Self::MoveToColumnRight, Self::MoveToColumnLeft) => {
Some("move to column right / left")
}
(Self::ScrollPageDown, Self::ScrollPageUp) => Some("scroll page down / up"),
(Self::ScrollPageUp, Self::ScrollPageDown) => Some("scroll page up / down"),
(Self::GoToBeginning, Self::GoToEnd) => Some("go to beginning / end"),
(Self::GoToEnd, Self::GoToBeginning) => Some("go to end / beginning"),

_ => None,
}
}
}

impl From<GeneralAction> for Action {
Expand Down
8 changes: 8 additions & 0 deletions rm-config/src/keymap/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ pub mod torrents_tab;

pub trait UserAction: Into<Action> {
fn desc(&self) -> &'static str;
fn merged_desc(&self, other: &Self) -> Option<&'static str> {
let _ = other;
None
}
fn is_mergable_with(&self, other: &Self) -> bool {
let _ = other;
false
}
}
3 changes: 3 additions & 0 deletions rm-config/src/keymap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct KeymapConfig {
#[derive(Serialize, Deserialize, Clone)]
pub struct KeybindsHolder<T: Into<Action>> {
pub keybindings: Vec<Keybinding<T>>,
#[serde(skip)]
pub help_repr: Vec<(String, &'static str)>,
}

#[derive(Serialize, Clone)]
Expand Down Expand Up @@ -314,6 +316,7 @@ impl KeymapConfig {
keys.push(keybinding.keycode_string());
}
}

for keybinding in &self.torrents_tab.keybindings {
if action == keybinding.action.into() {
keys.push(keybinding.keycode_string());
Expand Down
147 changes: 124 additions & 23 deletions rm-main/src/tui/global_popups/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ macro_rules! add_line {
};
}

const KEYS_DELIMITER: &str = ", ";

pub struct HelpPopup {
ctx: app::Ctx,
scroll: Option<Scroll>,
Expand All @@ -52,21 +54,21 @@ impl HelpPopup {
Self { ctx, scroll: None }
}

fn write_keybindings<T: Into<Action> + UserAction + Ord>(
fn get_keybindings<T: Into<Action> + UserAction + Ord>(
keybindings: &[Keybinding<T>],
lines: &mut Vec<Line>,
) {
let mut keys = BTreeMap::new();
let mut max_len = 0;
max_keycode_len: &mut usize,
max_line_len: &mut usize,
) -> Vec<(String, &'static str)> {
let mut keys: BTreeMap<&T, Vec<String>> = BTreeMap::new();

for keybinding in keybindings {
if !keybinding.show_in_help {
continue;
}

let keycode = keybinding.keycode_string();
if keycode.len() > max_len {
max_len = keycode.chars().count();
if keycode.len() > *max_keycode_len {
*max_keycode_len = keycode.chars().count();
}

keys.entry(&keybinding.action)
Expand All @@ -86,21 +88,66 @@ impl HelpPopup {
keycodes_total_len += keycode.chars().count();
}

if keycodes_total_len + delimiter_len > max_len {
max_len = keycodes_total_len + delimiter_len;
if keycodes_total_len + delimiter_len > *max_keycode_len {
*max_keycode_len = keycodes_total_len + delimiter_len;
}
}

let mut new_keys = vec![];

for (action, keycodes) in keys {
let mut keycode_string = keycodes.join(" / ");
let mut how_much_to_pad = max_len - keycode_string.chars().count();
while how_much_to_pad > 0 {
keycode_string.insert(0, ' ');
how_much_to_pad -= 1;
new_keys.push((action, keycodes));
}

let mut res = vec![];
let mut skip_next_loop = false;
for (idx, (action, keycodes)) in new_keys.iter().enumerate() {
if skip_next_loop {
skip_next_loop = false;
continue;
}

if let Some(next_key) = new_keys.get(idx + 1) {
if action.is_mergable_with(next_key.0) {
skip_next_loop = true;
let keys = format!(
"{} / {}",
keycodes.join(KEYS_DELIMITER),
next_key.1.join(KEYS_DELIMITER)
);

if keys.chars().count() > *max_keycode_len {
*max_keycode_len = keys.chars().count();
}

let desc = action
.merged_desc(next_key.0)
.expect("keys checked for mergability before");

let line_len = keys.chars().count() + desc.chars().count() + 3;
if line_len > *max_line_len {
*max_line_len = line_len;
}

res.push((keys, desc));

continue;
}
}

add_line!(lines, keycode_string, action.desc());
let keycode_string = keycodes.join(KEYS_DELIMITER);
if keycode_string.chars().count() > *max_keycode_len {
*max_keycode_len = keycode_string.chars().count();
}
let desc = action.desc();
let line_len = keycode_string.chars().count() + desc.chars().count() + 3;
if line_len > *max_line_len {
*max_line_len = line_len;
}
res.push((keycode_string, desc));
}

res
}

fn scroll_down(&mut self) -> ComponentAction {
Expand Down Expand Up @@ -162,13 +209,67 @@ impl Component for HelpPopup {

let block = popup_block_with_close_highlight(" Help ");

let mut lines = vec![Line::from(vec![Span::styled(
"Global Keybindings",
Style::default().bold().underlined(),
)])
.centered()];
let mut to_pad = 0;
let mut max_line_len = 0;
let mut global_keys = Self::get_keybindings(
&CONFIG.keybindings.general.keybindings,
&mut to_pad,
&mut max_line_len,
);
let mut torrents_keys = Self::get_keybindings(
&CONFIG.keybindings.torrents_tab.keybindings,
&mut to_pad,
&mut max_line_len,
);
let mut search_keys = Self::get_keybindings(
&CONFIG.keybindings.search_tab.keybindings,
&mut to_pad,
&mut max_line_len,
);

debug_assert!(to_pad > 0);
debug_assert!(max_line_len > 0);

let to_pad_additionally = (text_rect
.width
.saturating_sub(max_line_len.try_into().unwrap())
/ 2)
.saturating_sub(6);

to_pad += usize::from(to_pad_additionally);

let pad_keys = |keys: &mut Vec<(String, &'static str)>| {
for key in keys {
let mut how_much_to_pad = to_pad.saturating_sub(key.0.chars().count());
while how_much_to_pad > 0 {
key.0.insert(0, ' ');
how_much_to_pad -= 1;
}
}
};

pad_keys(&mut global_keys);
pad_keys(&mut torrents_keys);
pad_keys(&mut search_keys);

let mut lines = vec![];

let insert_keys = |lines: &mut Vec<Line>, keys: Vec<(String, &'static str)>| {
for (keycode, desc) in keys {
add_line!(lines, keycode, desc);
}
lines.push(Line::default());
};

lines.push(
Line::from(vec![Span::styled(
"Global Keybindings",
Style::default().bold().underlined(),
)])
.centered(),
);

Self::write_keybindings(&CONFIG.keybindings.general.keybindings, &mut lines);
insert_keys(&mut lines, global_keys);

lines.push(
Line::from(vec![Span::styled(
Expand All @@ -178,7 +279,7 @@ impl Component for HelpPopup {
.centered(),
);

Self::write_keybindings(&CONFIG.keybindings.torrents_tab.keybindings, &mut lines);
insert_keys(&mut lines, torrents_keys);

lines.push(
Line::from(vec![Span::styled(
Expand All @@ -188,7 +289,7 @@ impl Component for HelpPopup {
.centered(),
);

Self::write_keybindings(&CONFIG.keybindings.search_tab.keybindings, &mut lines);
insert_keys(&mut lines, search_keys);

let help_text = Text::from(lines);

Expand Down
Loading