diff --git a/television/channels/prototypes.rs b/television/channels/prototypes.rs index 6a77581..b05100f 100644 --- a/television/channels/prototypes.rs +++ b/television/channels/prototypes.rs @@ -739,7 +739,10 @@ mod tests { assert_eq!(ui.orientation, Some(Orientation::Landscape)); assert_eq!(ui.ui_scale, Some(40)); assert!(ui.features.is_none()); - assert_eq!(ui.input_bar.as_ref().unwrap().border_type, BorderType::None); + assert_eq!( + ui.input_bar.as_ref().unwrap().border_type, + BorderType::None + ); assert!(ui.preview_panel.is_some()); assert_eq!( ui.preview_panel diff --git a/television/cli/args.rs b/television/cli/args.rs index 6d45370..889bfa4 100644 --- a/television/cli/args.rs +++ b/television/cli/args.rs @@ -33,7 +33,7 @@ pub struct Cli { /// A list of the available channels can be displayed using the /// `list-channels` command. The channel can also be changed from within /// the application. - #[arg(value_enum, index = 1, verbatim_doc_comment)] + #[arg(index = 1, verbatim_doc_comment)] pub channel: Option, /// A preview line number offset template to use to scroll the preview to for each @@ -151,6 +151,20 @@ pub struct Cli { #[arg(long, value_name = "STRING", verbatim_doc_comment)] pub input_prompt: Option, + /// Sets the input panel border type. + /// + /// Available options are: `none`, `plain`, `rounded`, `thick`. + #[arg(long, value_enum, verbatim_doc_comment)] + pub input_border: Option, + + /// Sets the input panel padding. + /// + /// Format: `top=INTEGER;left=INTEGER;bottom=INTEGER;right=INTEGER` + /// + /// Example: `--input-padding='top=1;left=2;bottom=1;right=2'` + #[arg(long, value_name = "STRING", verbatim_doc_comment)] + pub input_padding: Option, + /// Preview header template /// /// When a channel is specified: This overrides the header defined in the channel prototype. @@ -181,6 +195,30 @@ pub struct Cli { )] pub preview_footer: Option, + /// Sets the preview panel border type. + /// + /// Available options are: `none`, `plain`, `rounded`, `thick`. + #[arg( + long, + value_enum, + verbatim_doc_comment, + conflicts_with = "no_preview" + )] + pub preview_border: Option, + + /// Sets the preview panel padding. + /// + /// Format: `top=INTEGER;left=INTEGER;bottom=INTEGER;right=INTEGER` + /// + /// Example: `--preview-padding='top=1;left=2;bottom=1;right=2'` + #[arg( + long, + value_name = "STRING", + verbatim_doc_comment, + conflicts_with = "no_preview" + )] + pub preview_padding: Option, + /// Source command to use for the current channel. /// /// When a channel is specified: This overrides the command defined in the channel prototype. @@ -220,6 +258,20 @@ pub struct Cli { #[arg(long, value_name = "STRING", verbatim_doc_comment)] pub source_output: Option, + /// Sets the results panel border type. + /// + /// Available options are: `none`, `plain`, `rounded`, `thick`. + #[arg(long, value_enum, verbatim_doc_comment)] + pub results_border: Option, + + /// Sets the results panel padding. + /// + /// Format: `top=INTEGER;left=INTEGER;bottom=INTEGER;right=INTEGER` + /// + /// Example: `--results-padding='top=1;left=2;bottom=1;right=2'` + #[arg(long, value_name = "STRING", verbatim_doc_comment)] + pub results_padding: Option, + /// The delimiter byte to use for splitting the source's command output into entries. /// /// This can be useful when the source command outputs multiline entries and you want to @@ -509,6 +561,14 @@ pub enum LayoutOrientation { Portrait, } +#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)] +pub enum BorderType { + None, + Plain, + Rounded, + Thick, +} + // Add validator functions fn validate_positive_float(s: &str) -> Result { match s.parse::() { diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 8a2a199..3d3a507 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -4,6 +4,7 @@ use crate::{ cli::args::{Cli, Command}, config::{ DEFAULT_PREVIEW_SIZE, KeyBindings, get_config_dir, get_data_dir, + ui::{BorderType, Padding}, }, errors::cli_parsing_error_exit, screen::layout::Orientation, @@ -62,6 +63,12 @@ pub struct PostProcessedCli { pub preview_size: Option, pub preview_header: Option, pub preview_footer: Option, + pub preview_border: Option, + pub preview_padding: Option, + + // Results panel configuration + pub results_border: Option, + pub results_padding: Option, // Status bar configuration pub no_status_bar: bool, @@ -82,6 +89,8 @@ pub struct PostProcessedCli { pub input: Option, pub input_header: Option, pub input_prompt: Option, + pub input_border: Option, + pub input_padding: Option, // UI and layout configuration pub layout: Option, @@ -112,6 +121,8 @@ pub struct PostProcessedCli { pub command: Option, } +const DEFAULT_BORDER_TYPE: BorderType = BorderType::Rounded; + impl Default for PostProcessedCli { fn default() -> Self { Self { @@ -134,6 +145,12 @@ impl Default for PostProcessedCli { preview_size: Some(DEFAULT_PREVIEW_SIZE), preview_header: None, preview_footer: None, + preview_border: Some(DEFAULT_BORDER_TYPE), + preview_padding: None, + + // Results panel configuration + results_border: Some(DEFAULT_BORDER_TYPE), + results_padding: None, // Status bar configuration no_status_bar: false, @@ -154,6 +171,8 @@ impl Default for PostProcessedCli { input: None, input_header: None, input_prompt: None, + input_border: Some(DEFAULT_BORDER_TYPE), + input_padding: None, // UI and layout configuration layout: None, @@ -300,11 +319,26 @@ pub fn post_process(cli: Cli, readable_stdin: bool) -> PostProcessedCli { }); // Determine layout - let layout: Option = - cli.layout.map(|layout_enum| match layout_enum { - args::LayoutOrientation::Landscape => Orientation::Landscape, - args::LayoutOrientation::Portrait => Orientation::Portrait, - }); + let layout: Option = cli.layout.map(Orientation::from); + + // borders + let input_border = cli.input_border.map(BorderType::from); + let results_border = cli.results_border.map(BorderType::from); + let preview_border = cli.preview_border.map(BorderType::from); + + // padding + let input_padding = cli.input_padding.map(|p| { + parse_padding_literal(&p, CLI_PADDING_DELIMITER) + .unwrap_or_else(|e| cli_parsing_error_exit(&e.to_string())) + }); + let results_padding = cli.results_padding.map(|p| { + parse_padding_literal(&p, CLI_PADDING_DELIMITER) + .unwrap_or_else(|e| cli_parsing_error_exit(&e.to_string())) + }); + let preview_padding = cli.preview_padding.map(|p| { + parse_padding_literal(&p, CLI_PADDING_DELIMITER) + .unwrap_or_else(|e| cli_parsing_error_exit(&e.to_string())) + }); PostProcessedCli { // Channel and source configuration @@ -326,6 +360,12 @@ pub fn post_process(cli: Cli, readable_stdin: bool) -> PostProcessedCli { preview_size: cli.preview_size, preview_header: cli.preview_header, preview_footer: cli.preview_footer, + preview_border, + preview_padding, + + // Results configuration + results_border, + results_padding, // Status bar configuration no_status_bar: cli.no_status_bar, @@ -346,6 +386,8 @@ pub fn post_process(cli: Cli, readable_stdin: bool) -> PostProcessedCli { input: cli.input, input_header: cli.input_header, input_prompt: cli.input_prompt, + input_border, + input_padding, // UI and layout configuration layout, @@ -432,6 +474,7 @@ fn validate_adhoc_mode_constraints(cli: &Cli, readable_stdin: bool) { } const CLI_KEYBINDINGS_DELIMITER: char = ';'; +const CLI_PADDING_DELIMITER: char = ';'; /// Parse a keybindings literal into a `KeyBindings` struct. /// @@ -452,6 +495,17 @@ fn parse_keybindings_literal( toml::from_str(&toml_definition).map_err(|e| anyhow!(e)) } +/// Parses the CLI padding values into `config::ui::Padding` +fn parse_padding_literal( + cli_padding: &str, + delimiter: char, +) -> Result { + let toml_definition = cli_padding + .split(delimiter) + .fold(String::new(), |acc, p| acc + p + "\n"); + toml::from_str(&toml_definition).map_err(|e| anyhow!(e)) +} + pub fn list_channels

(cable_dir: P) where P: AsRef, diff --git a/television/config/mod.rs b/television/config/mod.rs index 6e2a0e1..f1e4a6e 100644 --- a/television/config/mod.rs +++ b/television/config/mod.rs @@ -281,12 +281,14 @@ impl Config { .input_bar .border_type .clone_from(&input_bar.border_type); + self.ui.input_bar.padding = input_bar.padding; } if let Some(results_panel) = &ui_spec.results_panel { self.ui .results_panel .border_type .clone_from(&results_panel.border_type); + self.ui.results_panel.padding = results_panel.padding; } if let Some(preview_panel) = &ui_spec.preview_panel { self.ui.preview_panel.size = preview_panel.size; @@ -301,6 +303,7 @@ impl Config { .preview_panel .border_type .clone_from(&preview_panel.border_type); + self.ui.preview_panel.padding = preview_panel.padding; } } } @@ -372,6 +375,7 @@ pub use ui::{DEFAULT_PREVIEW_SIZE, DEFAULT_UI_SCALE}; #[cfg(test)] mod tests { use crate::action::Action; + use crate::config::ui::Padding; use crate::event::Key; use super::*; @@ -619,9 +623,11 @@ mod tests { header: Some(Template::Raw("hello".to_string())), prompt: "world".to_string(), border_type: BorderType::Thick, + padding: Padding::uniform(2), }), results_panel: Some(ResultsPanelConfig { border_type: BorderType::None, + padding: Padding::uniform(2), }), preview_panel: Some(PreviewPanelConfig { size: 42, @@ -629,6 +635,7 @@ mod tests { footer: Some(Template::Raw("moo".to_string())), scrollbar: true, border_type: BorderType::Plain, + padding: Padding::uniform(2), }), status_bar: Some(StatusBarConfig { separator_open: "open".to_string(), @@ -653,7 +660,11 @@ mod tests { ); assert_eq!(config.ui.input_bar.prompt, "world"); assert_eq!(config.ui.input_bar.border_type, BorderType::Thick); + assert_eq!(config.ui.input_bar.padding, Padding::uniform(2)); + assert_eq!(config.ui.results_panel.border_type, BorderType::None); + assert_eq!(config.ui.results_panel.padding, Padding::uniform(2)); + assert_eq!(config.ui.preview_panel.size, 42); assert_eq!( config.ui.preview_panel.header.as_ref().unwrap().raw(), @@ -665,6 +676,8 @@ mod tests { ); assert!(config.ui.preview_panel.scrollbar); assert_eq!(config.ui.preview_panel.border_type, BorderType::Plain); + assert_eq!(config.ui.preview_panel.padding, Padding::uniform(2)); + assert_eq!(config.ui.status_bar.separator_open, "open"); assert_eq!(config.ui.status_bar.separator_close, "close"); assert!(config.ui.remote_control.show_channel_descriptions); diff --git a/television/config/ui.rs b/television/config/ui.rs index 5730d06..9bd31a5 100644 --- a/television/config/ui.rs +++ b/television/config/ui.rs @@ -16,6 +16,7 @@ pub struct InputBarConfig { pub header: Option