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
# 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"

View File

@ -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<PreviewTitlePosition>,
pub theme: String,
pub custom_header: Option<String>,
@ -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,

View File

@ -241,12 +241,13 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
&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,

View File

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

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(
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(