From 499bfdb8e5b33d1c4c8554908fc3d71abf8bd0b3 Mon Sep 17 00:00:00 2001 From: Alex Pasmantier <47638216+alexpasmantier@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:21:17 +0100 Subject: [PATCH] refactor(ui): more compact general layout and make preview panel optional (#148) Fixes #147 Screenshot 2024-12-28 at 15 18 42 --- .config/config.toml | 37 +++++---- crates/television-screen/src/input.rs | 3 +- crates/television-screen/src/keybindings.rs | 14 ---- crates/television-screen/src/layout.rs | 61 ++++++--------- crates/television-screen/src/preview.rs | 45 ++++------- crates/television-screen/src/results.rs | 8 ++ crates/television/action.rs | 3 + crates/television/config/ui.rs | 22 ++++-- crates/television/television.rs | 87 ++++++++++++--------- 9 files changed, 142 insertions(+), 138 deletions(-) diff --git a/.config/config.toml b/.config/config.toml index 5ec5a19..1a5e4e0 100644 --- a/.config/config.toml +++ b/.config/config.toml @@ -43,11 +43,15 @@ use_nerd_font_icons = false ui_scale = 100 # Whether to show the top help bar in the UI by default # This option can be toggled with the (default) `ctrl-g` keybinding -show_help_bar = true +show_help_bar = false +# Whether to show the preview panel in the UI by default +# This option can be toggled with the (default) `ctrl-o` keybinding +show_preview_panel = true # Where to place the input bar in the UI (top or bottom) -input_bar_position = "bottom" +input_bar_position = "top" +# DEPRECATED: title is now always displayed at the top as part of the border # Where to place the preview title in the UI (top or bottom) -preview_title_position = "top" +# preview_title_position = "top" # The theme to use for the UI # A list of builtin themes can be found in the `themes` directory of the television # repository. You may also create your own theme by creating a new file in a `themes` @@ -88,6 +92,8 @@ toggle_remote_control = "ctrl-r" toggle_send_to_channel = "ctrl-s" # Toggle the help bar toggle_help = "ctrl-g" +# Toggle the preview panel +toggle_preview = "ctrl-o" # Remote control mode @@ -106,6 +112,8 @@ select_entry = "enter" toggle_remote_control = "ctrl-r" # Toggle the help bar toggle_help = "ctrl-g" +# Toggle the preview panel +toggle_preview = "ctrl-o" # Send to channel mode @@ -124,24 +132,27 @@ select_entry = "enter" toggle_send_to_channel = "ctrl-s" # Toggle the help bar toggle_help = "ctrl-g" +# Toggle the preview panel +toggle_preview = "ctrl-o" # Shell integration # ---------------------------------------------------------------------------- # # The shell integration feature allows you to use television as a picker for -# your shell commands. E.g. typing `git checkout ` will open television -# with a list of branches to checkout. +# your shell commands. E.g. typing `git checkout ` will open television +# with a list of branches to choose from. [shell_integration.commands] -# Which commands should trigger which channels -# The keys are the commands that should trigger the channels, and the values -# are the channels that should be triggered. -# Example: `git checkout` should trigger the `git-branches` channel -# `ls` should trigger the `dirs` channel -# `cat` should trigger the `files` channel +# Add your commands here. Each key is a command that will trigger tv with the +# corresponding channel as value. +# Example: say you want the following prompts to trigger the following channels +# when pressing : +# `git checkout` should trigger the `git-branches` channel +# `ls` should trigger the `dirs` channel +# `cat` should trigger the `files` channel # -# Would be written as: +# You would add the following to your configuration file: # ``` # [shell_integration.commands] # "git checkout" = "git-branches" @@ -149,8 +160,6 @@ toggle_help = "ctrl-g" # "cat" = "files" # ``` -# The following are an example, you can remove/modify them and add your own. - # shell history (according to your shell) "" = "zsh-history" diff --git a/crates/television-screen/src/input.rs b/crates/television-screen/src/input.rs index b67cc22..479e071 100644 --- a/crates/television-screen/src/input.rs +++ b/crates/television-screen/src/input.rs @@ -4,7 +4,7 @@ use ratatui::{ Alignment, Constraint, Direction, Layout as RatatuiLayout, Rect, }, style::{Style, Stylize}, - text::{Line, Span}, + text::Span, widgets::{Block, BorderType, Borders, ListState, Paragraph}, Frame, }; @@ -30,7 +30,6 @@ pub fn draw_input_box( colorscheme: &Colorscheme, ) -> Result<()> { let input_block = Block::default() - .title_top(Line::from(" Pattern ").alignment(Alignment::Center)) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(colorscheme.general.border_fg)) diff --git a/crates/television-screen/src/keybindings.rs b/crates/television-screen/src/keybindings.rs index ddee2b8..f555ed2 100644 --- a/crates/television-screen/src/keybindings.rs +++ b/crates/television-screen/src/keybindings.rs @@ -150,19 +150,6 @@ fn build_keybindings_table_for_channel<'a>( colorscheme.mode.channel, )); - // MISC line (quit, help, etc.) - // Toggle help bar - let toggle_help_bar_keys = keybindings - .bindings - .get(&DisplayableAction::ToggleHelpBar) - .unwrap(); - let toggle_help_bar_row = Row::new(build_cells_for_group( - "Toggle help bar", - toggle_help_bar_keys, - colorscheme.help.metadata_field_name_fg, - colorscheme.mode.channel, - )); - let widths = vec![Constraint::Fill(1), Constraint::Fill(2)]; Table::new( @@ -173,7 +160,6 @@ fn build_keybindings_table_for_channel<'a>( copy_entry_row, send_to_channel_row, switch_channels_row, - toggle_help_bar_row, ], widths, ) diff --git a/crates/television-screen/src/layout.rs b/crates/television-screen/src/layout.rs index 718b67a..70093c8 100644 --- a/crates/television-screen/src/layout.rs +++ b/crates/television-screen/src/layout.rs @@ -84,8 +84,7 @@ pub struct Layout { pub help_bar: Option, pub results: Rect, pub input: Rect, - pub preview_title: Rect, - pub preview_window: Rect, + pub preview_window: Option, pub remote_control: Option, } @@ -95,15 +94,13 @@ impl Layout { help_bar: Option, results: Rect, input: Rect, - preview_title: Rect, - preview_window: Rect, + preview_window: Option, remote_control: Option, ) -> Self { Self { help_bar, results, input, - preview_title, preview_window, remote_control, } @@ -114,8 +111,8 @@ impl Layout { area: Rect, with_remote: bool, with_help_bar: bool, + with_preview: bool, input_position: InputPosition, - preview_title_position: PreviewTitlePosition, ) -> Self { let main_block = centered_rect(dimensions.x, dimensions.y, area); // split the main block into two vertical chunks (help bar + rest) @@ -152,16 +149,16 @@ impl Layout { help_bar_layout = None; } - // split the main block into two vertical chunks - let constraints = if with_remote { - vec![ - Constraint::Fill(1), - Constraint::Fill(1), - Constraint::Length(24), - ] - } else { - vec![Constraint::Percentage(50), Constraint::Percentage(50)] - }; + // split the main block into 1, 2, or 3 vertical chunks + // (results + preview + remote) + let mut constraints = vec![Constraint::Fill(1)]; + if with_preview { + constraints.push(Constraint::Fill(1)); + } + if with_remote { + // in order to fit with the help bar logo + constraints.push(Constraint::Length(24)); + } let vt_chunks = layout::Layout::default() .direction(Direction::Horizontal) .constraints(constraints) @@ -186,34 +183,26 @@ impl Layout { }; // right block: preview title + preview - let preview_constraints = - vec![Constraint::Length(3), Constraint::Min(3)]; + let mut remote_idx = 1; + let preview_window = if with_preview { + remote_idx += 1; + Some(vt_chunks[1]) + } else { + None + }; - let right_chunks = layout::Layout::default() - .direction(Direction::Vertical) - .constraints(match preview_title_position { - PreviewTitlePosition::Bottom => { - preview_constraints.into_iter().rev().collect() - } - PreviewTitlePosition::Top => preview_constraints, - }) - .split(vt_chunks[1]); - let (preview_title, preview_window) = match preview_title_position { - PreviewTitlePosition::Top => (right_chunks[0], right_chunks[1]), - PreviewTitlePosition::Bottom => (right_chunks[1], right_chunks[0]), + let remote_control = if with_remote { + Some(vt_chunks[remote_idx]) + } else { + None }; Self::new( help_bar_layout, results, input, - preview_title, preview_window, - if with_remote { - Some(vt_chunks[2]) - } else { - None - }, + remote_control, ) } } diff --git a/crates/television-screen/src/preview.rs b/crates/television-screen/src/preview.rs index 9224ed6..ed1f70d 100644 --- a/crates/television-screen/src/preview.rs +++ b/crates/television-screen/src/preview.rs @@ -239,14 +239,18 @@ pub fn build_meta_preview_paragraph<'a>( Paragraph::new(Text::from(lines)) } -pub fn draw_preview_title_block( +#[allow(clippy::too_many_arguments)] +pub fn draw_preview_content_block( f: &mut Frame, rect: Rect, + entry: &Entry, preview: &Arc, + rendered_preview_cache: &Arc>>, + preview_scroll: u16, use_nerd_font_icons: bool, colorscheme: &Colorscheme, ) -> Result<()> { - let mut preview_title_spans = Vec::new(); + let mut preview_title_spans = vec![Span::from(" ")]; if preview.icon.is_some() && use_nerd_font_icons { let icon = preview.icon.as_ref().unwrap(); preview_title_spans.push(Span::styled( @@ -269,38 +273,18 @@ pub fn draw_preview_title_block( ), Style::default().fg(colorscheme.preview.title_fg).bold(), )); - let preview_title = Paragraph::new(Line::from(preview_title_spans)) - .block( - Block::default() - .padding(Padding::horizontal(1)) - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .border_style( - Style::default().fg(colorscheme.general.border_fg), - ), - ) - .alignment(Alignment::Left) - .style(Style::default().bg(colorscheme.general.background)); - f.render_widget(preview_title, rect); - Ok(()) -} - -pub fn draw_preview_content_block( - f: &mut Frame, - rect: Rect, - entry: &Entry, - preview: &Arc, - rendered_preview_cache: &Arc>>, - preview_scroll: u16, - colorscheme: &Colorscheme, -) { + preview_title_spans.push(Span::from(" ")); let preview_outer_block = Block::default() - .title_top(Line::from(" Preview ").alignment(Alignment::Center)) + .title_top( + Line::from(preview_title_spans) + .alignment(Alignment::Center) + .style(Style::default().fg(colorscheme.preview.title_fg)), + ) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(colorscheme.general.border_fg)) .style(Style::default().bg(colorscheme.general.background)) - .padding(Padding::right(1)); + .padding(Padding::new(0, 1, 1, 0)); let preview_inner_block = Block::default().style(Style::default()).padding(Padding { @@ -321,7 +305,7 @@ pub fn draw_preview_content_block( { let p = preview_paragraph.as_ref().clone(); f.render_widget(p.scroll((preview_scroll, 0)), inner); - return; + return Ok(()); } // If not, render the preview content and cache it if not empty let c_scheme = colorscheme.clone(); @@ -343,6 +327,7 @@ pub fn draw_preview_content_block( Arc::new(rp).as_ref().clone().scroll((preview_scroll, 0)), inner, ); + Ok(()) } fn build_line_number_span<'a>(line_number: usize) -> Span<'a> { diff --git a/crates/television-screen/src/results.rs b/crates/television-screen/src/results.rs index 014dfe5..242fed2 100644 --- a/crates/television-screen/src/results.rs +++ b/crates/television-screen/src/results.rs @@ -143,9 +143,17 @@ pub fn draw_results_list( use_nerd_font_icons: bool, icon_color_cache: &mut HashMap, colorscheme: &Colorscheme, + help_keybinding: &str, + preview_keybinding: &str, ) -> Result<()> { let results_block = Block::default() .title_top(Line::from(" Results ").alignment(Alignment::Center)) + .title_bottom( + Line::from(format!( + " help: <{help_keybinding}> preview: <{preview_keybinding}> " + )) + .alignment(Alignment::Center), + ) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(colorscheme.general.border_fg)) diff --git a/crates/television/action.rs b/crates/television/action.rs index b0e9ee5..10cb1c2 100644 --- a/crates/television/action.rs +++ b/crates/television/action.rs @@ -93,6 +93,9 @@ pub enum Action { /// Toggle the help bar. #[serde(alias = "toggle_help")] ToggleHelp, + /// Toggle the preview panel. + #[serde(alias = "toggle_preview")] + TogglePreview, /// Signal an error with the given message. #[serde(skip)] Error(String), diff --git a/crates/television/config/ui.rs b/crates/television/config/ui.rs index e879204..7d3650c 100644 --- a/crates/television/config/ui.rs +++ b/crates/television/config/ui.rs @@ -6,16 +6,17 @@ use television_screen::layout::{InputPosition, PreviewTitlePosition}; use super::themes::DEFAULT_THEME; -const DEFAULT_UI_SCALE: u16 = 90; +const DEFAULT_UI_SCALE: u16 = 100; #[derive(Clone, Debug, Deserialize)] pub struct UiConfig { pub use_nerd_font_icons: bool, pub ui_scale: u16, pub show_help_bar: bool, + pub show_preview_panel: bool, #[serde(default)] pub input_bar_position: InputPosition, - pub preview_title_position: PreviewTitlePosition, + pub preview_title_position: Option, pub theme: String, } @@ -24,9 +25,10 @@ impl Default for UiConfig { Self { use_nerd_font_icons: false, ui_scale: DEFAULT_UI_SCALE, - show_help_bar: true, - input_bar_position: InputPosition::Bottom, - preview_title_position: PreviewTitlePosition::Top, + show_help_bar: false, + show_preview_panel: true, + input_bar_position: InputPosition::Top, + preview_title_position: None, theme: String::from(DEFAULT_THEME), } } @@ -47,13 +49,21 @@ impl From for ValueKind { String::from("show_help_bar"), ValueKind::Boolean(val.show_help_bar).into(), ); + m.insert( + String::from("show_preview_panel"), + ValueKind::Boolean(val.show_preview_panel).into(), + ); m.insert( String::from("input_position"), ValueKind::String(val.input_bar_position.to_string()).into(), ); m.insert( String::from("preview_title_position"), - ValueKind::String(val.preview_title_position.to_string()).into(), + match val.preview_title_position { + Some(pos) => ValueKind::String(pos.to_string()), + None => ValueKind::Nil, + } + .into(), ); m.insert(String::from("theme"), ValueKind::String(val.theme).into()); ValueKind::Table(m) diff --git a/crates/television/television.rs b/crates/television/television.rs index 3113cad..5041c33 100644 --- a/crates/television/television.rs +++ b/crates/television/television.rs @@ -23,9 +23,7 @@ use television_screen::keybindings::{ }; use television_screen::layout::{Dimensions, InputPosition, Layout}; use television_screen::mode::Mode; -use television_screen::preview::{ - draw_preview_content_block, draw_preview_title_block, -}; +use television_screen::preview::draw_preview_content_block; use television_screen::remote_control::draw_remote_control; use television_screen::results::draw_results_list; use television_screen::spinner::{Spinner, SpinnerState}; @@ -386,6 +384,10 @@ impl Television { Action::ToggleHelp => { self.config.ui.show_help_bar = !self.config.ui.show_help_bar; } + Action::TogglePreview => { + self.config.ui.show_preview_panel = + !self.config.ui.show_preview_panel; + } _ => {} } Ok(None) @@ -405,8 +407,8 @@ impl Television { area, !matches!(self.mode, Mode::Channel), self.config.ui.show_help_bar, + self.config.ui.show_preview_panel, self.config.ui.input_bar_position, - self.config.ui.preview_title_position, ); // help bar (metadata, keymaps, logo) @@ -426,7 +428,10 @@ impl Television { self.results_area_height = u32::from(layout.results.height.saturating_sub(2)); // 2 for the borders - self.preview_pane_height = layout.preview_window.height; + self.preview_pane_height = match layout.preview_window { + Some(preview) => preview.height, + None => 0, + }; // results list let result_count = self.channel.result_count(); @@ -447,6 +452,22 @@ impl Television { self.config.ui.use_nerd_font_icons, &mut self.icon_color_cache, &self.colorscheme, + &self + .config + .keybindings + .get(&self.mode) + .unwrap() + .get(&Action::ToggleHelp) + .unwrap() + .to_string(), + &self + .config + .keybindings + .get(&self.mode) + .unwrap() + .get(&Action::TogglePreview) + .unwrap() + .to_string(), )?; // input box @@ -463,38 +484,32 @@ impl Television { &self.colorscheme, )?; - let selected_entry = self - .get_selected_entry(Some(Mode::Channel)) - .unwrap_or(ENTRY_PLACEHOLDER); - let preview = self.previewer.preview(&selected_entry); + if self.config.ui.show_preview_panel { + let selected_entry = self + .get_selected_entry(Some(Mode::Channel)) + .unwrap_or(ENTRY_PLACEHOLDER); - // preview title - self.current_preview_total_lines = preview.total_lines(); - draw_preview_title_block( - f, - layout.preview_title, - &preview, - self.config.ui.use_nerd_font_icons, - &self.colorscheme, - )?; - - // preview content - // initialize preview scroll - self.maybe_init_preview_scroll( - selected_entry - .line_number - .map(|l| u16::try_from(l).unwrap_or(0)), - layout.preview_window.height, - ); - draw_preview_content_block( - f, - layout.preview_window, - &selected_entry, - &preview, - &self.rendered_preview_cache, - self.preview_scroll.unwrap_or(0), - &self.colorscheme, - ); + // preview content + let preview = self.previewer.preview(&selected_entry); + self.current_preview_total_lines = preview.total_lines(); + // initialize preview scroll + self.maybe_init_preview_scroll( + selected_entry + .line_number + .map(|l| u16::try_from(l).unwrap_or(0)), + layout.preview_window.unwrap().height, + ); + draw_preview_content_block( + f, + layout.preview_window.unwrap(), + &selected_entry, + &preview, + &self.rendered_preview_cache, + self.preview_scroll.unwrap_or(0), + self.config.ui.use_nerd_font_icons, + &self.colorscheme, + )?; + } // remote control if matches!(self.mode, Mode::RemoteControl | Mode::SendToChannel) {