feat(orientation): add orientation for television #489

This commit is contained in:
Cr4ftx 2025-05-03 22:39:41 +02:00
parent be8008e97d
commit 85fe9bb388
5 changed files with 80 additions and 37 deletions

View File

@ -50,6 +50,8 @@ show_help_bar = false
show_preview_panel = true show_preview_panel = true
# Where to place the input bar in the UI (top or bottom) # Where to place the input bar in the UI (top or bottom)
input_bar_position = "top" 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 # 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) # Where to place the preview title in the UI (top or bottom)
# preview_title_position = "top" # preview_title_position = "top"

View File

@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::screen::layout::{InputPosition, PreviewTitlePosition}; use crate::screen::layout::{
InputPosition, Orientation, PreviewTitlePosition,
};
use super::themes::DEFAULT_THEME; use super::themes::DEFAULT_THEME;
@ -17,6 +19,7 @@ pub struct UiConfig {
pub show_preview_panel: bool, pub show_preview_panel: bool,
#[serde(default)] #[serde(default)]
pub input_bar_position: InputPosition, pub input_bar_position: InputPosition,
pub orientation: Orientation,
pub preview_title_position: Option<PreviewTitlePosition>, pub preview_title_position: Option<PreviewTitlePosition>,
pub theme: String, pub theme: String,
pub custom_header: Option<String>, pub custom_header: Option<String>,
@ -31,6 +34,7 @@ impl Default for UiConfig {
show_help_bar: false, show_help_bar: false,
show_preview_panel: true, show_preview_panel: true,
input_bar_position: InputPosition::Top, input_bar_position: InputPosition::Top,
orientation: Orientation::Landscape,
preview_title_position: None, preview_title_position: None,
theme: String::from(DEFAULT_THEME), theme: String::from(DEFAULT_THEME),
custom_header: None, custom_header: None,

View File

@ -241,12 +241,13 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
&ctx.tv_state.spinner, &ctx.tv_state.spinner,
&ctx.colorscheme, &ctx.colorscheme,
&ctx.config.ui.custom_header, &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( draw_preview_content_block(
f, f,
layout.preview_window.unwrap(), preview_rect,
&ctx.tv_state.preview_state, &ctx.tv_state.preview_state,
ctx.config.ui.use_nerd_font_icons, ctx.config.ui.use_nerd_font_icons,
&ctx.colorscheme, &ctx.colorscheme,

View File

@ -6,12 +6,16 @@ use ratatui::{
}, },
style::{Style, Stylize}, style::{Style, Stylize},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, BorderType, Borders, ListState, Paragraph}, widgets::{
block::Position, Block, BorderType, Borders, ListState, Paragraph,
},
Frame, Frame,
}; };
use crate::screen::{colors::Colorscheme, spinner::Spinner}; use crate::screen::{colors::Colorscheme, spinner::Spinner};
use super::layout::InputPosition;
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn draw_input_box( pub fn draw_input_box(
f: &mut Frame, f: &mut Frame,
@ -25,13 +29,18 @@ pub fn draw_input_box(
spinner: &Spinner, spinner: &Spinner,
colorscheme: &Colorscheme, colorscheme: &Colorscheme,
custom_header: &Option<String>, custom_header: &Option<String>,
input_bar_position: &InputPosition,
) -> Result<()> { ) -> Result<()> {
let header = custom_header.as_deref().unwrap_or(channel_name); let header = custom_header.as_deref().unwrap_or(channel_name);
let input_block = Block::default() let input_block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(Style::default().fg(colorscheme.general.border_fg)) .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 + " ") Line::from(String::from(" ") + header + " ")
.style(Style::default().fg(colorscheme.mode.channel).bold()) .style(Style::default().fg(colorscheme.mode.channel).bold())
.centered(), .centered(),

View File

@ -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( #[derive(
Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Hash, Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Hash,
)] )]
@ -168,26 +179,57 @@ impl Layout {
help_bar_layout = None; help_bar_layout = None;
} }
// split the main block into 1, 2, or 3 vertical chunks let remote_constraints = if show_remote {
// (results + preview + remote) vec![Constraint::Fill(1), Constraint::Length(24)]
let mut constraints = vec![Constraint::Fill(1)]; } else {
if show_preview { vec![Constraint::Fill(1)]
constraints.push(Constraint::Fill(1)); };
} let remote_chunks = layout::Layout::default()
if show_remote {
// in order to fit with the help bar logo
constraints.push(Constraint::Length(24));
}
let vt_chunks = layout::Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints(constraints) .constraints(remote_constraints)
.split(main_rect); .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 = let results_constraints =
vec![Constraint::Min(3), Constraint::Length(3)]; 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) .direction(Direction::Vertical)
.constraints(match ui_config.input_bar_position { .constraints(match ui_config.input_bar_position {
InputPosition::Top => { InputPosition::Top => {
@ -195,25 +237,10 @@ impl Layout {
} }
InputPosition::Bottom => results_constraints, InputPosition::Bottom => results_constraints,
}) })
.split(vt_chunks[0]); .split(result_window);
let (input, results) = match ui_config.input_bar_position { let (input, results) = match ui_config.input_bar_position {
InputPosition::Bottom => (left_chunks[1], left_chunks[0]), InputPosition::Bottom => (result_chunks[1], result_chunks[0]),
InputPosition::Top => (left_chunks[0], left_chunks[1]), InputPosition::Top => (result_chunks[0], result_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
}; };
Self::new( Self::new(