mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-06 03:25:23 +00:00
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:
parent
ba5b0857c3
commit
499bfdb8e5
@ -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
|
||||
# `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 <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"
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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))
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user