refactor(ui): more compact general layout and make preview panel optional (#148)

Fixes #147 

<img width="2552" alt="Screenshot 2024-12-28 at 15 18 42"
src="https://github.com/user-attachments/assets/08e440c2-6878-4a0f-8734-83a8e8b84e5a"
/>
This commit is contained in:
Alex Pasmantier 2024-12-28 15:21:17 +01:00 committed by GitHub
parent ba5b0857c3
commit 499bfdb8e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 142 additions and 138 deletions

View File

@ -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 <TAB>` will open television
# with a list of branches to checkout.
# your shell commands. E.g. typing `git checkout <CTRL-T>` 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
# 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 <CTRL-T>:
# `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"

View File

@ -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))

View File

@ -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,
)

View File

@ -84,8 +84,7 @@ pub struct Layout {
pub help_bar: Option<HelpBarLayout>,
pub results: Rect,
pub input: Rect,
pub preview_title: Rect,
pub preview_window: Rect,
pub preview_window: Option<Rect>,
pub remote_control: Option<Rect>,
}
@ -95,15 +94,13 @@ impl Layout {
help_bar: Option<HelpBarLayout>,
results: Rect,
input: Rect,
preview_title: Rect,
preview_window: Rect,
preview_window: Option<Rect>,
remote_control: Option<Rect>,
) -> 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,
)
}
}

View File

@ -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<Preview>,
rendered_preview_cache: &Arc<Mutex<RenderedPreviewCache<'static>>>,
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<Preview>,
rendered_preview_cache: &Arc<Mutex<RenderedPreviewCache<'static>>>,
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> {

View File

@ -143,9 +143,17 @@ pub fn draw_results_list(
use_nerd_font_icons: bool,
icon_color_cache: &mut HashMap<String, Color>,
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))

View File

@ -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),

View File

@ -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<PreviewTitlePosition>,
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<UiConfig> 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)

View File

@ -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,
)?;
if self.config.ui.show_preview_panel {
let selected_entry = self
.get_selected_entry(Some(Mode::Channel))
.unwrap_or(ENTRY_PLACEHOLDER);
let preview = self.previewer.preview(&selected_entry);
// 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
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.height,
layout.preview_window.unwrap().height,
);
draw_preview_content_block(
f,
layout.preview_window,
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) {