From 85fe9bb38811697a528c5eb27ee5aeda66b65890 Mon Sep 17 00:00:00 2001 From: Cr4ftx Date: Sat, 3 May 2025 22:39:41 +0200 Subject: [PATCH] feat(orientation): add orientation for television #489 --- .config/config.toml | 2 + television/config/ui.rs | 6 ++- television/draw.rs | 5 +- television/screen/input.rs | 13 +++++- television/screen/layout.rs | 91 ++++++++++++++++++++++++------------- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/.config/config.toml b/.config/config.toml index 71e2769..cd7172f 100644 --- a/.config/config.toml +++ b/.config/config.toml @@ -50,6 +50,8 @@ show_help_bar = false show_preview_panel = true # Where to place the input bar in the UI (top or bottom) input_bar_position = "top" +# What orientation should tv be (landscape or portrait) +orientation = "landscape" # 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" diff --git a/television/config/ui.rs b/television/config/ui.rs index e6f853e..1c90cb0 100644 --- a/television/config/ui.rs +++ b/television/config/ui.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; -use crate::screen::layout::{InputPosition, PreviewTitlePosition}; +use crate::screen::layout::{ + InputPosition, Orientation, PreviewTitlePosition, +}; use super::themes::DEFAULT_THEME; @@ -17,6 +19,7 @@ pub struct UiConfig { pub show_preview_panel: bool, #[serde(default)] pub input_bar_position: InputPosition, + pub orientation: Orientation, pub preview_title_position: Option, pub theme: String, pub custom_header: Option, @@ -31,6 +34,7 @@ impl Default for UiConfig { show_help_bar: false, show_preview_panel: true, input_bar_position: InputPosition::Top, + orientation: Orientation::Landscape, preview_title_position: None, theme: String::from(DEFAULT_THEME), custom_header: None, diff --git a/television/draw.rs b/television/draw.rs index 08ac662..5cc9db9 100644 --- a/television/draw.rs +++ b/television/draw.rs @@ -241,12 +241,13 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result { &ctx.tv_state.spinner, &ctx.colorscheme, &ctx.config.ui.custom_header, + &ctx.config.ui.input_bar_position, )?; - if layout.preview_window.is_some() { + if let Some(preview_rect) = layout.preview_window { draw_preview_content_block( f, - layout.preview_window.unwrap(), + preview_rect, &ctx.tv_state.preview_state, ctx.config.ui.use_nerd_font_icons, &ctx.colorscheme, diff --git a/television/screen/input.rs b/television/screen/input.rs index 9735146..6ecbb49 100644 --- a/television/screen/input.rs +++ b/television/screen/input.rs @@ -6,12 +6,16 @@ use ratatui::{ }, style::{Style, Stylize}, text::{Line, Span}, - widgets::{Block, BorderType, Borders, ListState, Paragraph}, + widgets::{ + block::Position, Block, BorderType, Borders, ListState, Paragraph, + }, Frame, }; use crate::screen::{colors::Colorscheme, spinner::Spinner}; +use super::layout::InputPosition; + #[allow(clippy::too_many_arguments)] pub fn draw_input_box( f: &mut Frame, @@ -25,13 +29,18 @@ pub fn draw_input_box( spinner: &Spinner, colorscheme: &Colorscheme, custom_header: &Option, + input_bar_position: &InputPosition, ) -> Result<()> { let header = custom_header.as_deref().unwrap_or(channel_name); let input_block = Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(colorscheme.general.border_fg)) - .title_top( + .title_position(match input_bar_position { + InputPosition::Top => Position::Top, + InputPosition::Bottom => Position::Bottom, + }) + .title( Line::from(String::from(" ") + header + " ") .style(Style::default().fg(colorscheme.mode.channel).bold()) .centered(), diff --git a/television/screen/layout.rs b/television/screen/layout.rs index c2daf5d..10e7611 100644 --- a/television/screen/layout.rs +++ b/television/screen/layout.rs @@ -66,6 +66,17 @@ impl Display for InputPosition { } } +#[derive( + Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Hash, +)] +pub enum Orientation { + #[serde(rename = "landscape")] + #[default] + Landscape, + #[serde(rename = "portrait")] + Portrait, +} + #[derive( Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Hash, )] @@ -168,26 +179,57 @@ impl Layout { help_bar_layout = None; } - // split the main block into 1, 2, or 3 vertical chunks - // (results + preview + remote) - let mut constraints = vec![Constraint::Fill(1)]; - if show_preview { - constraints.push(Constraint::Fill(1)); - } - if show_remote { - // in order to fit with the help bar logo - constraints.push(Constraint::Length(24)); - } - let vt_chunks = layout::Layout::default() + let remote_constraints = if show_remote { + vec![Constraint::Fill(1), Constraint::Length(24)] + } else { + vec![Constraint::Fill(1)] + }; + let remote_chunks = layout::Layout::default() .direction(Direction::Horizontal) - .constraints(constraints) + .constraints(remote_constraints) .split(main_rect); - // left block: results + input field + let remote_control = if show_remote { + Some(remote_chunks[1]) + } else { + None + }; + + // split the main block into 1 or 2 chunks + // (results + preview) + let constraints = if show_preview { + vec![Constraint::Fill(1), Constraint::Fill(1)] + } else { + vec![Constraint::Fill(1)] + }; + + let main_chunks = layout::Layout::default() + .direction(match ui_config.orientation { + Orientation::Portrait => Direction::Vertical, + Orientation::Landscape => Direction::Horizontal, + }) + .constraints(constraints) + .split(remote_chunks[0]); + + // result block: results + input field let results_constraints = vec![Constraint::Min(3), Constraint::Length(3)]; - let left_chunks = layout::Layout::default() + let (result_window, preview_window) = if show_preview { + match (ui_config.orientation, ui_config.input_bar_position) { + (Orientation::Landscape, _) + | (Orientation::Portrait, InputPosition::Top) => { + (main_chunks[0], Some(main_chunks[1])) + } + (Orientation::Portrait, InputPosition::Bottom) => { + (main_chunks[1], Some(main_chunks[0])) + } + } + } else { + (main_chunks[0], None) + }; + + let result_chunks = layout::Layout::default() .direction(Direction::Vertical) .constraints(match ui_config.input_bar_position { InputPosition::Top => { @@ -195,25 +237,10 @@ impl Layout { } InputPosition::Bottom => results_constraints, }) - .split(vt_chunks[0]); + .split(result_window); let (input, results) = match ui_config.input_bar_position { - InputPosition::Bottom => (left_chunks[1], left_chunks[0]), - InputPosition::Top => (left_chunks[0], left_chunks[1]), - }; - - // right block: preview title + preview - let mut remote_idx = 1; - let preview_window = if show_preview { - remote_idx += 1; - Some(vt_chunks[1]) - } else { - None - }; - - let remote_control = if show_remote { - Some(vt_chunks[remote_idx]) - } else { - None + InputPosition::Bottom => (result_chunks[1], result_chunks[0]), + InputPosition::Top => (result_chunks[0], result_chunks[1]), }; Self::new(