mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-06 19:45:23 +00:00
feat(themes): add support for ui themes (#114)
fixes #80 ### examples | gruvbox | solarized | | :-: | :-: | | <img width="1792" alt="gruvbox" src="https://github.com/user-attachments/assets/c0c168a5-5c95-4113-93fd-24b34a9344d8" /> |  |
This commit is contained in:
parent
a4d15af694
commit
913aa85af0
@ -1,5 +1,17 @@
|
||||
# Ui settings
|
||||
# ----------------------------------------------------------------------------
|
||||
# CONFIGURATION FILE LOCATION ON YOUR SYSTEM:
|
||||
# -------------------------------------------
|
||||
# Defaults:
|
||||
# ---------
|
||||
# Linux: `$HOME/.config/television/config.toml`
|
||||
# macOS: `$HOME/Library/Application Support/television/config.toml`
|
||||
# Windows: `%APPDATA%\television\config.toml`
|
||||
#
|
||||
# XDG dirs:
|
||||
# ---------
|
||||
# You may use XDG_CONFIG_HOME if set on your system.
|
||||
# In that case, television will expect the configuration file to be in:
|
||||
# `$XDG_CONFIG_HOME/television/config.toml`
|
||||
#
|
||||
[ui]
|
||||
# Whether to use nerd font icons in the UI
|
||||
# This option requires a font patched with Nerd Font in order to properly
|
||||
@ -28,6 +40,11 @@ ui_scale = 100
|
||||
show_help_bar = false
|
||||
# Where to place the input bar in the UI (top or bottom)
|
||||
input_bar_position = "bottom"
|
||||
# The theme to use for the UI
|
||||
# A list of builtin themes can be found in the `themes` directory of the television
|
||||
# repository. You may also create your own theme by creating a new file in a `themes`
|
||||
# directory in your configuration directory (see the `config.toml` location above).
|
||||
theme = "gruvbox-dark"
|
||||
|
||||
# Previewers settings
|
||||
# ----------------------------------------------------------------------------
|
||||
@ -35,7 +52,8 @@ input_bar_position = "bottom"
|
||||
# The theme to use for syntax highlighting
|
||||
# A list of available themes can be found in the https://github.com/sharkdp/bat
|
||||
# repository which uses the same syntax highlighting engine as television
|
||||
theme = "Visual Studio Dark+"
|
||||
# You may add your own themes by following the instructions in the bat repository
|
||||
theme = "gruvbox-dark"
|
||||
|
||||
# Keybindings
|
||||
# ----------------------------------------------------------------------------
|
||||
|
93
README.md
93
README.md
@ -89,6 +89,12 @@ It is inspired by the neovim [telescope](https://github.com/nvim-telescope/teles
|
||||
## Usage
|
||||
```bash
|
||||
tv [channel] #[default: files] [possible values: env, files, git-repos, text, alias]
|
||||
|
||||
# piping into tv
|
||||
ls -1a | tv
|
||||
|
||||
# piping into tv with a custom preview command
|
||||
ls -1a | tv --preview-command 'cat {}'
|
||||
```
|
||||
By default, `television` will launch with the `files` channel on.
|
||||
| <img width="2213" alt="Screenshot 2024-11-10 at 15 04 20" src="https://github.com/user-attachments/assets/a0fd70a9-ea26-452a-b235-cbce8aeed67f"> |
|
||||
@ -182,6 +188,32 @@ This would add two new cable channels to `television` available using the remote
|
||||
|
||||
</details>
|
||||
|
||||
## Configuration
|
||||
Default (may be overriden) locations where `television` expect the configuration files to be located for each platform:
|
||||
|
||||
|Platform|Value|
|
||||
|--------|:-----:|
|
||||
|Linux|`$HOME/.config/television/config.toml`|
|
||||
|macOS|`$HOME/Library/Application Support/com.television/config.toml`|
|
||||
|Windows|`{FOLDERID_LocalAppData}\television\config`|
|
||||
|
||||
Or, if you'd rather use the XDG Base Directory Specification, tv will look for the configuration file in
|
||||
`$XDG_CONFIG_HOME/television/config.toml` if the env variable is set.
|
||||
|
||||
**Default configuration: [config.toml](./.config/config.toml)**
|
||||
|
||||
## Themes
|
||||
Builtin themes are available in the [themes](./themes) directory. Feel free to contribute your own themes!
|
||||
|
||||
You may provide your own themes by adding files to a `themes` directory in your configuration folder and then
|
||||
referring to them by file name through the configuration file.
|
||||
```
|
||||
config_location/
|
||||
├── themes/
|
||||
│ └── my_theme.toml
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
## Design (high-level)
|
||||
#### Channels
|
||||
**Television**'s design is primarily based on the concept of **Channels**.
|
||||
@ -208,35 +240,6 @@ contents of a file, the value of an environment variable, etc. Because entries r
|
||||
represent different types of data, **Television** allows for channels to declare the type of previewer that should be
|
||||
used. Television comes with a set of built-in previewers that can be used out of the box and will grow over time.
|
||||
|
||||
## Recipes
|
||||
Here are some examples of how you can use `television` to make your life easier, more productive and fun. You may want to add some of these examples as aliases to your shell configuration file so that you can easily access them.
|
||||
|
||||
**NOTE**: *most of the following examples are meant for macOS. Most of the commands should work on Linux as well, but you may need to adjust them slightly.*
|
||||
|
||||
#### CDing into git repo
|
||||
```bash
|
||||
cd `tv git-repos`
|
||||
```
|
||||
#### Opening file in default editor
|
||||
```bash
|
||||
open `tv`
|
||||
```
|
||||
##### VSCode:
|
||||
```bash
|
||||
code --goto `tv`
|
||||
```
|
||||
##### Vim
|
||||
```bash
|
||||
vim `tv`
|
||||
```
|
||||
at a specific line using the text channel
|
||||
```bash
|
||||
tv text | xargs -oI {} sh -c 'vim "$(echo {} | cut -d ":" -f 1)" +$(echo {} | cut -d ":" -f 2)'
|
||||
```
|
||||
#### Inspecting the current directory
|
||||
```bash
|
||||
ls -1a | tv
|
||||
```
|
||||
|
||||
## Terminal Emulators Compatibility
|
||||
Here is a list of terminal emulators that have currently been tested with `television` and their compatibility status.
|
||||
@ -260,38 +263,6 @@ Here is a list of terminal emulators that have currently been tested with `telev
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
You may wish to customize the behavior of `television` by providing your own configuration file. The configuration file
|
||||
is a simple TOML file that allows you to customize the behavior of `television` in a number of ways.
|
||||
|
||||
Here are default locations where `television` expect the configuration files to be located for each platform:
|
||||
|
||||
|Platform|Value|
|
||||
|--------|:-----:|
|
||||
|Linux|`$HOME/.config/television/config.toml`|
|
||||
|macOS|`$HOME/Library/Application Support/com.television/config.toml`|
|
||||
|Windows|`{FOLDERID_LocalAppData}\television\config`|
|
||||
|
||||
**NOTE**: on either platform, `XDG_CONFIG_HOME` will always take precedence over default locations if set, in which case
|
||||
television will expect the configuration file to be in `$XDG_CONFIG_HOME/television/config.toml`.
|
||||
|
||||
You may also override these default paths by setting the `TELEVISION_CONFIG` environment variable to the path of your desired configuration **folder**.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>
|
||||
Using a custom configuration file location:
|
||||
</summary>
|
||||
|
||||
```bash
|
||||
export TELEVISION_CONFIG=$HOME/.config/television
|
||||
touch $TELEVISION_CONFIG/config.toml
|
||||
```
|
||||
</details>
|
||||
|
||||
#### Default Configuration
|
||||
The default configuration file can be found in the repository's [./.config/config.toml](./.config/config.toml).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions, issues and pull requests are welcome.
|
||||
|
@ -2,10 +2,11 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use devicons::FileIcon;
|
||||
use ratatui::layout::Alignment;
|
||||
use ratatui::prelude::{Line, Style};
|
||||
use ratatui::style::Color;
|
||||
use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding};
|
||||
use television_channels::entry::merge_ranges;
|
||||
use television_channels::entry::{Entry, PreviewType};
|
||||
use television_screen::colors::BORDER_COLOR;
|
||||
use television_screen::colors::ResultsColorscheme;
|
||||
use television_screen::results::build_results_list;
|
||||
pub fn results_list_benchmark(c: &mut Criterion) {
|
||||
let mut icon_color_cache = std::collections::HashMap::default();
|
||||
@ -628,6 +629,15 @@ pub fn results_list_benchmark(c: &mut Criterion) {
|
||||
},
|
||||
];
|
||||
|
||||
let colorscheme = ResultsColorscheme {
|
||||
result_name_fg: Color::Indexed(222),
|
||||
result_preview_fg: Color::Indexed(222),
|
||||
result_line_number_fg: Color::Indexed(222),
|
||||
result_selected_bg: Color::Indexed(222),
|
||||
match_foreground_color: Color::Indexed(222),
|
||||
pointer_fg: Color::Indexed(222),
|
||||
};
|
||||
|
||||
c.bench_function("results_list", |b| {
|
||||
b.iter(|| {
|
||||
build_results_list(
|
||||
@ -637,14 +647,14 @@ pub fn results_list_benchmark(c: &mut Criterion) {
|
||||
)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.border_style(Style::default().fg(Color::Blue))
|
||||
.style(Style::default())
|
||||
.padding(Padding::right(1)),
|
||||
&entries,
|
||||
ListDirection::BottomToTop,
|
||||
None,
|
||||
false,
|
||||
&mut icon_color_cache,
|
||||
&colorscheme,
|
||||
);
|
||||
})
|
||||
});
|
||||
|
@ -1,63 +1,54 @@
|
||||
use ratatui::style::Color;
|
||||
|
||||
pub const BORDER_COLOR: Color = Color::Blue;
|
||||
pub const ACTION_COLOR: Color = Color::DarkGray;
|
||||
// Styles
|
||||
// input
|
||||
pub const DEFAULT_INPUT_FG: Color = Color::LightRed;
|
||||
pub const DEFAULT_RESULTS_COUNT_FG: Color = Color::LightRed;
|
||||
// preview
|
||||
pub const DEFAULT_PREVIEW_TITLE_FG: Color = Color::Blue;
|
||||
pub const DEFAULT_SELECTED_PREVIEW_BG: Color = Color::Rgb(50, 50, 50);
|
||||
pub const DEFAULT_PREVIEW_CONTENT_FG: Color = Color::Rgb(150, 150, 180);
|
||||
pub const DEFAULT_PREVIEW_GUTTER_FG: Color = Color::Rgb(70, 70, 70);
|
||||
pub const DEFAULT_PREVIEW_GUTTER_SELECTED_FG: Color =
|
||||
Color::Rgb(255, 150, 150);
|
||||
// Styles
|
||||
pub const DEFAULT_RESULT_NAME_FG: Color = Color::Blue;
|
||||
pub const DEFAULT_RESULT_PREVIEW_FG: Color = Color::Rgb(150, 150, 150);
|
||||
pub const DEFAULT_RESULT_LINE_NUMBER_FG: Color = Color::Yellow;
|
||||
pub const DEFAULT_RESULT_SELECTED_BG: Color = Color::Rgb(50, 50, 50);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Colorscheme {
|
||||
pub general: GeneralColorscheme,
|
||||
pub help: HelpColorscheme,
|
||||
pub results: ResultsColorscheme,
|
||||
pub preview: PreviewColorscheme,
|
||||
pub input: InputColorscheme,
|
||||
pub mode: ModeColorscheme,
|
||||
}
|
||||
|
||||
pub const DEFAULT_RESULTS_LIST_MATCH_FOREGROUND_COLOR: Color = Color::Red;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GeneralColorscheme {
|
||||
pub border_fg: Color,
|
||||
//pub background: Color,
|
||||
}
|
||||
|
||||
pub struct ResultsListColors {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HelpColorscheme {
|
||||
pub metadata_field_name_fg: Color,
|
||||
pub metadata_field_value_fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResultsColorscheme {
|
||||
pub result_name_fg: Color,
|
||||
pub result_preview_fg: Color,
|
||||
pub result_line_number_fg: Color,
|
||||
pub result_selected_bg: Color,
|
||||
pub match_foreground_color: Color,
|
||||
}
|
||||
|
||||
impl Default for ResultsListColors {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
result_name_fg: DEFAULT_RESULT_NAME_FG,
|
||||
result_preview_fg: DEFAULT_RESULT_PREVIEW_FG,
|
||||
result_line_number_fg: DEFAULT_RESULT_LINE_NUMBER_FG,
|
||||
result_selected_bg: DEFAULT_RESULT_SELECTED_BG,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PreviewColorscheme {
|
||||
pub title_fg: Color,
|
||||
pub highlight_bg: Color,
|
||||
pub content_fg: Color,
|
||||
pub gutter_fg: Color,
|
||||
pub gutter_selected_fg: Color,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ResultsListColors {
|
||||
pub fn result_name_fg(mut self, color: Color) -> Self {
|
||||
self.result_name_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn result_preview_fg(mut self, color: Color) -> Self {
|
||||
self.result_preview_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn result_line_number_fg(mut self, color: Color) -> Self {
|
||||
self.result_line_number_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn result_selected_bg(mut self, color: Color) -> Self {
|
||||
self.result_selected_bg = color;
|
||||
self
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputColorscheme {
|
||||
pub input_fg: Color,
|
||||
pub results_count_fg: Color,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModeColorscheme {
|
||||
pub channel: Color,
|
||||
pub remote_control: Color,
|
||||
pub send_to_channel: Color,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::layout::HelpBarLayout;
|
||||
use crate::colors::BORDER_COLOR;
|
||||
use crate::colors::{Colorscheme, GeneralColorscheme};
|
||||
use crate::logo::build_logo_paragraph;
|
||||
use crate::metadata::build_metadata_table;
|
||||
use crate::mode::{mode_color, Mode};
|
||||
@ -10,12 +10,17 @@ use ratatui::Frame;
|
||||
use television_channels::channels::UnitChannel;
|
||||
use television_utils::metadata::AppMetadata;
|
||||
|
||||
pub fn draw_logo_block(f: &mut Frame, area: Rect, color: Color) {
|
||||
pub fn draw_logo_block(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
mode_color: Color,
|
||||
general_colorscheme: &GeneralColorscheme,
|
||||
) {
|
||||
let logo_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.style(Style::default().fg(color))
|
||||
.border_style(Style::default().fg(general_colorscheme.border_fg))
|
||||
.style(Style::default().fg(mode_color))
|
||||
.padding(Padding::horizontal(1));
|
||||
|
||||
let logo_paragraph = build_logo_paragraph().block(logo_block);
|
||||
@ -29,32 +34,38 @@ fn draw_metadata_block(
|
||||
mode: Mode,
|
||||
current_channel: UnitChannel,
|
||||
app_metadata: &AppMetadata,
|
||||
colorscheme: &Colorscheme,
|
||||
) {
|
||||
let metadata_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Color::Blue))
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.padding(Padding::horizontal(1))
|
||||
.style(Style::default());
|
||||
|
||||
let metadata_table =
|
||||
build_metadata_table(mode, current_channel, app_metadata)
|
||||
build_metadata_table(mode, current_channel, app_metadata, colorscheme)
|
||||
.block(metadata_block);
|
||||
|
||||
f.render_widget(metadata_table, area);
|
||||
}
|
||||
|
||||
fn draw_keymaps_block(f: &mut Frame, area: Rect, keymap_table: Table) {
|
||||
fn draw_keymaps_block(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
keymap_table: Table,
|
||||
colorscheme: &GeneralColorscheme,
|
||||
) {
|
||||
let keymaps_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Color::Blue))
|
||||
.border_style(Style::default().fg(colorscheme.border_fg))
|
||||
.style(Style::default())
|
||||
.padding(Padding::horizontal(1));
|
||||
|
||||
let keymaps_table = keymap_table.block(keymaps_block);
|
||||
let table = keymap_table.block(keymaps_block);
|
||||
|
||||
f.render_widget(keymaps_table, area);
|
||||
f.render_widget(table, area);
|
||||
}
|
||||
|
||||
pub fn draw_help_bar(
|
||||
@ -64,6 +75,7 @@ pub fn draw_help_bar(
|
||||
keymap_table: Table,
|
||||
mode: Mode,
|
||||
app_metadata: &AppMetadata,
|
||||
colorscheme: &Colorscheme,
|
||||
) {
|
||||
if let Some(help_bar) = layout {
|
||||
draw_metadata_block(
|
||||
@ -72,8 +84,19 @@ pub fn draw_help_bar(
|
||||
mode,
|
||||
current_channel,
|
||||
app_metadata,
|
||||
colorscheme,
|
||||
);
|
||||
draw_keymaps_block(
|
||||
f,
|
||||
help_bar.middle,
|
||||
keymap_table,
|
||||
&colorscheme.general,
|
||||
);
|
||||
draw_logo_block(
|
||||
f,
|
||||
help_bar.right,
|
||||
mode_color(mode, &colorscheme.mode),
|
||||
&colorscheme.general,
|
||||
);
|
||||
draw_keymaps_block(f, help_bar.middle, keymap_table);
|
||||
draw_logo_block(f, help_bar.right, mode_color(mode));
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use ratatui::{
|
||||
use television_utils::input::Input;
|
||||
|
||||
use crate::{
|
||||
colors::{BORDER_COLOR, DEFAULT_INPUT_FG, DEFAULT_RESULTS_COUNT_FG},
|
||||
colors::Colorscheme,
|
||||
spinner::{Spinner, SpinnerState},
|
||||
};
|
||||
|
||||
@ -27,12 +27,13 @@ pub fn draw_input_box(
|
||||
matcher_running: bool,
|
||||
spinner: &Spinner,
|
||||
spinner_state: &mut SpinnerState,
|
||||
colorscheme: &Colorscheme,
|
||||
) -> Result<()> {
|
||||
let input_block = Block::default()
|
||||
.title_top(Line::from(" Pattern ").alignment(Alignment::Center))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.style(Style::default());
|
||||
|
||||
let input_block_inner = input_block.inner(rect);
|
||||
@ -62,7 +63,7 @@ pub fn draw_input_box(
|
||||
let arrow_block = Block::default();
|
||||
let arrow = Paragraph::new(Span::styled(
|
||||
"> ",
|
||||
Style::default().fg(DEFAULT_INPUT_FG).bold(),
|
||||
Style::default().fg(colorscheme.input.input_fg).bold(),
|
||||
))
|
||||
.block(arrow_block);
|
||||
f.render_widget(arrow, inner_input_chunks[0]);
|
||||
@ -74,7 +75,12 @@ pub fn draw_input_box(
|
||||
let input = Paragraph::new(input_state.value())
|
||||
.scroll((0, u16::try_from(scroll)?))
|
||||
.block(interactive_input_block)
|
||||
.style(Style::default().fg(DEFAULT_INPUT_FG).bold().italic())
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(colorscheme.input.input_fg)
|
||||
.bold()
|
||||
.italic(),
|
||||
)
|
||||
.alignment(Alignment::Left);
|
||||
f.render_widget(input, inner_input_chunks[1]);
|
||||
|
||||
@ -97,7 +103,9 @@ pub fn draw_input_box(
|
||||
},
|
||||
results_count,
|
||||
),
|
||||
Style::default().fg(DEFAULT_RESULTS_COUNT_FG).italic(),
|
||||
Style::default()
|
||||
.fg(colorscheme.input.results_count_fg)
|
||||
.italic(),
|
||||
))
|
||||
.block(result_count_block)
|
||||
.alignment(Alignment::Right);
|
||||
|
@ -1,9 +1,6 @@
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::{
|
||||
colors::ACTION_COLOR,
|
||||
mode::{Mode, CHANNEL_COLOR, REMOTE_CONTROL_COLOR, SEND_TO_CHANNEL_COLOR},
|
||||
};
|
||||
use crate::{colors::Colorscheme, mode::Mode};
|
||||
use ratatui::{
|
||||
layout::Constraint,
|
||||
style::{Color, Style},
|
||||
@ -48,32 +45,37 @@ impl Display for DisplayableAction {
|
||||
DisplayableAction::Cancel => "Cancel",
|
||||
DisplayableAction::Quit => "Quit",
|
||||
};
|
||||
write!(f, "{}", action)
|
||||
write!(f, "{action}")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_keybindings_table(
|
||||
keybindings: &HashMap<Mode, DisplayableKeybindings>,
|
||||
pub fn build_keybindings_table<'a>(
|
||||
keybindings: &'a HashMap<Mode, DisplayableKeybindings>,
|
||||
mode: Mode,
|
||||
) -> Table<'_> {
|
||||
colorscheme: &'a Colorscheme,
|
||||
) -> Table<'a> {
|
||||
match mode {
|
||||
Mode::Channel => {
|
||||
build_keybindings_table_for_channel(&keybindings[&mode])
|
||||
}
|
||||
Mode::RemoteControl => {
|
||||
build_keybindings_table_for_channel_selection(&keybindings[&mode])
|
||||
}
|
||||
Mode::Channel => build_keybindings_table_for_channel(
|
||||
&keybindings[&mode],
|
||||
colorscheme,
|
||||
),
|
||||
Mode::RemoteControl => build_keybindings_table_for_channel_selection(
|
||||
&keybindings[&mode],
|
||||
colorscheme,
|
||||
),
|
||||
Mode::SendToChannel => {
|
||||
build_keybindings_table_for_channel_transitions(
|
||||
&keybindings[&mode],
|
||||
colorscheme,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_keybindings_table_for_channel(
|
||||
keybindings: &DisplayableKeybindings,
|
||||
) -> Table<'_> {
|
||||
fn build_keybindings_table_for_channel<'a>(
|
||||
keybindings: &'a DisplayableKeybindings,
|
||||
colorscheme: &'a Colorscheme,
|
||||
) -> Table<'a> {
|
||||
// Results navigation
|
||||
let results_navigation_keys = keybindings
|
||||
.bindings
|
||||
@ -82,7 +84,8 @@ fn build_keybindings_table_for_channel(
|
||||
let results_row = Row::new(build_cells_for_group(
|
||||
"Results navigation",
|
||||
results_navigation_keys,
|
||||
CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
// Preview navigation
|
||||
@ -93,7 +96,8 @@ fn build_keybindings_table_for_channel(
|
||||
let preview_row = Row::new(build_cells_for_group(
|
||||
"Preview navigation",
|
||||
preview_navigation_keys,
|
||||
CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
// Select entry
|
||||
@ -104,7 +108,8 @@ fn build_keybindings_table_for_channel(
|
||||
let select_entry_row = Row::new(build_cells_for_group(
|
||||
"Select entry",
|
||||
select_entry_keys,
|
||||
CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
// Copy entry to clipboard
|
||||
@ -115,7 +120,8 @@ fn build_keybindings_table_for_channel(
|
||||
let copy_entry_row = Row::new(build_cells_for_group(
|
||||
"Copy entry to clipboard",
|
||||
copy_entry_keys,
|
||||
CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
// Send to channel
|
||||
@ -126,7 +132,8 @@ fn build_keybindings_table_for_channel(
|
||||
let send_to_channel_row = Row::new(build_cells_for_group(
|
||||
"Send results to",
|
||||
send_to_channel_keys,
|
||||
CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
// Switch channels
|
||||
@ -137,15 +144,20 @@ fn build_keybindings_table_for_channel(
|
||||
let switch_channels_row = Row::new(build_cells_for_group(
|
||||
"Toggle Remote control",
|
||||
switch_channels_keys,
|
||||
CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
// MISC line (quit, help, etc.)
|
||||
// Quit ⏼
|
||||
let quit_keys =
|
||||
keybindings.bindings.get(&DisplayableAction::Quit).unwrap();
|
||||
let quit_row =
|
||||
Row::new(build_cells_for_group("Quit", quit_keys, CHANNEL_COLOR));
|
||||
let quit_row = Row::new(build_cells_for_group(
|
||||
"Quit",
|
||||
quit_keys,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.channel,
|
||||
));
|
||||
|
||||
let widths = vec![Constraint::Fill(1), Constraint::Fill(2)];
|
||||
|
||||
@ -163,9 +175,10 @@ fn build_keybindings_table_for_channel(
|
||||
)
|
||||
}
|
||||
|
||||
fn build_keybindings_table_for_channel_selection(
|
||||
keybindings: &DisplayableKeybindings,
|
||||
) -> Table<'_> {
|
||||
fn build_keybindings_table_for_channel_selection<'a>(
|
||||
keybindings: &'a DisplayableKeybindings,
|
||||
colorscheme: &'a Colorscheme,
|
||||
) -> Table<'a> {
|
||||
// Results navigation
|
||||
let navigation_keys = keybindings
|
||||
.bindings
|
||||
@ -174,7 +187,8 @@ fn build_keybindings_table_for_channel_selection(
|
||||
let results_row = Row::new(build_cells_for_group(
|
||||
"Browse channels",
|
||||
navigation_keys,
|
||||
REMOTE_CONTROL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.remote_control,
|
||||
));
|
||||
|
||||
// Select entry
|
||||
@ -185,7 +199,8 @@ fn build_keybindings_table_for_channel_selection(
|
||||
let select_entry_row = Row::new(build_cells_for_group(
|
||||
"Select channel",
|
||||
select_entry_keys,
|
||||
REMOTE_CONTROL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.remote_control,
|
||||
));
|
||||
|
||||
// Remote control
|
||||
@ -196,7 +211,8 @@ fn build_keybindings_table_for_channel_selection(
|
||||
let switch_channels_row = Row::new(build_cells_for_group(
|
||||
"Toggle Remote control",
|
||||
switch_channels_keys,
|
||||
REMOTE_CONTROL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.remote_control,
|
||||
));
|
||||
|
||||
Table::new(
|
||||
@ -205,9 +221,10 @@ fn build_keybindings_table_for_channel_selection(
|
||||
)
|
||||
}
|
||||
|
||||
fn build_keybindings_table_for_channel_transitions(
|
||||
keybindings: &DisplayableKeybindings,
|
||||
) -> Table<'_> {
|
||||
fn build_keybindings_table_for_channel_transitions<'a>(
|
||||
keybindings: &'a DisplayableKeybindings,
|
||||
colorscheme: &'a Colorscheme,
|
||||
) -> Table<'a> {
|
||||
// Results navigation
|
||||
let results_navigation_keys = keybindings
|
||||
.bindings
|
||||
@ -216,7 +233,8 @@ fn build_keybindings_table_for_channel_transitions(
|
||||
let results_row = Row::new(build_cells_for_group(
|
||||
"Browse channels",
|
||||
results_navigation_keys,
|
||||
SEND_TO_CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.send_to_channel,
|
||||
));
|
||||
|
||||
// Select entry
|
||||
@ -227,7 +245,8 @@ fn build_keybindings_table_for_channel_transitions(
|
||||
let select_entry_row = Row::new(build_cells_for_group(
|
||||
"Send to channel",
|
||||
select_entry_keys,
|
||||
SEND_TO_CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.send_to_channel,
|
||||
));
|
||||
|
||||
// Cancel
|
||||
@ -238,7 +257,8 @@ fn build_keybindings_table_for_channel_transitions(
|
||||
let cancel_row = Row::new(build_cells_for_group(
|
||||
"Cancel",
|
||||
cancel_keys,
|
||||
SEND_TO_CHANNEL_COLOR,
|
||||
colorscheme.help.metadata_field_name_fg,
|
||||
colorscheme.mode.send_to_channel,
|
||||
));
|
||||
|
||||
Table::new(
|
||||
@ -251,23 +271,24 @@ fn build_cells_for_group<'a>(
|
||||
group_name: &str,
|
||||
keys: &'a [String],
|
||||
key_color: Color,
|
||||
value_color: Color,
|
||||
) -> Vec<Cell<'a>> {
|
||||
// group name
|
||||
let mut cells = vec![Cell::from(Span::styled(
|
||||
group_name.to_owned() + ": ",
|
||||
Style::default().fg(ACTION_COLOR),
|
||||
Style::default().fg(key_color),
|
||||
))];
|
||||
|
||||
let spans = keys.iter().skip(1).fold(
|
||||
vec![Span::styled(
|
||||
keys[0].clone(),
|
||||
Style::default().fg(key_color),
|
||||
Style::default().fg(value_color),
|
||||
)],
|
||||
|mut acc, key| {
|
||||
acc.push(Span::raw(" / "));
|
||||
acc.push(Span::styled(
|
||||
key.to_owned(),
|
||||
Style::default().fg(key_color),
|
||||
Style::default().fg(value_color),
|
||||
));
|
||||
acc
|
||||
},
|
||||
|
@ -1,18 +1,18 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::mode::{mode_color, Mode};
|
||||
use crate::{
|
||||
colors::Colorscheme,
|
||||
mode::{mode_color, Mode},
|
||||
};
|
||||
use ratatui::{
|
||||
layout::Constraint,
|
||||
style::{Color, Style},
|
||||
style::Style,
|
||||
text::Span,
|
||||
widgets::{Cell, Row, Table},
|
||||
};
|
||||
use television_channels::channels::UnitChannel;
|
||||
use television_utils::metadata::AppMetadata;
|
||||
|
||||
const METADATA_FIELD_NAME_COLOR: Color = Color::DarkGray;
|
||||
const METADATA_FIELD_VALUE_COLOR: Color = Color::Gray;
|
||||
|
||||
impl Display for Mode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
@ -23,89 +23,90 @@ impl Display for Mode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_metadata_table(
|
||||
pub fn build_metadata_table<'a>(
|
||||
mode: Mode,
|
||||
current_channel: UnitChannel,
|
||||
app_metadata: &AppMetadata,
|
||||
) -> Table<'_> {
|
||||
app_metadata: &'a AppMetadata,
|
||||
colorscheme: &'a Colorscheme,
|
||||
) -> Table<'a> {
|
||||
let version_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"version: ",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
&app_metadata.version,
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
||||
)),
|
||||
]);
|
||||
|
||||
let target_triple_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"target triple: ",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
&app_metadata.build.target_triple,
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
||||
)),
|
||||
]);
|
||||
|
||||
let build_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"build: ",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
&app_metadata.build.rustc_version,
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
" (",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
&app_metadata.build.build_date,
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
")",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
]);
|
||||
|
||||
let current_dir_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"current directory: ",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
std::env::current_dir()
|
||||
.expect("Could not get current directory")
|
||||
.display()
|
||||
.to_string(),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
||||
)),
|
||||
]);
|
||||
|
||||
let current_channel_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"current channel: ",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
current_channel.to_string(),
|
||||
Style::default().fg(METADATA_FIELD_VALUE_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
||||
)),
|
||||
]);
|
||||
|
||||
let current_mode_row = Row::new(vec![
|
||||
Cell::from(Span::styled(
|
||||
"current mode: ",
|
||||
Style::default().fg(METADATA_FIELD_NAME_COLOR),
|
||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
||||
)),
|
||||
Cell::from(Span::styled(
|
||||
mode.to_string(),
|
||||
Style::default().fg(mode_color(mode)),
|
||||
Style::default().fg(mode_color(mode, &colorscheme.mode)),
|
||||
)),
|
||||
]);
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
use ratatui::style::Color;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const CHANNEL_COLOR: Color = Color::Indexed(222);
|
||||
pub const REMOTE_CONTROL_COLOR: Color = Color::Indexed(1);
|
||||
pub const SEND_TO_CHANNEL_COLOR: Color = Color::Indexed(105);
|
||||
use crate::colors::ModeColorscheme;
|
||||
|
||||
pub fn mode_color(mode: Mode) -> Color {
|
||||
pub fn mode_color(mode: Mode, colorscheme: &ModeColorscheme) -> Color {
|
||||
match mode {
|
||||
Mode::Channel => CHANNEL_COLOR,
|
||||
Mode::RemoteControl => REMOTE_CONTROL_COLOR,
|
||||
Mode::SendToChannel => SEND_TO_CHANNEL_COLOR,
|
||||
Mode::Channel => colorscheme.channel,
|
||||
Mode::RemoteControl => colorscheme.remote_control,
|
||||
Mode::SendToChannel => colorscheme.send_to_channel,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
use crate::cache::RenderedPreviewCache;
|
||||
use crate::colors::{
|
||||
BORDER_COLOR, DEFAULT_PREVIEW_CONTENT_FG, DEFAULT_PREVIEW_GUTTER_FG,
|
||||
DEFAULT_PREVIEW_GUTTER_SELECTED_FG, DEFAULT_PREVIEW_TITLE_FG,
|
||||
DEFAULT_SELECTED_PREVIEW_BG,
|
||||
};
|
||||
use crate::colors::{Colorscheme, PreviewColorscheme};
|
||||
use ansi_to_tui::IntoText;
|
||||
use color_eyre::eyre::Result;
|
||||
use ratatui::layout::{Alignment, Rect};
|
||||
@ -12,7 +8,6 @@ use ratatui::widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap};
|
||||
use ratatui::Frame;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use syntect::highlighting::Color as SyntectColor;
|
||||
use television_channels::entry::Entry;
|
||||
use television_previewers::previewers::{
|
||||
Preview, PreviewContent, FILE_TOO_LARGE_MSG, PREVIEW_NOT_SUPPORTED_MSG,
|
||||
@ -27,12 +22,13 @@ const FILL_CHAR_SLANTED: char = '╱';
|
||||
const FILL_CHAR_EMPTY: char = ' ';
|
||||
|
||||
pub fn build_preview_paragraph(
|
||||
preview_block: Block,
|
||||
preview_block: Block<'_>,
|
||||
inner: Rect,
|
||||
preview_content: PreviewContent,
|
||||
target_line: Option<u16>,
|
||||
preview_scroll: u16,
|
||||
) -> Paragraph {
|
||||
colorscheme: Colorscheme,
|
||||
) -> Paragraph<'_> {
|
||||
match preview_content {
|
||||
PreviewContent::AnsiText(text) => {
|
||||
build_ansi_text_paragraph(text, preview_block, preview_scroll)
|
||||
@ -42,9 +38,14 @@ pub fn build_preview_paragraph(
|
||||
preview_block,
|
||||
target_line,
|
||||
preview_scroll,
|
||||
colorscheme.preview,
|
||||
),
|
||||
PreviewContent::PlainTextWrapped(content) => {
|
||||
build_plain_text_wrapped_paragraph(content, preview_block)
|
||||
build_plain_text_wrapped_paragraph(
|
||||
content,
|
||||
preview_block,
|
||||
colorscheme.preview,
|
||||
)
|
||||
}
|
||||
PreviewContent::SyntectHighlightedText(highlighted_lines) => {
|
||||
build_syntect_highlighted_paragraph(
|
||||
@ -52,6 +53,7 @@ pub fn build_preview_paragraph(
|
||||
preview_block,
|
||||
target_line,
|
||||
preview_scroll,
|
||||
colorscheme.preview,
|
||||
)
|
||||
}
|
||||
// meta
|
||||
@ -104,10 +106,11 @@ fn build_ansi_text_paragraph(
|
||||
|
||||
fn build_plain_text_paragraph(
|
||||
text: Vec<String>,
|
||||
preview_block: Block,
|
||||
preview_block: Block<'_>,
|
||||
target_line: Option<u16>,
|
||||
preview_scroll: u16,
|
||||
) -> Paragraph {
|
||||
colorscheme: PreviewColorscheme,
|
||||
) -> Paragraph<'_> {
|
||||
let mut lines = Vec::new();
|
||||
for (i, line) in text.iter().enumerate() {
|
||||
lines.push(Line::from(vec![
|
||||
@ -117,18 +120,18 @@ fn build_plain_text_paragraph(
|
||||
Some(l) if l == u16::try_from(i).unwrap_or(0) + 1
|
||||
)
|
||||
{
|
||||
DEFAULT_PREVIEW_GUTTER_SELECTED_FG
|
||||
colorscheme.gutter_selected_fg
|
||||
} else {
|
||||
DEFAULT_PREVIEW_GUTTER_FG
|
||||
colorscheme.gutter_fg
|
||||
},
|
||||
)),
|
||||
Span::styled(" │ ",
|
||||
Style::default().fg(DEFAULT_PREVIEW_GUTTER_FG).dim()),
|
||||
Style::default().fg(colorscheme.gutter_fg).dim()),
|
||||
Span::styled(
|
||||
line.to_string(),
|
||||
Style::default().fg(DEFAULT_PREVIEW_CONTENT_FG).bg(
|
||||
Style::default().fg(colorscheme.content_fg).bg(
|
||||
if matches!(target_line, Some(l) if l == u16::try_from(i).unwrap() + 1) {
|
||||
DEFAULT_SELECTED_PREVIEW_BG
|
||||
colorscheme.highlight_bg
|
||||
} else {
|
||||
Color::Reset
|
||||
},
|
||||
@ -144,13 +147,14 @@ fn build_plain_text_paragraph(
|
||||
|
||||
fn build_plain_text_wrapped_paragraph(
|
||||
text: String,
|
||||
preview_block: Block,
|
||||
) -> Paragraph {
|
||||
preview_block: Block<'_>,
|
||||
colorscheme: PreviewColorscheme,
|
||||
) -> Paragraph<'_> {
|
||||
let mut lines = Vec::new();
|
||||
for line in text.lines() {
|
||||
lines.push(Line::styled(
|
||||
line.to_string(),
|
||||
Style::default().fg(DEFAULT_PREVIEW_CONTENT_FG),
|
||||
Style::default().fg(colorscheme.content_fg),
|
||||
));
|
||||
}
|
||||
let text = Text::from(lines);
|
||||
@ -164,10 +168,12 @@ fn build_syntect_highlighted_paragraph(
|
||||
preview_block: Block,
|
||||
target_line: Option<u16>,
|
||||
preview_scroll: u16,
|
||||
colorscheme: PreviewColorscheme,
|
||||
) -> Paragraph {
|
||||
compute_paragraph_from_highlighted_lines(
|
||||
&highlighted_lines,
|
||||
target_line.map(|l| l as usize),
|
||||
colorscheme,
|
||||
)
|
||||
.block(preview_block)
|
||||
.alignment(Alignment::Left)
|
||||
@ -232,6 +238,7 @@ pub fn draw_preview_title_block(
|
||||
rect: Rect,
|
||||
preview: &Arc<Preview>,
|
||||
use_nerd_font_icons: bool,
|
||||
colorscheme: &Colorscheme,
|
||||
) -> Result<()> {
|
||||
let mut preview_title_spans = Vec::new();
|
||||
if preview.icon.is_some() && use_nerd_font_icons {
|
||||
@ -254,7 +261,7 @@ pub fn draw_preview_title_block(
|
||||
.0,
|
||||
rect.width.saturating_sub(4) as usize,
|
||||
),
|
||||
Style::default().fg(DEFAULT_PREVIEW_TITLE_FG).bold(),
|
||||
Style::default().fg(colorscheme.preview.title_fg).bold(),
|
||||
));
|
||||
let preview_title = Paragraph::new(Line::from(preview_title_spans))
|
||||
.block(
|
||||
@ -262,9 +269,12 @@ pub fn draw_preview_title_block(
|
||||
.padding(Padding::horizontal(1))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR)),
|
||||
.border_style(
|
||||
Style::default().fg(colorscheme.general.border_fg),
|
||||
),
|
||||
)
|
||||
.alignment(Alignment::Left);
|
||||
.alignment(Alignment::Left)
|
||||
.style(Style::default());
|
||||
f.render_widget(preview_title, rect);
|
||||
Ok(())
|
||||
}
|
||||
@ -276,12 +286,13 @@ pub fn draw_preview_content_block(
|
||||
preview: &Arc<Preview>,
|
||||
rendered_preview_cache: &Arc<Mutex<RenderedPreviewCache<'static>>>,
|
||||
preview_scroll: u16,
|
||||
colorscheme: &Colorscheme,
|
||||
) {
|
||||
let preview_outer_block = Block::default()
|
||||
.title_top(Line::from(" Preview ").alignment(Alignment::Center))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.style(Style::default())
|
||||
.padding(Padding::right(1));
|
||||
|
||||
@ -307,12 +318,14 @@ pub fn draw_preview_content_block(
|
||||
return;
|
||||
}
|
||||
// If not, render the preview content and cache it if not empty
|
||||
let c_scheme = colorscheme.clone();
|
||||
let rp = build_preview_paragraph(
|
||||
preview_inner_block,
|
||||
inner,
|
||||
preview.content.clone(),
|
||||
target_line,
|
||||
preview_scroll,
|
||||
c_scheme,
|
||||
);
|
||||
if !preview.stale {
|
||||
rendered_preview_cache
|
||||
@ -333,6 +346,7 @@ fn build_line_number_span<'a>(line_number: usize) -> Span<'a> {
|
||||
fn compute_paragraph_from_highlighted_lines(
|
||||
highlighted_lines: &[Vec<(syntect::highlighting::Style, String)>],
|
||||
line_specifier: Option<usize>,
|
||||
colorscheme: PreviewColorscheme,
|
||||
) -> Paragraph<'static> {
|
||||
let preview_lines: Vec<Line> = highlighted_lines
|
||||
.iter()
|
||||
@ -343,16 +357,16 @@ fn compute_paragraph_from_highlighted_lines(
|
||||
if line_specifier.is_some()
|
||||
&& i == line_specifier.unwrap().saturating_sub(1)
|
||||
{
|
||||
DEFAULT_PREVIEW_GUTTER_SELECTED_FG
|
||||
colorscheme.gutter_selected_fg
|
||||
} else {
|
||||
DEFAULT_PREVIEW_GUTTER_FG
|
||||
colorscheme.gutter_fg
|
||||
},
|
||||
));
|
||||
Line::from_iter(
|
||||
std::iter::once(line_number)
|
||||
.chain(std::iter::once(Span::styled(
|
||||
" │ ",
|
||||
Style::default().fg(DEFAULT_PREVIEW_GUTTER_FG).dim(),
|
||||
Style::default().fg(colorscheme.gutter_fg).dim(),
|
||||
)))
|
||||
.chain(l.iter().cloned().map(|sr| {
|
||||
convert_syn_region_to_span(
|
||||
@ -362,12 +376,7 @@ fn compute_paragraph_from_highlighted_lines(
|
||||
.unwrap()
|
||||
.saturating_sub(1)
|
||||
{
|
||||
Some(SyntectColor {
|
||||
r: 50,
|
||||
g: 50,
|
||||
b: 50,
|
||||
a: 255,
|
||||
})
|
||||
Some(colorscheme.highlight_bg)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@ -382,12 +391,12 @@ fn compute_paragraph_from_highlighted_lines(
|
||||
|
||||
pub fn convert_syn_region_to_span<'a>(
|
||||
syn_region: &(syntect::highlighting::Style, String),
|
||||
background: Option<syntect::highlighting::Color>,
|
||||
background: Option<Color>,
|
||||
) -> Span<'a> {
|
||||
let mut style = Style::default()
|
||||
.fg(convert_syn_color_to_ratatui_color(syn_region.0.foreground));
|
||||
if let Some(background) = background {
|
||||
style = style.bg(convert_syn_color_to_ratatui_color(background));
|
||||
style = style.bg(background);
|
||||
}
|
||||
style = match syn_region.0.font_style {
|
||||
syntect::highlighting::FontStyle::BOLD => style.bold(),
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::colors::Colorscheme;
|
||||
use crate::logo::build_remote_logo_paragraph;
|
||||
use crate::mode::REMOTE_CONTROL_COLOR;
|
||||
use crate::mode::{mode_color, Mode};
|
||||
use crate::results::build_results_list;
|
||||
use television_channels::entry::Entry;
|
||||
use television_utils::input::Input;
|
||||
|
||||
use crate::colors::{ResultsListColors, BORDER_COLOR, DEFAULT_INPUT_FG};
|
||||
use color_eyre::eyre::Result;
|
||||
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
||||
use ratatui::prelude::Style;
|
||||
@ -17,6 +17,7 @@ use ratatui::widgets::{
|
||||
};
|
||||
use ratatui::Frame;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw_remote_control(
|
||||
f: &mut Frame,
|
||||
rect: Rect,
|
||||
@ -25,6 +26,8 @@ pub fn draw_remote_control(
|
||||
picker_state: &mut ListState,
|
||||
input_state: &mut Input,
|
||||
icon_color_cache: &mut HashMap<String, Color>,
|
||||
mode: &Mode,
|
||||
colorscheme: &Colorscheme,
|
||||
) -> Result<()> {
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
@ -44,9 +47,10 @@ pub fn draw_remote_control(
|
||||
use_nerd_font_icons,
|
||||
picker_state,
|
||||
icon_color_cache,
|
||||
colorscheme,
|
||||
);
|
||||
draw_rc_input(f, layout[1], input_state)?;
|
||||
draw_rc_logo(f, layout[2]);
|
||||
draw_rc_input(f, layout[1], input_state, colorscheme)?;
|
||||
draw_rc_logo(f, layout[2], mode_color(*mode, &colorscheme.mode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -57,11 +61,12 @@ fn draw_rc_channels(
|
||||
use_nerd_font_icons: bool,
|
||||
picker_state: &mut ListState,
|
||||
icon_color_cache: &mut HashMap<String, Color>,
|
||||
colorscheme: &Colorscheme,
|
||||
) {
|
||||
let rc_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.style(Style::default())
|
||||
.padding(Padding::right(1));
|
||||
|
||||
@ -69,22 +74,25 @@ fn draw_rc_channels(
|
||||
rc_block,
|
||||
entries,
|
||||
ListDirection::TopToBottom,
|
||||
Some(
|
||||
ResultsListColors::default().result_name_fg(REMOTE_CONTROL_COLOR),
|
||||
),
|
||||
use_nerd_font_icons,
|
||||
icon_color_cache,
|
||||
&colorscheme.results,
|
||||
);
|
||||
|
||||
f.render_stateful_widget(channel_list, area, picker_state);
|
||||
}
|
||||
|
||||
fn draw_rc_input(f: &mut Frame, area: Rect, input: &mut Input) -> Result<()> {
|
||||
fn draw_rc_input(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
input: &mut Input,
|
||||
colorscheme: &Colorscheme,
|
||||
) -> Result<()> {
|
||||
let input_block = Block::default()
|
||||
.title_top(Line::from("Remote Control").alignment(Alignment::Center))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.style(Style::default());
|
||||
|
||||
let input_block_inner = input_block.inner(area);
|
||||
@ -105,7 +113,7 @@ fn draw_rc_input(f: &mut Frame, area: Rect, input: &mut Input) -> Result<()> {
|
||||
let prompt_symbol_block = Block::default();
|
||||
let arrow = Paragraph::new(Span::styled(
|
||||
"> ",
|
||||
Style::default().fg(DEFAULT_INPUT_FG).bold(),
|
||||
Style::default().fg(colorscheme.input.input_fg).bold(),
|
||||
))
|
||||
.block(prompt_symbol_block);
|
||||
f.render_widget(arrow, inner_input_chunks[0]);
|
||||
@ -117,7 +125,12 @@ fn draw_rc_input(f: &mut Frame, area: Rect, input: &mut Input) -> Result<()> {
|
||||
let input_paragraph = Paragraph::new(input.value())
|
||||
.scroll((0, u16::try_from(scroll)?))
|
||||
.block(interactive_input_block)
|
||||
.style(Style::default().fg(DEFAULT_INPUT_FG).bold().italic())
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(colorscheme.input.input_fg)
|
||||
.bold()
|
||||
.italic(),
|
||||
)
|
||||
.alignment(Alignment::Left);
|
||||
f.render_widget(input_paragraph, inner_input_chunks[1]);
|
||||
|
||||
@ -132,9 +145,8 @@ fn draw_rc_input(f: &mut Frame, area: Rect, input: &mut Input) -> Result<()> {
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
fn draw_rc_logo(f: &mut Frame, area: Rect) {
|
||||
let logo_block =
|
||||
Block::default().style(Style::default().fg(REMOTE_CONTROL_COLOR));
|
||||
fn draw_rc_logo(f: &mut Frame, area: Rect, mode_color: Color) {
|
||||
let logo_block = Block::default().style(Style::default().fg(mode_color));
|
||||
|
||||
let logo_paragraph = build_remote_logo_paragraph()
|
||||
.alignment(Alignment::Center)
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::colors::{
|
||||
ResultsListColors, BORDER_COLOR,
|
||||
DEFAULT_RESULTS_LIST_MATCH_FOREGROUND_COLOR,
|
||||
};
|
||||
use crate::colors::{Colorscheme, ResultsColorscheme};
|
||||
use crate::layout::InputPosition;
|
||||
use color_eyre::eyre::Result;
|
||||
use ratatui::layout::{Alignment, Rect};
|
||||
use ratatui::prelude::{Color, Line, Span, Style};
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::widgets::{
|
||||
Block, BorderType, Borders, List, ListDirection, ListState, Padding,
|
||||
};
|
||||
@ -22,14 +20,13 @@ pub fn build_results_list<'a, 'b>(
|
||||
results_block: Block<'b>,
|
||||
entries: &'a [Entry],
|
||||
list_direction: ListDirection,
|
||||
results_list_colors: Option<ResultsListColors>,
|
||||
use_icons: bool,
|
||||
icon_color_cache: &mut HashMap<String, Color>,
|
||||
colorscheme: &ResultsColorscheme,
|
||||
) -> List<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let results_list_colors = results_list_colors.unwrap_or_default();
|
||||
List::new(entries.iter().map(|entry| {
|
||||
let mut spans = Vec::new();
|
||||
// optional icon
|
||||
@ -67,13 +64,12 @@ where
|
||||
spans.push(Span::styled(
|
||||
slice_at_char_boundaries(&entry_name, last_match_end, start)
|
||||
.to_string(),
|
||||
Style::default().fg(results_list_colors.result_name_fg),
|
||||
Style::default().fg(colorscheme.result_name_fg),
|
||||
));
|
||||
// the current match
|
||||
spans.push(Span::styled(
|
||||
slice_at_char_boundaries(&entry_name, start, end).to_string(),
|
||||
Style::default()
|
||||
.fg(DEFAULT_RESULTS_LIST_MATCH_FOREGROUND_COLOR),
|
||||
Style::default().fg(colorscheme.match_foreground_color),
|
||||
));
|
||||
last_match_end = end;
|
||||
}
|
||||
@ -84,14 +80,14 @@ where
|
||||
let remainder = entry_name[next_boundary..].to_string();
|
||||
spans.push(Span::styled(
|
||||
remainder,
|
||||
Style::default().fg(results_list_colors.result_name_fg),
|
||||
Style::default().fg(colorscheme.result_name_fg),
|
||||
));
|
||||
}
|
||||
// optional line number
|
||||
if let Some(line_number) = entry.line_number {
|
||||
spans.push(Span::styled(
|
||||
format!(":{line_number}"),
|
||||
Style::default().fg(results_list_colors.result_line_number_fg),
|
||||
Style::default().fg(colorscheme.result_line_number_fg),
|
||||
));
|
||||
}
|
||||
// optional preview
|
||||
@ -111,12 +107,11 @@ where
|
||||
spans.push(Span::styled(
|
||||
slice_at_char_boundaries(&preview, last_match_end, start)
|
||||
.to_string(),
|
||||
Style::default().fg(results_list_colors.result_preview_fg),
|
||||
Style::default().fg(colorscheme.result_preview_fg),
|
||||
));
|
||||
spans.push(Span::styled(
|
||||
slice_at_char_boundaries(&preview, start, end).to_string(),
|
||||
Style::default()
|
||||
.fg(DEFAULT_RESULTS_LIST_MATCH_FOREGROUND_COLOR),
|
||||
Style::default().fg(colorscheme.match_foreground_color),
|
||||
));
|
||||
last_match_end = end;
|
||||
}
|
||||
@ -124,7 +119,7 @@ where
|
||||
if next_boundary < preview.len() {
|
||||
spans.push(Span::styled(
|
||||
preview[next_boundary..].to_string(),
|
||||
Style::default().fg(results_list_colors.result_preview_fg),
|
||||
Style::default().fg(colorscheme.result_preview_fg),
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -132,12 +127,13 @@ where
|
||||
}))
|
||||
.direction(list_direction)
|
||||
.highlight_style(
|
||||
Style::default().bg(results_list_colors.result_selected_bg),
|
||||
Style::default().bg(colorscheme.result_selected_bg).bold(),
|
||||
)
|
||||
.highlight_symbol("> ")
|
||||
.block(results_block)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw_results_list(
|
||||
f: &mut Frame,
|
||||
rect: Rect,
|
||||
@ -146,12 +142,13 @@ pub fn draw_results_list(
|
||||
input_bar_position: InputPosition,
|
||||
use_nerd_font_icons: bool,
|
||||
icon_color_cache: &mut HashMap<String, Color>,
|
||||
colorscheme: &Colorscheme,
|
||||
) -> Result<()> {
|
||||
let results_block = Block::default()
|
||||
.title_top(Line::from(" Results ").alignment(Alignment::Center))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(BORDER_COLOR))
|
||||
.border_style(Style::default().fg(colorscheme.general.border_fg))
|
||||
.style(Style::default())
|
||||
.padding(Padding::right(1));
|
||||
|
||||
@ -162,9 +159,9 @@ pub fn draw_results_list(
|
||||
InputPosition::Bottom => ListDirection::BottomToTop,
|
||||
InputPosition::Top => ListDirection::TopToBottom,
|
||||
},
|
||||
None,
|
||||
use_nerd_font_icons,
|
||||
icon_color_cache,
|
||||
&colorscheme.results,
|
||||
);
|
||||
|
||||
f.render_stateful_widget(results_list, rect, relative_picker_state);
|
||||
|
@ -5,12 +5,11 @@ use television_screen::mode::Mode;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::config::parse_key;
|
||||
use crate::config::{parse_key, Config};
|
||||
use crate::keymap::Keymap;
|
||||
use crate::television::Television;
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
event::{Event, EventLoop, Key},
|
||||
render::{render, RenderingTask},
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use color_eyre::Result;
|
||||
use directories::ProjectDirs;
|
||||
pub use keybindings::parse_key;
|
||||
pub use keybindings::KeyBindings;
|
||||
@ -9,12 +9,14 @@ use lazy_static::lazy_static;
|
||||
use previewers::PreviewersConfig;
|
||||
use serde::Deserialize;
|
||||
use styles::Styles;
|
||||
pub use themes::Theme;
|
||||
use tracing::{debug, warn};
|
||||
use ui::UiConfig;
|
||||
|
||||
mod keybindings;
|
||||
mod previewers;
|
||||
mod styles;
|
||||
mod themes;
|
||||
mod ui;
|
||||
|
||||
const CONFIG: &str = include_str!("../../.config/config.toml");
|
||||
@ -64,6 +66,7 @@ lazy_static! {
|
||||
const CONFIG_FILE_NAME: &str = "config.toml";
|
||||
|
||||
impl Config {
|
||||
// FIXME: default management is a bit of a mess right now
|
||||
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
||||
pub fn new() -> Result<Self> {
|
||||
// Load the default_config values as base defaults
|
||||
@ -77,7 +80,8 @@ impl Config {
|
||||
.set_default("data_dir", data_dir.to_str().unwrap())?
|
||||
.set_default("config_dir", config_dir.to_str().unwrap())?
|
||||
.set_default("ui", UiConfig::default())?
|
||||
.set_default("previewers", PreviewersConfig::default())?;
|
||||
.set_default("previewers", PreviewersConfig::default())?
|
||||
.set_default("theme", default_config.ui.theme.clone())?;
|
||||
|
||||
// Load the user's config file
|
||||
let source = config::File::from(config_dir.join(CONFIG_FILE_NAME))
|
||||
@ -87,13 +91,13 @@ impl Config {
|
||||
|
||||
if config_dir.join(CONFIG_FILE_NAME).is_file() {
|
||||
debug!("Found config file at {:?}", config_dir);
|
||||
let mut cfg: Self =
|
||||
builder.build()?.try_deserialize().with_context(|| {
|
||||
format!(
|
||||
"Error parsing config file at {:?}",
|
||||
config_dir.join(CONFIG_FILE_NAME)
|
||||
)
|
||||
})?;
|
||||
let mut cfg: Self = builder.build()?.try_deserialize().unwrap();
|
||||
//.with_context(|| {
|
||||
// format!(
|
||||
// "Error parsing config file at {:?}",
|
||||
// config_dir.join(CONFIG_FILE_NAME)
|
||||
// )
|
||||
//})?;
|
||||
|
||||
for (mode, default_bindings) in default_config.keybindings.iter() {
|
||||
let user_bindings = cfg.keybindings.entry(*mode).or_default();
|
||||
@ -128,7 +132,7 @@ pub fn get_data_dir() -> PathBuf {
|
||||
debug!("Falling back to default data dir");
|
||||
proj_dirs.data_local_dir().to_path_buf()
|
||||
} else {
|
||||
PathBuf::from(".").join(".data")
|
||||
PathBuf::from("../../../../..").join(".data")
|
||||
};
|
||||
directory
|
||||
}
|
||||
@ -141,7 +145,7 @@ pub fn get_config_dir() -> PathBuf {
|
||||
debug!("Falling back to default config dir");
|
||||
proj_dirs.config_local_dir().to_path_buf()
|
||||
} else {
|
||||
PathBuf::from(".").join(".config")
|
||||
PathBuf::from("../../../../..").join("../../../../../.config")
|
||||
};
|
||||
directory
|
||||
}
|
||||
@ -149,24 +153,3 @@ pub fn get_config_dir() -> PathBuf {
|
||||
fn project_directory() -> Option<ProjectDirs> {
|
||||
ProjectDirs::from("com", "", env!("CARGO_PKG_NAME"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::action::Action;
|
||||
use crate::config::keybindings::parse_key;
|
||||
use television_screen::mode::Mode;
|
||||
|
||||
#[test]
|
||||
fn test_config() -> Result<()> {
|
||||
let c = Config::new()?;
|
||||
assert_eq!(
|
||||
c.keybindings
|
||||
.get(&Mode::Channel)
|
||||
.unwrap()
|
||||
.get(&Action::Quit),
|
||||
Some(&parse_key("esc").unwrap())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +39,21 @@ impl From<BasicPreviewerConfig> for ValueKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Default)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct FilePreviewerConfig {
|
||||
//pub max_file_size: u64,
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
impl Default for FilePreviewerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
//max_file_size: 1024 * 1024,
|
||||
theme: String::from("gruvbox-dark"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilePreviewerConfig> for ValueKind {
|
||||
fn from(val: FilePreviewerConfig) -> Self {
|
||||
let mut m = HashMap::new();
|
||||
|
256
crates/television/config/themes.rs
Normal file
256
crates/television/config/themes.rs
Normal file
@ -0,0 +1,256 @@
|
||||
use color_eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ratatui::style::Color as RatatuiColor;
|
||||
use serde::Deserialize;
|
||||
use television_screen::colors::{
|
||||
Colorscheme, GeneralColorscheme, HelpColorscheme, InputColorscheme,
|
||||
ModeColorscheme, PreviewColorscheme, ResultsColorscheme,
|
||||
};
|
||||
|
||||
use super::get_config_dir;
|
||||
|
||||
pub mod builtin;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Color {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
let s = s.trim_start_matches('#');
|
||||
let r = u8::from_str_radix(&s[0..2], 16).ok()?;
|
||||
let g = u8::from_str_radix(&s[2..4], 16).ok()?;
|
||||
let b = u8::from_str_radix(&s[4..6], 16).ok()?;
|
||||
Some(Self { r, g, b })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Theme {
|
||||
// general
|
||||
pub border_fg: Color,
|
||||
pub text_fg: Color,
|
||||
pub dimmed_text_fg: Color,
|
||||
// input
|
||||
pub input_text_fg: Color,
|
||||
pub result_count_fg: Color,
|
||||
// results
|
||||
pub result_name_fg: Color,
|
||||
pub result_line_number_fg: Color,
|
||||
pub result_value_fg: Color,
|
||||
pub selection_bg: Color,
|
||||
pub match_fg: Color,
|
||||
// preview
|
||||
pub preview_title_fg: Color,
|
||||
// modes
|
||||
pub channel_mode_fg: Color,
|
||||
pub remote_control_mode_fg: Color,
|
||||
pub send_to_channel_mode_fg: Color,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn from_name(name: &str) -> Self {
|
||||
Self::from_path(
|
||||
&get_config_dir()
|
||||
.join("themes")
|
||||
.join(name)
|
||||
.with_extension("toml"),
|
||||
)
|
||||
.unwrap_or_else(|_| {
|
||||
Self::from_builtin(name).unwrap_or_else(|_| Self::default())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_builtin(
|
||||
name: &str,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let theme_content: &str = builtin::BUILTIN_THEMES.get(name).map_or(
|
||||
builtin::BUILTIN_THEMES.get(DEFAULT_THEME).unwrap(),
|
||||
|t| *t,
|
||||
);
|
||||
let theme = toml::from_str(theme_content)?;
|
||||
Ok(theme)
|
||||
}
|
||||
|
||||
pub fn from_path(
|
||||
path: &PathBuf,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let theme = std::fs::read_to_string(path)?;
|
||||
let theme: Theme = toml::from_str(&theme)?;
|
||||
Ok(theme)
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_THEME: &str = "gruvbox-dark";
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
let theme_content = include_str!("../../../themes/gruvbox-dark.toml");
|
||||
toml::from_str(theme_content).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename = "theme")]
|
||||
struct Inner {
|
||||
// general
|
||||
border_fg: String,
|
||||
// info
|
||||
text_fg: String,
|
||||
dimmed_text_fg: String,
|
||||
// input
|
||||
input_text_fg: String,
|
||||
result_count_fg: String,
|
||||
//results
|
||||
result_name_fg: String,
|
||||
result_line_number_fg: String,
|
||||
result_value_fg: String,
|
||||
selection_bg: String,
|
||||
match_fg: String,
|
||||
//preview
|
||||
preview_title_fg: String,
|
||||
//modes
|
||||
channel_mode_fg: String,
|
||||
remote_control_mode_fg: String,
|
||||
send_to_channel_mode_fg: String,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Theme {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let inner = Inner::deserialize(deserializer).unwrap();
|
||||
Ok(Self {
|
||||
border_fg: Color::from_str(&inner.border_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
text_fg: Color::from_str(&inner.text_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
dimmed_text_fg: Color::from_str(&inner.dimmed_text_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
input_text_fg: Color::from_str(&inner.input_text_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
result_count_fg: Color::from_str(&inner.result_count_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
result_name_fg: Color::from_str(&inner.result_name_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
result_line_number_fg: Color::from_str(
|
||||
&inner.result_line_number_fg,
|
||||
)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
result_value_fg: Color::from_str(&inner.result_value_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
selection_bg: Color::from_str(&inner.selection_bg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
match_fg: Color::from_str(&inner.match_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
preview_title_fg: Color::from_str(&inner.preview_title_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
channel_mode_fg: Color::from_str(&inner.channel_mode_fg)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
remote_control_mode_fg: Color::from_str(
|
||||
&inner.remote_control_mode_fg,
|
||||
)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
send_to_channel_mode_fg: Color::from_str(
|
||||
&inner.send_to_channel_mode_fg,
|
||||
)
|
||||
.ok_or_else(|| serde::de::Error::custom("invalid color"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<RatatuiColor> for &Color {
|
||||
fn into(self) -> RatatuiColor {
|
||||
RatatuiColor::Rgb(self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<Colorscheme> for &Theme {
|
||||
fn into(self) -> Colorscheme {
|
||||
Colorscheme {
|
||||
general: self.into(),
|
||||
help: self.into(),
|
||||
results: self.into(),
|
||||
preview: self.into(),
|
||||
input: self.into(),
|
||||
mode: self.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<GeneralColorscheme> for &Theme {
|
||||
fn into(self) -> GeneralColorscheme {
|
||||
GeneralColorscheme {
|
||||
border_fg: (&self.border_fg).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<HelpColorscheme> for &Theme {
|
||||
fn into(self) -> HelpColorscheme {
|
||||
HelpColorscheme {
|
||||
metadata_field_name_fg: (&self.dimmed_text_fg).into(),
|
||||
metadata_field_value_fg: (&self.text_fg).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<ResultsColorscheme> for &Theme {
|
||||
fn into(self) -> ResultsColorscheme {
|
||||
ResultsColorscheme {
|
||||
result_name_fg: (&self.result_name_fg).into(),
|
||||
result_preview_fg: (&self.result_value_fg).into(),
|
||||
result_line_number_fg: (&self.result_line_number_fg).into(),
|
||||
result_selected_bg: (&self.selection_bg).into(),
|
||||
match_foreground_color: (&self.match_fg).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<PreviewColorscheme> for &Theme {
|
||||
fn into(self) -> PreviewColorscheme {
|
||||
PreviewColorscheme {
|
||||
title_fg: (&self.preview_title_fg).into(),
|
||||
highlight_bg: (&self.selection_bg).into(),
|
||||
content_fg: (&self.text_fg).into(),
|
||||
gutter_fg: (&self.dimmed_text_fg).into(),
|
||||
gutter_selected_fg: (&self.match_fg).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<InputColorscheme> for &Theme {
|
||||
fn into(self) -> InputColorscheme {
|
||||
InputColorscheme {
|
||||
input_fg: (&self.input_text_fg).into(),
|
||||
results_count_fg: (&self.result_count_fg).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<ModeColorscheme> for &Theme {
|
||||
fn into(self) -> ModeColorscheme {
|
||||
ModeColorscheme {
|
||||
channel: (&self.channel_mode_fg).into(),
|
||||
remote_control: (&self.remote_control_mode_fg).into(),
|
||||
send_to_channel: (&self.send_to_channel_mode_fg).into(),
|
||||
}
|
||||
}
|
||||
}
|
23
crates/television/config/themes/builtin.rs
Normal file
23
crates/television/config/themes/builtin.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BUILTIN_THEMES: HashMap<&'static str, &'static str> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
"gruvbox-dark",
|
||||
include_str!("../../../../themes/gruvbox-dark.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"catppuccin",
|
||||
include_str!("../../../../themes/catppuccin.toml"),
|
||||
);
|
||||
m.insert("nord", include_str!("../../../../themes/nord.toml"));
|
||||
m.insert(
|
||||
"solarized-dark",
|
||||
include_str!("../../../../themes/solarized-dark.toml"),
|
||||
);
|
||||
m
|
||||
};
|
||||
}
|
@ -4,6 +4,8 @@ use std::collections::HashMap;
|
||||
|
||||
use television_screen::layout::InputPosition;
|
||||
|
||||
use super::themes::DEFAULT_THEME;
|
||||
|
||||
const DEFAULT_UI_SCALE: u16 = 90;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
@ -13,6 +15,7 @@ pub struct UiConfig {
|
||||
pub show_help_bar: bool,
|
||||
#[serde(default)]
|
||||
pub input_bar_position: InputPosition,
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
impl Default for UiConfig {
|
||||
@ -22,6 +25,7 @@ impl Default for UiConfig {
|
||||
ui_scale: DEFAULT_UI_SCALE,
|
||||
show_help_bar: true,
|
||||
input_bar_position: InputPosition::Bottom,
|
||||
theme: String::from(DEFAULT_THEME),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,6 +49,7 @@ impl From<UiConfig> for ValueKind {
|
||||
String::from("input_position"),
|
||||
ValueKind::String(val.input_bar_position.to_string()).into(),
|
||||
);
|
||||
m.insert(String::from("theme"), ValueKind::String(val.theme).into());
|
||||
ValueKind::Table(m)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::config::KeyBindings;
|
||||
use crate::action::Action;
|
||||
use crate::config::{Config, KeyBindings, Theme};
|
||||
use crate::input::convert_action_to_input_request;
|
||||
use crate::picker::Picker;
|
||||
use crate::{action::Action, config::Config};
|
||||
use crate::{cable::load_cable_channels, keymap::Keymap};
|
||||
use color_eyre::Result;
|
||||
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||
@ -15,6 +15,7 @@ use television_channels::channels::{
|
||||
use television_channels::entry::{Entry, ENTRY_PLACEHOLDER};
|
||||
use television_previewers::previewers::Previewer;
|
||||
use television_screen::cache::RenderedPreviewCache;
|
||||
use television_screen::colors::Colorscheme;
|
||||
use television_screen::help::draw_help_bar;
|
||||
use television_screen::input::draw_input_box;
|
||||
use television_screen::keybindings::{
|
||||
@ -52,6 +53,7 @@ pub struct Television {
|
||||
pub(crate) spinner: Spinner,
|
||||
pub(crate) spinner_state: SpinnerState,
|
||||
pub app_metadata: AppMetadata,
|
||||
pub colorscheme: Colorscheme,
|
||||
}
|
||||
|
||||
impl Television {
|
||||
@ -77,6 +79,7 @@ impl Television {
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
let colorscheme = (&Theme::from_name(&config.ui.theme)).into();
|
||||
|
||||
channel.find(EMPTY_STRING);
|
||||
let spinner = Spinner::default();
|
||||
@ -104,6 +107,7 @@ impl Television {
|
||||
spinner,
|
||||
spinner_state: SpinnerState::from(&spinner),
|
||||
app_metadata,
|
||||
colorscheme,
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,9 +416,11 @@ impl Television {
|
||||
build_keybindings_table(
|
||||
&self.config.keybindings.to_displayable(),
|
||||
self.mode,
|
||||
&self.colorscheme,
|
||||
),
|
||||
self.mode,
|
||||
&self.app_metadata,
|
||||
&self.colorscheme,
|
||||
);
|
||||
|
||||
self.results_area_height =
|
||||
@ -439,6 +445,7 @@ impl Television {
|
||||
self.config.ui.input_bar_position,
|
||||
self.config.ui.use_nerd_font_icons,
|
||||
&mut self.icon_color_cache,
|
||||
&self.colorscheme,
|
||||
)?;
|
||||
|
||||
// input box
|
||||
@ -452,6 +459,7 @@ impl Television {
|
||||
self.channel.running(),
|
||||
&self.spinner,
|
||||
&mut self.spinner_state,
|
||||
&self.colorscheme,
|
||||
)?;
|
||||
|
||||
let selected_entry = self
|
||||
@ -466,6 +474,7 @@ impl Television {
|
||||
layout.preview_title,
|
||||
&preview,
|
||||
self.config.ui.use_nerd_font_icons,
|
||||
&self.colorscheme,
|
||||
)?;
|
||||
|
||||
// preview content
|
||||
@ -483,6 +492,7 @@ impl Television {
|
||||
&preview,
|
||||
&self.rendered_preview_cache,
|
||||
self.preview_scroll.unwrap_or(0),
|
||||
&self.colorscheme,
|
||||
);
|
||||
|
||||
// remote control
|
||||
@ -505,6 +515,8 @@ impl Television {
|
||||
&mut self.rc_picker.state,
|
||||
&mut self.rc_picker.input,
|
||||
&mut self.icon_color_cache,
|
||||
&self.mode,
|
||||
&self.colorscheme,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
|
19
themes/catppuccin.toml
Normal file
19
themes/catppuccin.toml
Normal file
@ -0,0 +1,19 @@
|
||||
# general
|
||||
border_fg = '#6c7086'
|
||||
text_fg = '#cdd6f4'
|
||||
dimmed_text_fg = '#6c7086'
|
||||
# input
|
||||
input_text_fg = '#f38ba8'
|
||||
result_count_fg = '#f38ba8'
|
||||
# results
|
||||
result_name_fg = '#b4befe'
|
||||
result_line_number_fg = '#f9e2af'
|
||||
result_value_fg = '#a6adc8'
|
||||
selection_bg = '#313244'
|
||||
match_fg = '#f38ba8'
|
||||
# preview
|
||||
preview_title_fg = '#fab387'
|
||||
# modes
|
||||
channel_mode_fg = '#f5c2e7'
|
||||
remote_control_mode_fg = '#a6e3a1'
|
||||
send_to_channel_mode_fg = '#89dceb'
|
19
themes/gruvbox-dark.toml
Normal file
19
themes/gruvbox-dark.toml
Normal file
@ -0,0 +1,19 @@
|
||||
# general
|
||||
border_fg = '#928374'
|
||||
text_fg = '#ebdbb2'
|
||||
dimmed_text_fg = '#a89984'
|
||||
# input
|
||||
input_text_fg = '#fb4934'
|
||||
result_count_fg = '#cc241d'
|
||||
# results
|
||||
result_name_fg = '#83a598'
|
||||
result_line_number_fg = '#fabd2f'
|
||||
result_value_fg = '#ebdbb2'
|
||||
selection_bg = '#32302f'
|
||||
match_fg = '#fb4934'
|
||||
# preview
|
||||
preview_title_fg = '#b8bb26'
|
||||
# modes
|
||||
channel_mode_fg = '#b16286'
|
||||
remote_control_mode_fg = '#8ec07c'
|
||||
send_to_channel_mode_fg = '#458588'
|
19
themes/nord.toml
Normal file
19
themes/nord.toml
Normal file
@ -0,0 +1,19 @@
|
||||
# general
|
||||
border_fg = '#434c5e'
|
||||
text_fg = '#d8dee9'
|
||||
dimmed_text_fg = '#4c566a'
|
||||
# input
|
||||
input_text_fg = '#bf616a'
|
||||
result_count_fg = '#bf616a'
|
||||
# results
|
||||
result_name_fg = '#81a1c1'
|
||||
result_line_number_fg = '#ebcb8b'
|
||||
result_value_fg = '#d8dee9'
|
||||
selection_bg = '#3b4252'
|
||||
match_fg = '#bf616a'
|
||||
# preview
|
||||
preview_title_fg = '#8fbcbb'
|
||||
# modes
|
||||
channel_mode_fg = '#b48ead'
|
||||
remote_control_mode_fg = '#a3be8c'
|
||||
send_to_channel_mode_fg = '#d08770'
|
19
themes/solarized-dark.toml
Normal file
19
themes/solarized-dark.toml
Normal file
@ -0,0 +1,19 @@
|
||||
# general
|
||||
border_fg = '#586e75'
|
||||
text_fg = '#eee8d5'
|
||||
dimmed_text_fg = '#93a1a1'
|
||||
# input
|
||||
input_text_fg = '#cb4b16'
|
||||
result_count_fg = '#cb4b16'
|
||||
# results
|
||||
result_name_fg = '#268bd2'
|
||||
result_line_number_fg = '#b58900'
|
||||
result_value_fg = '#657b83'
|
||||
selection_bg = '#073642'
|
||||
match_fg = '#cb4b16'
|
||||
# preview
|
||||
preview_title_fg = '#859900'
|
||||
# modes
|
||||
channel_mode_fg = '#2aa198'
|
||||
remote_control_mode_fg = '#859900'
|
||||
send_to_channel_mode_fg = '#dc322f'
|
Loading…
x
Reference in New Issue
Block a user