From aee8b37caaa578b9d1f6c854928343ba4f645563 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Thu, 11 Apr 2024 10:58:45 +0200 Subject: [PATCH] lib: Rework page shortcuts --- bin/commands/inbox/select/ui.rs | 58 ++++++++++++------------------ bin/commands/issue/select/ui.rs | 58 ++++++++++++------------------ bin/commands/patch/select/ui.rs | 60 +++++++++++++------------------ src/ui/widget.rs | 63 +++++++++++++++++++++------------ 4 files changed, 110 insertions(+), 129 deletions(-) diff --git a/bin/commands/inbox/select/ui.rs b/bin/commands/inbox/select/ui.rs index 2d218dc6..7727fa25 100644 --- a/bin/commands/inbox/select/ui.rs +++ b/bin/commands/inbox/select/ui.rs @@ -17,7 +17,7 @@ use tui::ui::span; use tui::ui::widget::container::{Footer, FooterProps, Header}; use tui::ui::widget::input::{TextField, TextFieldProps}; use tui::ui::widget::text::{Paragraph, ParagraphProps}; -use tui::ui::widget::{Column, Render, Shortcut, Shortcuts, ShortcutsProps, Table, Widget}; +use tui::ui::widget::{Column, Render, Shortcuts, Table, Widget}; use tui::Selection; use crate::tui_inbox::common::{InboxOperation, Mode, RepositoryMode, SelectionMode}; @@ -25,7 +25,6 @@ use crate::tui_inbox::common::{InboxOperation, Mode, RepositoryMode, SelectionMo use super::{Action, State}; pub struct ListPageProps { - mode: Mode, show_search: bool, show_help: bool, } @@ -33,7 +32,6 @@ pub struct ListPageProps { impl From<&State> for ListPageProps { fn from(state: &State) -> Self { Self { - mode: state.mode.clone(), show_search: state.ui.show_search, show_help: state.ui.show_help, } @@ -66,7 +64,7 @@ impl<'a> Widget for ListPage<'a> { notifications: Notifications::new(state, action_tx.clone()), search: Search::new(state, action_tx.clone()), help: Help::new(state, action_tx.clone()), - shortcuts: Shortcuts::new(state, action_tx.clone()), + shortcuts: Shortcuts::new(&(), action_tx.clone()), } .move_with_state(state) } @@ -75,9 +73,28 @@ impl<'a> Widget for ListPage<'a> { where Self: Sized, { + let shorts = if state.ui.show_search { + vec![("esc", "cancel"), ("enter", "apply")] + } else if state.ui.show_help { + vec![("?", "close")] + } else { + match state.mode.selection() { + SelectionMode::Id => vec![("enter", "select"), ("/", "search")], + SelectionMode::Operation => vec![ + ("enter", "show"), + ("c", "clear"), + ("/", "search"), + ("?", "help"), + ], + } + }; + + let shortcuts = self.shortcuts.move_with_state(&()); + let shortcuts = shortcuts.shortcuts(&shorts); + ListPage { notifications: self.notifications.move_with_state(state), - shortcuts: self.shortcuts.move_with_state(state), + shortcuts, help: self.help.move_with_state(state), props: ListPageProps::from(state), ..self @@ -116,28 +133,6 @@ impl<'a> Render<()> for ListPage<'a> { let area = frame.size(); let layout = tui::ui::layout::default_page(area, 0u16, 1u16); - let shortcuts = if self.props.show_search { - vec![ - Shortcut::new("esc", "cancel"), - Shortcut::new("enter", "apply"), - ] - } else if self.props.show_help { - vec![Shortcut::new("?", "close")] - } else { - match self.props.mode.selection() { - SelectionMode::Id => vec![ - Shortcut::new("enter", "select"), - Shortcut::new("/", "search"), - ], - SelectionMode::Operation => vec![ - Shortcut::new("enter", "show"), - Shortcut::new("c", "clear"), - Shortcut::new("/", "search"), - Shortcut::new("?", "help"), - ], - } - }; - if self.props.show_search { let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)]) .split(layout.component); @@ -152,14 +147,7 @@ impl<'a> Render<()> for ListPage<'a> { self.notifications.render::(frame, layout.component, ()); } - self.shortcuts.render::( - frame, - layout.shortcuts, - ShortcutsProps { - shortcuts, - divider: '∙', - }, - ); + self.shortcuts.render::(frame, layout.shortcuts, ()); } } diff --git a/bin/commands/issue/select/ui.rs b/bin/commands/issue/select/ui.rs index 0b033e59..28b00d47 100644 --- a/bin/commands/issue/select/ui.rs +++ b/bin/commands/issue/select/ui.rs @@ -19,7 +19,7 @@ use tui::ui::span; use tui::ui::widget::container::{Footer, FooterProps, Header}; use tui::ui::widget::input::{TextField, TextFieldProps}; use tui::ui::widget::text::{Paragraph, ParagraphProps}; -use tui::ui::widget::{Column, Render, Shortcut, Shortcuts, ShortcutsProps, Table, Widget}; +use tui::ui::widget::{Column, Render, Shortcuts, Table, Widget}; use tui::Selection; use crate::tui_issue::common::IssueOperation; @@ -28,7 +28,6 @@ use crate::tui_issue::common::Mode; use super::{Action, State}; pub struct ListPageProps { - mode: Mode, show_search: bool, show_help: bool, } @@ -36,7 +35,6 @@ pub struct ListPageProps { impl From<&State> for ListPageProps { fn from(state: &State) -> Self { Self { - mode: state.mode.clone(), show_search: state.ui.show_search, show_help: state.ui.show_help, } @@ -69,7 +67,7 @@ impl<'a> Widget for ListPage<'a> { issues: Issues::new(state, action_tx.clone()), search: Search::new(state, action_tx.clone()), help: Help::new(state, action_tx.clone()), - shortcuts: Shortcuts::new(state, action_tx.clone()), + shortcuts: Shortcuts::new(&(), action_tx.clone()), } .move_with_state(state) } @@ -78,9 +76,28 @@ impl<'a> Widget for ListPage<'a> { where Self: Sized, { + let shorts = if state.ui.show_search { + vec![("esc", "cancel"), ("enter", "apply")] + } else if state.ui.show_help { + vec![("?", "close")] + } else { + match state.mode { + Mode::Id => vec![("enter", "select"), ("/", "search")], + Mode::Operation => vec![ + ("enter", "show"), + ("e", "edit"), + ("/", "search"), + ("?", "help"), + ], + } + }; + + let shortcuts = self.shortcuts.move_with_state(&()); + let shortcuts = shortcuts.shortcuts(&shorts); + ListPage { issues: self.issues.move_with_state(state), - shortcuts: self.shortcuts.move_with_state(state), + shortcuts, help: self.help.move_with_state(state), props: ListPageProps::from(state), ..self @@ -116,28 +133,6 @@ impl<'a> Render<()> for ListPage<'a> { let area = frame.size(); let layout = tui::ui::layout::default_page(area, 0u16, 1u16); - let shortcuts = if self.props.show_search { - vec![ - Shortcut::new("esc", "cancel"), - Shortcut::new("enter", "apply"), - ] - } else if self.props.show_help { - vec![Shortcut::new("?", "close")] - } else { - match self.props.mode { - Mode::Id => vec![ - Shortcut::new("enter", "select"), - Shortcut::new("/", "search"), - ], - Mode::Operation => vec![ - Shortcut::new("enter", "show"), - Shortcut::new("e", "edit"), - Shortcut::new("/", "search"), - Shortcut::new("?", "help"), - ], - } - }; - if self.props.show_search { let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)]) .split(layout.component); @@ -151,14 +146,7 @@ impl<'a> Render<()> for ListPage<'a> { self.issues.render::(frame, layout.component, ()); } - self.shortcuts.render::( - frame, - layout.shortcuts, - ShortcutsProps { - shortcuts, - divider: '∙', - }, - ); + self.shortcuts.render::(frame, layout.shortcuts, ()); } } diff --git a/bin/commands/patch/select/ui.rs b/bin/commands/patch/select/ui.rs index 888d7dd5..855c99c1 100644 --- a/bin/commands/patch/select/ui.rs +++ b/bin/commands/patch/select/ui.rs @@ -20,7 +20,7 @@ use tui::ui::span; use tui::ui::widget::container::{Footer, FooterProps, Header}; use tui::ui::widget::input::{TextField, TextFieldProps}; use tui::ui::widget::text::{Paragraph, ParagraphProps}; -use tui::ui::widget::{Column, Render, Shortcut, Shortcuts, ShortcutsProps, Table, Widget}; +use tui::ui::widget::{Column, Render, Shortcuts, Table, Widget}; use tui::Selection; use crate::tui_patch::common::Mode; @@ -29,7 +29,6 @@ use crate::tui_patch::common::PatchOperation; use super::{Action, State}; pub struct ListPageProps { - mode: Mode, show_search: bool, show_help: bool, } @@ -37,7 +36,6 @@ pub struct ListPageProps { impl From<&State> for ListPageProps { fn from(state: &State) -> Self { Self { - mode: state.mode.clone(), show_search: state.ui.show_search, show_help: state.ui.show_help, } @@ -70,7 +68,7 @@ impl<'a> Widget for ListPage<'a> { patches: Patches::new(state, action_tx.clone()), search: Search::new(state, action_tx.clone()), help: Help::new(state, action_tx.clone()), - shortcuts: Shortcuts::new(state, action_tx), + shortcuts: Shortcuts::new(&(), action_tx), } .move_with_state(state) } @@ -79,10 +77,30 @@ impl<'a> Widget for ListPage<'a> { where Self: Sized, { + let shorts = if state.ui.show_search { + vec![("esc", "cancel"), ("enter", "apply")] + } else if state.ui.show_help { + vec![("?", "close")] + } else { + match state.mode { + Mode::Id => vec![("enter", "select"), ("/", "search")], + Mode::Operation => vec![ + ("enter", "show"), + ("c", "checkout"), + ("d", "diff"), + ("/", "search"), + ("?", "help"), + ], + } + }; + + let shortcuts = self.shortcuts.move_with_state(&()); + let shortcuts = shortcuts.shortcuts(&shorts); + ListPage { patches: self.patches.move_with_state(state), search: self.search.move_with_state(state), - shortcuts: self.shortcuts.move_with_state(state), + shortcuts, help: self.help.move_with_state(state), props: ListPageProps::from(state), ..self @@ -118,29 +136,6 @@ impl<'a> Render<()> for ListPage<'a> { let area = frame.size(); let layout = tui::ui::layout::default_page(area, 0u16, 1u16); - let shortcuts = if self.props.show_search { - vec![ - Shortcut::new("esc", "cancel"), - Shortcut::new("enter", "apply"), - ] - } else if self.props.show_help { - vec![Shortcut::new("?", "close")] - } else { - match self.props.mode { - Mode::Id => vec![ - Shortcut::new("enter", "select"), - Shortcut::new("/", "search"), - ], - Mode::Operation => vec![ - Shortcut::new("enter", "show"), - Shortcut::new("c", "checkout"), - Shortcut::new("d", "diff"), - Shortcut::new("/", "search"), - Shortcut::new("?", "help"), - ], - } - }; - if self.props.show_search { let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)]) .split(layout.component); @@ -154,14 +149,7 @@ impl<'a> Render<()> for ListPage<'a> { self.patches.render::(frame, layout.component, ()); } - self.shortcuts.render::( - frame, - layout.shortcuts, - ShortcutsProps { - shortcuts, - divider: '∙', - }, - ); + self.shortcuts.render::(frame, layout.shortcuts, ()); } } diff --git a/src/ui/widget.rs b/src/ui/widget.rs index 96ca25ba..f427c7bc 100644 --- a/src/ui/widget.rs +++ b/src/ui/widget.rs @@ -37,41 +37,57 @@ pub trait ToRow { fn to_row(&self) -> Vec; } -pub struct Shortcut { - pub short: String, - pub long: String, +pub struct ShortcutsProps { + pub shortcuts: Vec<(String, String)>, + pub divider: char, } -impl Shortcut { - pub fn new(short: &str, long: &str) -> Self { +impl Default for ShortcutsProps { + fn default() -> Self { Self { - short: short.to_string(), - long: long.to_string(), + shortcuts: vec![], + divider: '∙', } } } -pub struct ShortcutsProps { - pub shortcuts: Vec, - pub divider: char, -} - pub struct Shortcuts { + /// Message sender pub action_tx: UnboundedSender, + /// Internal properties + props: ShortcutsProps, } -impl Widget for Shortcuts { - fn new(state: &S, action_tx: UnboundedSender) -> Self +impl Shortcuts { + pub fn divider(mut self, divider: char) -> Self { + self.props.divider = divider; + self + } + + pub fn shortcuts(mut self, shortcuts: &[(&str, &str)]) -> Self { + self.props.shortcuts.clear(); + for (short, long) in shortcuts { + self.props + .shortcuts + .push((short.to_string(), long.to_string())); + } + self + } +} + +impl Widget<(), A> for Shortcuts { + fn new(state: &(), action_tx: UnboundedSender) -> Self where Self: Sized, { Self { action_tx: action_tx.clone(), + props: ShortcutsProps::default(), } .move_with_state(state) } - fn move_with_state(self, _state: &S) -> Self + fn move_with_state(self, _state: &()) -> Self where Self: Sized, { @@ -81,22 +97,23 @@ impl Widget for Shortcuts { fn handle_key_event(&mut self, _key: termion::event::Key) {} } -impl Render for Shortcuts { - fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: ShortcutsProps) { +impl Render<()> for Shortcuts { + fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: ()) { use ratatui::widgets::Table; - let mut shortcuts = props.shortcuts.iter().peekable(); + let mut shortcuts = self.props.shortcuts.iter().peekable(); let mut row = vec![]; while let Some(shortcut) = shortcuts.next() { - let short = Text::from(shortcut.short.clone()).style(style::gray()); - let long = Text::from(shortcut.long.clone()).style(style::gray().dim()); + let short = Text::from(shortcut.0.clone()).style(style::gray()); + let long = Text::from(shortcut.1.clone()).style(style::gray().dim()); let spacer = Text::from(String::new()); - let divider = Text::from(format!(" {} ", props.divider)).style(style::gray().dim()); + let divider = + Text::from(format!(" {} ", self.props.divider)).style(style::gray().dim()); - row.push((shortcut.short.chars().count(), short)); + row.push((shortcut.0.chars().count(), short)); row.push((1, spacer)); - row.push((shortcut.long.chars().count(), long)); + row.push((shortcut.1.chars().count(), long)); if shortcuts.peek().is_some() { row.push((3, divider));