mirror of
https://github.com/alexpasmantier/television.git
synced 2025-07-29 14:21:43 +00:00
feat(config): BorderType
This commit is contained in:
parent
39610e145d
commit
784c3df283
@ -54,10 +54,6 @@ use_nerd_font_icons = false
|
||||
# └───────────────────────────────────────┘
|
||||
ui_scale = 100
|
||||
|
||||
# Where to place the input bar in the UI (top or bottom)
|
||||
input_bar_position = "top"
|
||||
# The input prompt string (defaults to ">" if not specified)
|
||||
input_prompt = ">"
|
||||
# What orientation should tv be (landscape or portrait)
|
||||
orientation = "landscape"
|
||||
# The theme to use for the UI
|
||||
@ -100,6 +96,14 @@ remote_control = { enabled = true, visible = false }
|
||||
|
||||
# Feature-specific configurations
|
||||
# Each feature can have its own configuration section
|
||||
[ui.input_bar]
|
||||
# Where to place the input bar in the UI (top or bottom)
|
||||
position = "top"
|
||||
# The input prompt string (defaults to ">" if not specified)
|
||||
prompt = ">"
|
||||
# header = "{}"
|
||||
border_type = "rounded" # https://docs.rs/ratatui/latest/ratatui/widgets/block/enum.BorderType.html#variants
|
||||
|
||||
[ui.status_bar]
|
||||
# Status bar separators (bubble):
|
||||
#separator_open = ""
|
||||
@ -108,12 +112,16 @@ remote_control = { enabled = true, visible = false }
|
||||
separator_open = ""
|
||||
separator_close = ""
|
||||
|
||||
[ui.results_panel]
|
||||
border_type = "rounded"
|
||||
|
||||
[ui.preview_panel]
|
||||
# Preview panel size (percentage of screen width/height)
|
||||
size = 50
|
||||
#header = "{}"
|
||||
#footer = ""
|
||||
scrollbar = true
|
||||
border_type = "rounded"
|
||||
|
||||
[ui.remote_control]
|
||||
# Whether to show channel descriptions in remote control mode
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::cli::parse_source_entry_delimiter;
|
||||
use crate::config::ui::InputBarConfig;
|
||||
use crate::{
|
||||
config::{KeyBindings, ui},
|
||||
event::Key,
|
||||
features::Features,
|
||||
screen::layout::{InputPosition, Orientation},
|
||||
screen::layout::Orientation,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
@ -387,16 +388,14 @@ pub struct UiSpec {
|
||||
// `layout` is clearer for the user but collides with the overall `Layout` type.
|
||||
#[serde(rename = "layout", alias = "orientation", default)]
|
||||
pub orientation: Option<Orientation>,
|
||||
#[serde(default)]
|
||||
pub input_bar_position: Option<InputPosition>,
|
||||
#[serde(default)]
|
||||
pub input_header: Option<Template>,
|
||||
#[serde(default)]
|
||||
pub input_prompt: Option<String>,
|
||||
// Feature-specific configurations
|
||||
#[serde(default)]
|
||||
pub input_bar: Option<InputBarConfig>,
|
||||
#[serde(default)]
|
||||
pub preview_panel: Option<ui::PreviewPanelConfig>,
|
||||
#[serde(default)]
|
||||
pub results_panel: Option<ui::ResultsPanelConfig>,
|
||||
#[serde(default)]
|
||||
pub status_bar: Option<ui::StatusBarConfig>,
|
||||
#[serde(default)]
|
||||
pub help_panel: Option<ui::HelpPanelConfig>,
|
||||
@ -412,10 +411,9 @@ impl From<&crate::config::UiConfig> for UiSpec {
|
||||
ui_scale: Some(config.ui_scale),
|
||||
features: Some(config.features.clone()),
|
||||
orientation: Some(config.orientation),
|
||||
input_bar_position: Some(config.input_bar_position),
|
||||
input_header: config.input_header.clone(),
|
||||
input_prompt: Some(config.input_prompt.clone()),
|
||||
input_bar: Some(config.input_bar.clone()),
|
||||
preview_panel: Some(config.preview_panel.clone()),
|
||||
results_panel: Some(config.results_panel.clone()),
|
||||
status_bar: Some(config.status_bar.clone()),
|
||||
help_panel: Some(config.help_panel.clone()),
|
||||
remote_control: Some(config.remote_control.clone()),
|
||||
@ -425,7 +423,10 @@ impl From<&crate::config::UiConfig> for UiSpec {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{action::Action, event::Key};
|
||||
use crate::{
|
||||
action::Action, config::ui::BorderType, event::Key,
|
||||
screen::layout::InputPosition,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use toml::from_str;
|
||||
@ -508,16 +509,23 @@ mod tests {
|
||||
[ui]
|
||||
layout = "landscape"
|
||||
ui_scale = 100
|
||||
input_bar_position = "bottom"
|
||||
input_header = "Input: {}"
|
||||
|
||||
[ui.features]
|
||||
preview_panel = { enabled = true, visible = true }
|
||||
|
||||
[ui.input_bar]
|
||||
position = "bottom"
|
||||
header = "Input: {}"
|
||||
border_type = "plain"
|
||||
|
||||
[ui.preview_panel]
|
||||
size = 66
|
||||
header = "Preview: {}"
|
||||
footer = "Press 'q' to quit"
|
||||
border_type = "thick"
|
||||
|
||||
[ui.results_panel]
|
||||
border_type = "none"
|
||||
|
||||
[keybindings]
|
||||
esc = "quit"
|
||||
@ -565,29 +573,23 @@ mod tests {
|
||||
assert!(ui.features.is_some());
|
||||
let features = ui.features.as_ref().unwrap();
|
||||
assert!(features.preview_panel.enabled);
|
||||
assert_eq!(ui.input_bar_position, Some(InputPosition::Bottom));
|
||||
assert_eq!(ui.preview_panel.as_ref().unwrap().size, 66);
|
||||
assert_eq!(ui.input_header.as_ref().unwrap().raw(), "Input: {}");
|
||||
let input_bar = ui.input_bar.as_ref().unwrap();
|
||||
assert_eq!(input_bar.position, InputPosition::Bottom);
|
||||
assert_eq!(input_bar.header.as_ref().unwrap().raw(), "Input: {}");
|
||||
assert_eq!(input_bar.border_type, BorderType::Plain);
|
||||
let preview_panel = ui.preview_panel.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
ui.preview_panel
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.header
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.raw(),
|
||||
preview_panel.header.as_ref().unwrap().raw(),
|
||||
"Preview: {}"
|
||||
);
|
||||
assert_eq!(
|
||||
ui.preview_panel
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.footer
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.raw(),
|
||||
preview_panel.footer.as_ref().unwrap().raw(),
|
||||
"Press 'q' to quit"
|
||||
);
|
||||
assert_eq!(preview_panel.border_type, BorderType::Thick);
|
||||
|
||||
assert_eq!(ui.results_panel.unwrap().border_type, BorderType::None);
|
||||
|
||||
let keybindings = prototype.keybindings.unwrap();
|
||||
assert_eq!(
|
||||
@ -734,9 +736,8 @@ mod tests {
|
||||
assert_eq!(ui.orientation, Some(Orientation::Landscape));
|
||||
assert_eq!(ui.ui_scale, Some(40));
|
||||
assert!(ui.features.is_none());
|
||||
assert!(ui.input_bar_position.is_none());
|
||||
assert!(ui.input_bar.is_none());
|
||||
assert!(ui.preview_panel.is_some());
|
||||
assert!(ui.input_header.is_none());
|
||||
assert_eq!(
|
||||
ui.preview_panel
|
||||
.as_ref()
|
||||
|
@ -255,14 +255,14 @@ impl Config {
|
||||
if let Some(value) = ui_spec.orientation {
|
||||
self.ui.orientation = value;
|
||||
}
|
||||
if let Some(value) = ui_spec.input_bar_position {
|
||||
self.ui.input_bar_position = value;
|
||||
}
|
||||
|
||||
// Apply clone fields
|
||||
if let Some(value) = &ui_spec.features {
|
||||
self.ui.features = value.clone();
|
||||
}
|
||||
if let Some(value) = &ui_spec.input_bar {
|
||||
self.ui.input_bar = value.clone();
|
||||
}
|
||||
if let Some(value) = &ui_spec.status_bar {
|
||||
self.ui.status_bar = value.clone();
|
||||
}
|
||||
@ -273,16 +273,6 @@ impl Config {
|
||||
self.ui.remote_control = value.clone();
|
||||
}
|
||||
|
||||
// Apply input_header
|
||||
if let Some(value) = &ui_spec.input_header {
|
||||
self.ui.input_header = Some(value.clone());
|
||||
}
|
||||
|
||||
// Apply input_prompt
|
||||
if let Some(value) = &ui_spec.input_prompt {
|
||||
self.ui.input_prompt.clone_from(value);
|
||||
}
|
||||
|
||||
// Handle preview_panel with field merging
|
||||
if let Some(preview_panel) = &ui_spec.preview_panel {
|
||||
self.ui.preview_panel.size = preview_panel.size;
|
||||
@ -498,8 +488,8 @@ mod tests {
|
||||
}
|
||||
|
||||
const USER_CONFIG_INPUT_PROMPT: &str = r#"
|
||||
[ui]
|
||||
input_prompt = "❯"
|
||||
[ui.input_bar]
|
||||
prompt = "❯"
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
@ -518,7 +508,7 @@ mod tests {
|
||||
let config = Config::new(&config_env, None).unwrap();
|
||||
|
||||
// Verify that input_prompt was loaded from user config
|
||||
assert_eq!(config.ui.input_prompt, "❯");
|
||||
assert_eq!(config.ui.input_bar.prompt, "❯");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -9,6 +9,26 @@ use serde::{Deserialize, Serialize};
|
||||
pub const DEFAULT_UI_SCALE: u16 = 100;
|
||||
pub const DEFAULT_PREVIEW_SIZE: u16 = 50;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash)]
|
||||
#[serde(default)]
|
||||
pub struct InputBarConfig {
|
||||
pub position: InputPosition,
|
||||
pub header: Option<Template>,
|
||||
pub prompt: String,
|
||||
pub border_type: BorderType,
|
||||
}
|
||||
|
||||
impl Default for InputBarConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: InputPosition::default(),
|
||||
header: None,
|
||||
prompt: ">".to_string(),
|
||||
border_type: BorderType::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash, Default)]
|
||||
#[serde(default)]
|
||||
pub struct StatusBarConfig {
|
||||
@ -16,6 +36,12 @@ pub struct StatusBarConfig {
|
||||
pub separator_close: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash, Default)]
|
||||
#[serde(default)]
|
||||
pub struct ResultsPanelConfig {
|
||||
pub border_type: BorderType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash)]
|
||||
#[serde(default)]
|
||||
pub struct PreviewPanelConfig {
|
||||
@ -23,6 +49,7 @@ pub struct PreviewPanelConfig {
|
||||
pub header: Option<Template>,
|
||||
pub footer: Option<Template>,
|
||||
pub scrollbar: bool,
|
||||
pub border_type: BorderType,
|
||||
}
|
||||
|
||||
impl Default for PreviewPanelConfig {
|
||||
@ -32,6 +59,7 @@ impl Default for PreviewPanelConfig {
|
||||
header: None,
|
||||
footer: None,
|
||||
scrollbar: true,
|
||||
border_type: BorderType::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,17 +132,15 @@ pub struct ThemeOverrides {
|
||||
pub struct UiConfig {
|
||||
pub use_nerd_font_icons: bool,
|
||||
pub ui_scale: u16,
|
||||
pub input_bar_position: InputPosition,
|
||||
pub orientation: Orientation,
|
||||
pub theme: String,
|
||||
pub input_header: Option<Template>,
|
||||
#[serde(default = "default_input_prompt")]
|
||||
pub input_prompt: String,
|
||||
pub features: Features,
|
||||
|
||||
// Feature-specific configurations
|
||||
pub input_bar: InputBarConfig,
|
||||
pub status_bar: StatusBarConfig,
|
||||
pub preview_panel: PreviewPanelConfig,
|
||||
pub results_panel: ResultsPanelConfig,
|
||||
pub help_panel: HelpPanelConfig,
|
||||
pub remote_control: RemoteControlConfig,
|
||||
|
||||
@ -123,27 +149,43 @@ pub struct UiConfig {
|
||||
pub theme_overrides: ThemeOverrides,
|
||||
}
|
||||
|
||||
const DEFAULT_INPUT_PROMPT: &str = ">";
|
||||
fn default_input_prompt() -> String {
|
||||
String::from(DEFAULT_INPUT_PROMPT)
|
||||
}
|
||||
|
||||
impl Default for UiConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_nerd_font_icons: false,
|
||||
ui_scale: DEFAULT_UI_SCALE,
|
||||
input_bar_position: InputPosition::Top,
|
||||
orientation: Orientation::Landscape,
|
||||
theme: String::from(DEFAULT_THEME),
|
||||
input_header: None,
|
||||
input_prompt: String::from(DEFAULT_INPUT_PROMPT),
|
||||
features: Features::default(),
|
||||
input_bar: InputBarConfig::default(),
|
||||
status_bar: StatusBarConfig::default(),
|
||||
preview_panel: PreviewPanelConfig::default(),
|
||||
results_panel: ResultsPanelConfig::default(),
|
||||
help_panel: HelpPanelConfig::default(),
|
||||
remote_control: RemoteControlConfig::default(),
|
||||
theme_overrides: ThemeOverrides::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Hash, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BorderType {
|
||||
None,
|
||||
Plain,
|
||||
#[default]
|
||||
Rounded,
|
||||
Thick,
|
||||
}
|
||||
impl BorderType {
|
||||
pub fn to_ratatui_border_type(
|
||||
&self,
|
||||
) -> Option<ratatui::widgets::BorderType> {
|
||||
match self {
|
||||
BorderType::None => None,
|
||||
BorderType::Plain => Some(ratatui::widgets::BorderType::Plain),
|
||||
BorderType::Rounded => Some(ratatui::widgets::BorderType::Rounded),
|
||||
BorderType::Thick => Some(ratatui::widgets::BorderType::Thick),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,14 +191,12 @@ pub fn draw(ctx: Box<Ctx>, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||
&ctx.tv_state.results_picker.entries,
|
||||
&ctx.tv_state.channel_state.selected_entries,
|
||||
&mut ctx.tv_state.results_picker.relative_state.clone(),
|
||||
ctx.config.ui.input_bar_position,
|
||||
ctx.config.ui.input_bar.position,
|
||||
ctx.config.ui.use_nerd_font_icons,
|
||||
&ctx.colorscheme,
|
||||
&ctx.config.ui.results_panel,
|
||||
)?;
|
||||
|
||||
// input box
|
||||
let input_prompt = ctx.config.ui.input_prompt.clone();
|
||||
|
||||
draw_input_box(
|
||||
f,
|
||||
layout.input,
|
||||
@ -210,9 +208,7 @@ pub fn draw(ctx: Box<Ctx>, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||
&ctx.tv_state.channel_state.current_channel_name,
|
||||
&ctx.tv_state.spinner,
|
||||
&ctx.colorscheme,
|
||||
&ctx.config.ui.input_header,
|
||||
&input_prompt,
|
||||
&ctx.config.ui.input_bar_position,
|
||||
&ctx.config.ui.input_bar,
|
||||
)?;
|
||||
|
||||
// status bar at the bottom
|
||||
@ -228,7 +224,7 @@ pub fn draw(ctx: Box<Ctx>, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||
ctx.tv_state.preview_state,
|
||||
ctx.config.ui.use_nerd_font_icons,
|
||||
&ctx.colorscheme,
|
||||
ctx.config.ui.preview_panel.scrollbar,
|
||||
&ctx.config.ui.preview_panel,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,10 @@ use std::env;
|
||||
use std::io::{BufWriter, IsTerminal, Write, stdout};
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use television::cable::cable_empty_exit;
|
||||
use television::{
|
||||
action::Action,
|
||||
app::{App, AppOptions},
|
||||
cable::{Cable, load_cable},
|
||||
cable::{Cable, cable_empty_exit, load_cable},
|
||||
channels::prototypes::{
|
||||
ChannelPrototype, CommandSpec, PreviewSpec, Template, UiSpec,
|
||||
},
|
||||
@ -18,7 +17,7 @@ use television::{
|
||||
args::{Cli, Command},
|
||||
guess_channel_from_prompt, list_channels,
|
||||
},
|
||||
config::{Config, ConfigEnv, merge_bindings},
|
||||
config::{Config, ConfigEnv, merge_bindings, ui::InputBarConfig},
|
||||
errors::os_error_exit,
|
||||
features::FeatureFlags,
|
||||
gh::update_local_channels,
|
||||
@ -212,11 +211,11 @@ fn apply_cli_overrides(args: &PostProcessedCli, config: &mut Config) {
|
||||
config.ui.ui_scale = args.ui_scale.unwrap_or(config.ui.ui_scale);
|
||||
if let Some(input_header) = &args.input_header {
|
||||
if let Ok(t) = Template::parse(input_header) {
|
||||
config.ui.input_header = Some(t);
|
||||
config.ui.input_bar.header = Some(t);
|
||||
}
|
||||
}
|
||||
if let Some(input_prompt) = &args.input_prompt {
|
||||
config.ui.input_prompt.clone_from(input_prompt);
|
||||
config.ui.input_bar.prompt.clone_from(input_prompt);
|
||||
}
|
||||
if let Some(preview_header) = &args.preview_header {
|
||||
if let Ok(t) = Template::parse(preview_header) {
|
||||
@ -330,7 +329,10 @@ fn create_adhoc_channel(
|
||||
|
||||
// Set UI specification
|
||||
let mut ui_spec = UiSpec::from(&config.ui);
|
||||
ui_spec.input_header = Some(input_header);
|
||||
let input_bar = ui_spec
|
||||
.input_bar
|
||||
.get_or_insert_with(InputBarConfig::default);
|
||||
input_bar.header = Some(input_header);
|
||||
ui_spec.features = Some(features);
|
||||
prototype.ui = Some(ui_spec);
|
||||
|
||||
@ -389,10 +391,9 @@ fn apply_ui_overrides(
|
||||
ui_scale: None,
|
||||
features: None,
|
||||
orientation: None,
|
||||
input_bar_position: None,
|
||||
input_header: None,
|
||||
input_prompt: None,
|
||||
input_bar: None,
|
||||
preview_panel: None,
|
||||
results_panel: None,
|
||||
status_bar: None,
|
||||
help_panel: None,
|
||||
remote_control: None,
|
||||
@ -401,14 +402,20 @@ fn apply_ui_overrides(
|
||||
// Apply input header override
|
||||
if let Some(input_header_str) = &args.input_header {
|
||||
if let Ok(template) = Template::parse(input_header_str) {
|
||||
ui_spec.input_header = Some(template);
|
||||
let input_bar = ui_spec
|
||||
.input_bar
|
||||
.get_or_insert_with(InputBarConfig::default);
|
||||
input_bar.header = Some(template);
|
||||
ui_changes_needed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply input prompt override
|
||||
if let Some(input_prompt_str) = &args.input_prompt {
|
||||
ui_spec.input_prompt = Some(input_prompt_str.clone());
|
||||
let input_bar = ui_spec
|
||||
.input_bar
|
||||
.get_or_insert_with(InputBarConfig::default);
|
||||
input_bar.prompt.clone_from(input_prompt_str);
|
||||
ui_changes_needed = true;
|
||||
}
|
||||
|
||||
@ -507,8 +514,12 @@ pub fn determine_channel(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
use television::channels::prototypes::{
|
||||
use television::{
|
||||
channels::prototypes::{
|
||||
ChannelPrototype, CommandSpec, PreviewSpec, Template,
|
||||
},
|
||||
config::ui::{BorderType, InputBarConfig},
|
||||
screen::layout::InputPosition,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -716,7 +727,7 @@ mod tests {
|
||||
// Preview should be disabled since no preview command was provided
|
||||
assert!(!features.is_enabled(FeatureFlags::PreviewPanel));
|
||||
assert_eq!(
|
||||
ui_spec.input_header,
|
||||
ui_spec.input_bar.as_ref().unwrap().header,
|
||||
Some(Template::parse("Custom Channel").unwrap())
|
||||
);
|
||||
}
|
||||
@ -737,7 +748,7 @@ mod tests {
|
||||
assert_eq!(config.application.tick_rate, 100_f64);
|
||||
assert!(!config.ui.features.is_enabled(FeatureFlags::PreviewPanel));
|
||||
assert_eq!(
|
||||
config.ui.input_header,
|
||||
config.ui.input_bar.header,
|
||||
Some(Template::parse("Input Header").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
@ -761,9 +772,12 @@ mod tests {
|
||||
ui_scale: None,
|
||||
features: None,
|
||||
orientation: Some(Orientation::Portrait),
|
||||
input_bar_position: None,
|
||||
input_header: Some(Template::parse("Original Header").unwrap()),
|
||||
input_prompt: None,
|
||||
input_bar: Some(InputBarConfig {
|
||||
position: InputPosition::default(),
|
||||
header: Some(Template::parse("Original Header").unwrap()),
|
||||
prompt: ">".to_string(),
|
||||
border_type: BorderType::Rounded,
|
||||
}),
|
||||
preview_panel: Some(television::config::ui::PreviewPanelConfig {
|
||||
size: 50,
|
||||
header: Some(
|
||||
@ -773,7 +787,9 @@ mod tests {
|
||||
Template::parse("Original Preview Footer").unwrap(),
|
||||
),
|
||||
scrollbar: false,
|
||||
border_type: BorderType::default(),
|
||||
}),
|
||||
results_panel: None,
|
||||
status_bar: None,
|
||||
help_panel: None,
|
||||
remote_control: None,
|
||||
@ -800,7 +816,7 @@ mod tests {
|
||||
let ui_spec = result_channel.ui.as_ref().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
ui_spec.input_header,
|
||||
ui_spec.input_bar.as_ref().unwrap().header,
|
||||
Some(Template::parse("CLI Input Header").unwrap())
|
||||
);
|
||||
assert_eq!(ui_spec.orientation, Some(Orientation::Landscape));
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
channels::prototypes::Template,
|
||||
config::ui::InputBarConfig,
|
||||
screen::{colors::Colorscheme, layout::InputPosition, spinner::Spinner},
|
||||
utils::input::Input,
|
||||
};
|
||||
@ -11,9 +11,7 @@ use ratatui::{
|
||||
},
|
||||
style::{Style, Stylize},
|
||||
text::{Line, Span},
|
||||
widgets::{
|
||||
Block, BorderType, Borders, ListState, Paragraph, block::Position,
|
||||
},
|
||||
widgets::{Block, Borders, ListState, Paragraph, block::Position},
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -28,19 +26,15 @@ pub fn draw_input_box(
|
||||
channel_name: &str,
|
||||
spinner: &Spinner,
|
||||
colorscheme: &Colorscheme,
|
||||
input_header: &Option<Template>,
|
||||
input_prompt: &str,
|
||||
input_bar_position: &InputPosition,
|
||||
input_bar_config: &InputBarConfig,
|
||||
) -> Result<()> {
|
||||
let header = input_header
|
||||
let header = input_bar_config
|
||||
.header
|
||||
.as_ref()
|
||||
.and_then(|tpl| tpl.format(channel_name).ok())
|
||||
.unwrap_or_else(|| channel_name.to_string());
|
||||
let input_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.title_position(match input_bar_position {
|
||||
let mut input_block = Block::default()
|
||||
.title_position(match input_bar_config.position {
|
||||
InputPosition::Top => Position::Top,
|
||||
InputPosition::Bottom => Position::Bottom,
|
||||
})
|
||||
@ -53,6 +47,14 @@ pub fn draw_input_box(
|
||||
Style::default()
|
||||
.bg(colorscheme.general.background.unwrap_or_default()),
|
||||
);
|
||||
if let Some(border_type) =
|
||||
input_bar_config.border_type.to_ratatui_border_type()
|
||||
{
|
||||
input_block = input_block
|
||||
.borders(Borders::ALL)
|
||||
.border_type(border_type)
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg));
|
||||
}
|
||||
|
||||
let input_block_inner = input_block.inner(rect);
|
||||
if input_block_inner.area() == 0 {
|
||||
@ -67,7 +69,8 @@ pub fn draw_input_box(
|
||||
.constraints([
|
||||
// prompt symbol + space
|
||||
Constraint::Length(
|
||||
u16::try_from(input_prompt.chars().count() + 1).unwrap_or(2),
|
||||
u16::try_from(input_bar_config.prompt.chars().count() + 1)
|
||||
.unwrap_or(2),
|
||||
),
|
||||
// input field
|
||||
Constraint::Fill(1),
|
||||
@ -84,7 +87,7 @@ pub fn draw_input_box(
|
||||
|
||||
let arrow_block = Block::default();
|
||||
let arrow = Paragraph::new(Span::styled(
|
||||
format!("{} ", input_prompt),
|
||||
format!("{} ", input_bar_config.prompt),
|
||||
Style::default().fg(colorscheme.input.input_fg).bold(),
|
||||
))
|
||||
.block(arrow_block);
|
||||
|
@ -194,7 +194,7 @@ impl Layout {
|
||||
// results percentage is whatever remains
|
||||
let results_percentage = 100u16.saturating_sub(preview_percentage);
|
||||
|
||||
match (ui_config.orientation, ui_config.input_bar_position) {
|
||||
match (ui_config.orientation, ui_config.input_bar.position) {
|
||||
// Preview is rendered on the right or bottom depending on orientation
|
||||
(Orientation::Landscape, _)
|
||||
| (Orientation::Portrait, InputPosition::Top) => {
|
||||
@ -243,7 +243,7 @@ impl Layout {
|
||||
// Now split the results window vertically into results list + input
|
||||
let result_chunks = layout::Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(match ui_config.input_bar_position {
|
||||
.constraints(match ui_config.input_bar.position {
|
||||
InputPosition::Top => results_constraints
|
||||
.clone()
|
||||
.into_iter()
|
||||
@ -254,7 +254,8 @@ impl Layout {
|
||||
.split(result_window);
|
||||
|
||||
let (input_rect, results_rect) = match ui_config
|
||||
.input_bar_position
|
||||
.input_bar
|
||||
.position
|
||||
{
|
||||
InputPosition::Bottom => {
|
||||
(result_chunks[1], result_chunks[0])
|
||||
@ -278,7 +279,7 @@ impl Layout {
|
||||
|
||||
let mut portrait_constraints: Vec<Constraint> = Vec::new();
|
||||
|
||||
match ui_config.input_bar_position {
|
||||
match ui_config.input_bar.position {
|
||||
InputPosition::Top => {
|
||||
// Input bar is always the first chunk
|
||||
portrait_constraints
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
previewer::state::PreviewState,
|
||||
config::ui::{BorderType, PreviewPanelConfig},
|
||||
previewer::{Preview, state::PreviewState},
|
||||
screen::colors::Colorscheme,
|
||||
utils::strings::{
|
||||
EMPTY_STRING, ReplaceNonPrintableConfig, replace_non_printable,
|
||||
@ -7,13 +8,12 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use devicons::FileIcon;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Alignment, Rect},
|
||||
prelude::{Color, Line, Span, Style, Stylize, Text},
|
||||
widgets::{
|
||||
Block, BorderType, Borders, Clear, Padding, Paragraph, Scrollbar,
|
||||
Block, Borders, Clear, Padding, Paragraph, Scrollbar,
|
||||
ScrollbarOrientation, ScrollbarState, StatefulWidget,
|
||||
},
|
||||
};
|
||||
@ -26,15 +26,14 @@ pub fn draw_preview_content_block(
|
||||
preview_state: PreviewState,
|
||||
use_nerd_font_icons: bool,
|
||||
colorscheme: &Colorscheme,
|
||||
scrollbar_enabled: bool,
|
||||
preview_panel_config: &PreviewPanelConfig,
|
||||
) -> Result<()> {
|
||||
let inner = draw_content_outer_block(
|
||||
f,
|
||||
rect,
|
||||
colorscheme,
|
||||
preview_state.preview.icon,
|
||||
&preview_state.preview.title,
|
||||
&preview_state.preview.footer,
|
||||
&preview_panel_config.border_type,
|
||||
&preview_state.preview,
|
||||
use_nerd_font_icons,
|
||||
)?;
|
||||
let scroll = preview_state.scroll as usize;
|
||||
@ -50,7 +49,7 @@ pub fn draw_preview_content_block(
|
||||
f.render_widget(rp, inner);
|
||||
|
||||
// render scrollbar if enabled
|
||||
if scrollbar_enabled {
|
||||
if preview_panel_config.scrollbar {
|
||||
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||
.style(Style::default().fg(colorscheme.general.border_fg));
|
||||
|
||||
@ -165,15 +164,14 @@ fn draw_content_outer_block(
|
||||
f: &mut Frame,
|
||||
rect: Rect,
|
||||
colorscheme: &Colorscheme,
|
||||
icon: Option<FileIcon>,
|
||||
title: &str,
|
||||
footer: &str,
|
||||
border_type: &BorderType,
|
||||
preview: &Preview,
|
||||
use_nerd_font_icons: bool,
|
||||
) -> Result<Rect> {
|
||||
let mut preview_title_spans = vec![Span::from(" ")];
|
||||
// optional icon
|
||||
if icon.is_some() && use_nerd_font_icons {
|
||||
let icon = icon.as_ref().unwrap();
|
||||
if preview.icon.is_some() && use_nerd_font_icons {
|
||||
let icon = preview.icon.as_ref().unwrap();
|
||||
preview_title_spans.push(Span::styled(
|
||||
{
|
||||
let mut icon_str = String::from(icon.icon);
|
||||
@ -187,7 +185,7 @@ fn draw_content_outer_block(
|
||||
preview_title_spans.push(Span::styled(
|
||||
shrink_with_ellipsis(
|
||||
&replace_non_printable(
|
||||
title.as_bytes(),
|
||||
preview.title.as_bytes(),
|
||||
&ReplaceNonPrintableConfig::default(),
|
||||
)
|
||||
.0,
|
||||
@ -205,10 +203,10 @@ fn draw_content_outer_block(
|
||||
);
|
||||
|
||||
// preview footer
|
||||
if !footer.is_empty() {
|
||||
if !preview.footer.is_empty() {
|
||||
let footer_line = Line::from(vec![
|
||||
Span::from(" "),
|
||||
Span::from(footer),
|
||||
Span::from(preview.footer.as_str()),
|
||||
Span::from(" "),
|
||||
])
|
||||
.alignment(Alignment::Center)
|
||||
@ -216,15 +214,18 @@ fn draw_content_outer_block(
|
||||
block = block.title_bottom(footer_line);
|
||||
}
|
||||
|
||||
let preview_outer_block = block
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
let mut preview_outer_block = block
|
||||
.style(
|
||||
Style::default()
|
||||
.bg(colorscheme.general.background.unwrap_or_default()),
|
||||
)
|
||||
.padding(Padding::new(0, 1, 1, 0));
|
||||
if let Some(border_type) = border_type.to_ratatui_border_type() {
|
||||
preview_outer_block = preview_outer_block
|
||||
.borders(Borders::ALL)
|
||||
.border_type(border_type)
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg));
|
||||
}
|
||||
|
||||
let inner = preview_outer_block.inner(rect);
|
||||
f.render_widget(preview_outer_block, rect);
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
channels::entry::Entry,
|
||||
config::ui::ResultsPanelConfig,
|
||||
screen::{colors::Colorscheme, layout::InputPosition, result_item},
|
||||
};
|
||||
use anyhow::Result;
|
||||
@ -8,7 +9,7 @@ use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
prelude::Style,
|
||||
text::Line,
|
||||
widgets::{Block, BorderType, Borders, ListState, Padding},
|
||||
widgets::{Block, Borders, ListState, Padding},
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
@ -22,17 +23,23 @@ pub fn draw_results_list(
|
||||
input_bar_position: InputPosition,
|
||||
use_nerd_font_icons: bool,
|
||||
colorscheme: &Colorscheme,
|
||||
results_panel_config: &ResultsPanelConfig,
|
||||
) -> Result<()> {
|
||||
let results_block = Block::default()
|
||||
let mut results_block = Block::default()
|
||||
.title_top(Line::from(" Results ").alignment(Alignment::Center))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.style(
|
||||
Style::default()
|
||||
.bg(colorscheme.general.background.unwrap_or_default()),
|
||||
)
|
||||
.padding(Padding::right(1));
|
||||
if let Some(border_type) =
|
||||
results_panel_config.border_type.to_ratatui_border_type()
|
||||
{
|
||||
results_block = results_block
|
||||
.borders(Borders::ALL)
|
||||
.border_type(border_type)
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg));
|
||||
}
|
||||
|
||||
let list_direction = match input_bar_position {
|
||||
InputPosition::Bottom => ratatui::widgets::ListDirection::BottomToTop,
|
||||
|
@ -111,7 +111,7 @@ impl Television {
|
||||
debug!("Merged config: {:?}", config);
|
||||
|
||||
let mut results_picker = Picker::new(input.clone());
|
||||
if config.ui.input_bar_position == InputPosition::Bottom {
|
||||
if config.ui.input_bar.position == InputPosition::Bottom {
|
||||
results_picker = results_picker.inverted();
|
||||
}
|
||||
|
||||
@ -206,9 +206,11 @@ impl Television {
|
||||
// ui
|
||||
if let Some(ui_spec) = &channel_prototype.ui {
|
||||
config.apply_prototype_ui_spec(ui_spec);
|
||||
if config.ui.input_header.is_none() {
|
||||
if let Some(header_tpl) = &ui_spec.input_header {
|
||||
config.ui.input_header = Some(header_tpl.clone());
|
||||
if config.ui.input_bar.header.is_none() {
|
||||
if let Some(input_bar) = &ui_spec.input_bar {
|
||||
if let Some(header_tpl) = &input_bar.header {
|
||||
config.ui.input_bar.header = Some(header_tpl.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.ui.preview_panel.header.is_none() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user