fix(themes): selection_fg was not applied correctly + code improvements

Fixes #636
This commit is contained in:
alexandre pasmantier 2025-07-13 00:21:39 +02:00
parent 1bbda8e62f
commit fb97f011be
18 changed files with 85 additions and 67 deletions

View File

@ -7,7 +7,9 @@ use ratatui::layout::Alignment;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::prelude::{Line, Style}; use ratatui::prelude::{Line, Style};
use ratatui::style::Color; use ratatui::style::Color;
use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding}; use ratatui::widgets::{
Block, BorderType, Borders, ListDirection, ListState, Padding,
};
use television::channels::prototypes::ChannelPrototype; use television::channels::prototypes::ChannelPrototype;
use television::picker::Movement; use television::picker::Movement;
use television::{ use television::{
@ -390,9 +392,7 @@ pub fn draw_results_list(c: &mut Criterion) {
]; ];
let colorscheme = ResultsColorscheme { let colorscheme = ResultsColorscheme {
result_name_fg: Color::Indexed(222), result_fg: Color::Indexed(222),
result_preview_fg: Color::Indexed(222),
result_line_number_fg: Color::Indexed(222),
result_selected_fg: Color::Indexed(222), result_selected_fg: Color::Indexed(222),
result_selected_bg: Color::Indexed(222), result_selected_bg: Color::Indexed(222),
match_foreground_color: Color::Indexed(222), match_foreground_color: Color::Indexed(222),
@ -411,6 +411,7 @@ pub fn draw_results_list(c: &mut Criterion) {
.style(Style::default()) .style(Style::default())
.padding(Padding::right(1)), .padding(Padding::right(1)),
&entries, &entries,
&ListState::default(),
ListDirection::BottomToTop, ListDirection::BottomToTop,
false, false,
&colorscheme, &colorscheme,

View File

@ -477,9 +477,7 @@ impl Into<HelpColorscheme> for &Theme {
impl Into<ResultsColorscheme> for &Theme { impl Into<ResultsColorscheme> for &Theme {
fn into(self) -> ResultsColorscheme { fn into(self) -> ResultsColorscheme {
ResultsColorscheme { ResultsColorscheme {
result_name_fg: (&self.result_name_fg).into(), result_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(), result_selected_bg: (&self.selection_bg).into(),
result_selected_fg: (&self.selection_fg).into(), result_selected_fg: (&self.selection_fg).into(),
match_foreground_color: (&self.match_fg).into(), match_foreground_color: (&self.match_fg).into(),

View File

@ -24,9 +24,7 @@ pub struct HelpColorscheme {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ResultsColorscheme { pub struct ResultsColorscheme {
pub result_name_fg: Color, pub result_fg: Color,
pub result_preview_fg: Color,
pub result_line_number_fg: Color,
pub result_selected_bg: Color, pub result_selected_bg: Color,
pub result_selected_fg: Color, pub result_selected_fg: Color,
pub match_foreground_color: Color, pub match_foreground_color: Color,

View File

@ -255,6 +255,7 @@ fn draw_rc_channels(
let channel_list = result_item::build_results_list( let channel_list = result_item::build_results_list(
rc_block, rc_block,
entries, entries,
picker_state,
ListDirection::TopToBottom, ListDirection::TopToBottom,
use_nerd_font_icons, use_nerd_font_icons,
&colorscheme.results, &colorscheme.results,

View File

@ -13,7 +13,7 @@ use devicons::FileIcon;
use ratatui::{ use ratatui::{
prelude::{Color, Line, Span, Style}, prelude::{Color, Line, Span, Style},
style::Stylize, style::Stylize,
widgets::{Block, List, ListDirection}, widgets::{Block, List, ListDirection, ListState},
}; };
use std::str::FromStr; use std::str::FromStr;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -42,12 +42,17 @@ pub trait ResultItem {
/// Build a single `Line` for a [`ResultItem`]. /// Build a single `Line` for a [`ResultItem`].
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
// TODO: pass the right colors directly as arguments and make the
// calling function responsible for the colors used for each line.
pub fn build_result_line<'a, T: ResultItem + ?Sized>( pub fn build_result_line<'a, T: ResultItem + ?Sized>(
item: &'a T, item: &'a T,
use_icons: bool, use_icons: bool,
colorscheme: &ResultsColorscheme, selection_fg: Color,
result_fg: Color,
match_fg: Color,
area_width: u16, area_width: u16,
prefix: Option<bool>, // Some(true)=selected ●, Some(false)=unselected, None=no prefix // Some(true)=selected ●, Some(false)=unselected, None=no prefix
prefix: Option<bool>,
) -> Line<'a> { ) -> Line<'a> {
// PERF: Pre-allocate spans vector with estimated capacity // PERF: Pre-allocate spans vector with estimated capacity
let mut spans = Vec::<Span<'a>>::with_capacity(16); let mut spans = Vec::<Span<'a>>::with_capacity(16);
@ -57,7 +62,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
if selected { if selected {
spans.push(Span::styled( spans.push(Span::styled(
SELECTED_SYMBOL, SELECTED_SYMBOL,
Style::default().fg(colorscheme.result_selected_fg), Style::default().fg(selection_fg),
)); ));
} else { } else {
spans.push(Span::raw(DESELECTED_SYMBOL)); spans.push(Span::raw(DESELECTED_SYMBOL));
@ -111,10 +116,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
// PERF: Early return for empty match ranges - common case optimization // PERF: Early return for empty match ranges - common case optimization
if match_ranges.is_empty() { if match_ranges.is_empty() {
spans.push(Span::styled( spans.push(Span::styled(entry_name, Style::default().fg(result_fg)));
entry_name,
Style::default().fg(colorscheme.result_name_fg),
));
} else { } else {
// PERF: Collect chars once to avoid repeated Unicode parsing // PERF: Collect chars once to avoid repeated Unicode parsing
let chars: Vec<char> = entry_name.chars().collect(); let chars: Vec<char> = entry_name.chars().collect();
@ -131,7 +133,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
if !text.is_empty() { if !text.is_empty() {
spans.push(Span::styled( spans.push(Span::styled(
text, text,
Style::default().fg(colorscheme.result_name_fg), Style::default().fg(result_fg),
)); ));
} }
} }
@ -143,8 +145,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
if !text.is_empty() { if !text.is_empty() {
spans.push(Span::styled( spans.push(Span::styled(
text, text,
Style::default() Style::default().fg(match_fg),
.fg(colorscheme.match_foreground_color),
)); ));
} }
} }
@ -156,10 +157,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
if last_end < name_len { if last_end < name_len {
let text: String = chars[last_end..].iter().collect(); let text: String = chars[last_end..].iter().collect();
if !text.is_empty() { if !text.is_empty() {
spans.push(Span::styled( spans.push(Span::styled(text, Style::default().fg(result_fg)));
text,
Style::default().fg(colorscheme.result_name_fg),
));
} }
} }
} }
@ -170,7 +168,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
match binding { match binding {
Binding::SingleKey(k) => spans.push(Span::styled( Binding::SingleKey(k) => spans.push(Span::styled(
k.to_string(), k.to_string(),
Style::default().fg(colorscheme.match_foreground_color), Style::default().fg(match_fg),
)), )),
Binding::MultipleKeys(keys) => { Binding::MultipleKeys(keys) => {
for (i, k) in keys.iter().enumerate() { for (i, k) in keys.iter().enumerate() {
@ -179,8 +177,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
} }
spans.push(Span::styled( spans.push(Span::styled(
k.to_string(), k.to_string(),
Style::default() Style::default().fg(match_fg),
.fg(colorscheme.match_foreground_color),
)); ));
} }
} }
@ -195,6 +192,7 @@ pub fn build_result_line<'a, T: ResultItem + ?Sized>(
pub fn build_results_list<'a, 'b, T, F>( pub fn build_results_list<'a, 'b, T, F>(
block: Block<'b>, block: Block<'b>,
entries: &'a [T], entries: &'a [T],
relative_picker_state: &ListState,
list_direction: ListDirection, list_direction: ListDirection,
use_icons: bool, use_icons: bool,
colorscheme: &ResultsColorscheme, colorscheme: &ResultsColorscheme,
@ -206,10 +204,22 @@ where
T: ResultItem, T: ResultItem,
F: FnMut(&T) -> Option<bool>, F: FnMut(&T) -> Option<bool>,
{ {
use ratatui::widgets::List; List::new(entries.iter().enumerate().map(|(i, e)| {
List::new(entries.iter().map(|e| {
let prefix = prefix_fn(e); let prefix = prefix_fn(e);
build_result_line(e, use_icons, colorscheme, area_width, prefix) let result_fg = if relative_picker_state.selected() == Some(i) {
colorscheme.result_selected_fg
} else {
colorscheme.result_fg
};
build_result_line(
e,
use_icons,
colorscheme.result_selected_fg,
result_fg,
colorscheme.match_foreground_color,
area_width,
prefix,
)
})) }))
.direction(list_direction) .direction(list_direction)
.highlight_style( .highlight_style(
@ -223,7 +233,6 @@ where
mod tests { mod tests {
use super::*; use super::*;
use crate::channels::entry::Entry; use crate::channels::entry::Entry;
use crate::screen::colors::ResultsColorscheme;
use ratatui::prelude::{Color, Span}; use ratatui::prelude::{Color, Span};
use ratatui::text::Line; use ratatui::text::Line;
@ -234,7 +243,9 @@ mod tests {
let line = build_result_line( let line = build_result_line(
&entry, &entry,
false, false,
&ResultsColorscheme::default(), Color::Reset,
Color::Reset,
Color::Reset,
200, 200,
None, None,
); );
@ -258,7 +269,9 @@ mod tests {
let line = build_result_line( let line = build_result_line(
&entry, &entry,
false, false,
&ResultsColorscheme::default(), Color::Reset,
Color::Reset,
Color::Reset,
20, // small width 20, // small width
None, None,
); );

View File

@ -44,6 +44,7 @@ pub fn draw_results_list(
let results_list = result_item::build_results_list( let results_list = result_item::build_results_list(
results_block, results_block,
entries, entries,
relative_picker_state,
list_direction, list_direction,
use_nerd_font_icons, use_nerd_font_icons,
&colorscheme.results, &colorscheme.results,

View File

@ -69,7 +69,7 @@ pub fn draw_status_bar(f: &mut Frame<'_>, area: Rect, ctx: &Ctx) {
// Add channel-specific info in Channel mode // Add channel-specific info in Channel mode
if ctx.tv_state.mode == Mode::Channel { if ctx.tv_state.mode == Mode::Channel {
let name_style = Style::default() let name_style = Style::default()
.fg(ctx.colorscheme.results.result_name_fg) .fg(ctx.colorscheme.results.result_fg)
.add_modifier(Modifier::BOLD); .add_modifier(Modifier::BOLD);
// Channel name // Channel name
@ -89,7 +89,7 @@ pub fn draw_status_bar(f: &mut Frame<'_>, area: Rect, ctx: &Ctx) {
Span::styled( Span::styled(
format!("{} selected", selected_count), format!("{} selected", selected_count),
Style::default() Style::default()
.fg(ctx.colorscheme.results.result_name_fg) .fg(ctx.colorscheme.results.result_fg)
.add_modifier(Modifier::ITALIC), .add_modifier(Modifier::ITALIC),
), ),
]); ]);
@ -191,7 +191,7 @@ pub fn draw_status_bar(f: &mut Frame<'_>, area: Rect, ctx: &Ctx) {
let right_spans = vec![Span::styled( let right_spans = vec![Span::styled(
format!("v{} ", ctx.app_metadata.version), format!("v{} ", ctx.app_metadata.version),
Style::default() Style::default()
.fg(ctx.colorscheme.results.result_name_fg) .fg(ctx.colorscheme.results.result_fg)
.add_modifier(Modifier::ITALIC), .add_modifier(Modifier::ITALIC),
)]; )];

View File

@ -20,4 +20,3 @@ channel_mode_fg = '#1e1e2e'
channel_mode_bg = '#f5c2e7' channel_mode_bg = '#f5c2e7'
remote_control_mode_fg = '#1e1e2e' remote_control_mode_fg = '#1e1e2e'
remote_control_mode_bg = '#a6e3a1' remote_control_mode_bg = '#a6e3a1'
send_to_channel_mode_fg = '#89dceb'

View File

@ -16,6 +16,8 @@ match_fg = '#FF5555'
# preview # preview
preview_title_fg = '#50FA7B' preview_title_fg = '#50FA7B'
# modes # modes
channel_mode_fg = '#8BE9FD' channel_mode_fg = '#282A36'
remote_control_mode_fg = '#FFB86C' channel_mode_bg = '#8BE9FD'
remote_control_mode_fg = '#282A36'
remote_control_mode_bg = '#FFB86C'
send_to_channel_mode_fg = '#FF79C6' send_to_channel_mode_fg = '#FF79C6'

View File

@ -20,4 +20,3 @@ channel_mode_fg = '#282828'
channel_mode_bg = '#b16286' channel_mode_bg = '#b16286'
remote_control_mode_fg = '#282828' remote_control_mode_fg = '#282828'
remote_control_mode_bg = '#8ec07c' remote_control_mode_bg = '#8ec07c'
send_to_channel_mode_fg = '#458588'

View File

@ -16,7 +16,7 @@ match_fg = '#af3a03'
# preview # preview
preview_title_fg = '#98971a' preview_title_fg = '#98971a'
# modes # modes
channel_mode_fg = '#d65d0e' channel_mode_fg = '#504945'
remote_control_mode_fg = '#689d6a' channel_mode_bg = '#d65d0e'
send_to_channel_mode_fg = '#458588' remote_control_mode_fg = '#504945'
remote_control_mode_bg = '#d79921'

View File

@ -16,6 +16,7 @@ match_fg = '#f92672'
# preview # preview
preview_title_fg = '#fd971f' preview_title_fg = '#fd971f'
# modes # modes
channel_mode_fg = '#fd971f' channel_mode_fg = '#2e2e2e'
remote_control_mode_fg = '#a6e22e' channel_mode_bg = '#fd971f'
send_to_channel_mode_fg = '#66d9ef' remote_control_mode_fg = '#2e2e2e'
remote_control_mode_bg = '#a6e22e'

View File

@ -16,6 +16,7 @@ match_fg = '#bf616a'
# preview # preview
preview_title_fg = '#8fbcbb' preview_title_fg = '#8fbcbb'
# modes # modes
channel_mode_fg = '#b48ead' channel_mode_fg = '#2e3440'
remote_control_mode_fg = '#a3be8c' channel_mode_bg = '#b48ead'
send_to_channel_mode_fg = '#d08770' remote_control_mode_fg = '#2e3440'
remote_control_mode_bg = '#a3be8c'

View File

@ -16,6 +16,7 @@ match_fg = '#e06c75'
# preview # preview
preview_title_fg = '#61afef' preview_title_fg = '#61afef'
# modes # modes
channel_mode_fg = '#61afef' channel_mode_fg = '#2c323c'
remote_control_mode_fg = '#98c379' channel_mode_bg = '#61afef'
send_to_channel_mode_fg = '#c678dd' remote_control_mode_fg = '#2c323c'
remote_control_mode_bg = '#98c379'

View File

@ -16,6 +16,7 @@ match_fg = '#cb4b16'
# preview # preview
preview_title_fg = '#859900' preview_title_fg = '#859900'
# modes # modes
channel_mode_fg = '#2aa198' channel_mode_fg = '#002b36'
remote_control_mode_fg = '#859900' channel_mode_bg = '#2aa198'
send_to_channel_mode_fg = '#dc322f' remote_control_mode_fg = '#002b36'
remote_control_mode_bg = '#b58900'

View File

@ -16,6 +16,7 @@ match_fg = '#cb4b16'
# preview # preview
preview_title_fg = '#859900' preview_title_fg = '#859900'
# modes # modes
channel_mode_fg = '#2aa198' channel_mode_fg = '#fdf6e3'
remote_control_mode_fg = '#859900' channel_mode_bg = '#2aa198'
send_to_channel_mode_fg = '#dc322f' remote_control_mode_fg = '#fdf6e3'
remote_control_mode_bg = '#859900'

View File

@ -17,6 +17,7 @@ match_fg = '#d2788c'
# preview # preview
preview_title_fg = '#d2a374' preview_title_fg = '#d2a374'
# modes # modes
channel_mode_fg = '#8faf77' channel_mode_fg = '#18191a'
remote_control_mode_fg = '#d2a374' channel_mode_bg = '#8faf77'
send_to_channel_mode_fg = '#d2788c' remote_control_mode_fg = '#18191a'
remote_control_mode_bg = '#d2a374'

View File

@ -16,7 +16,7 @@ match_fg = '#f7768e'
# preview # preview
preview_title_fg = '#bb9af7' preview_title_fg = '#bb9af7'
# modes # modes
channel_mode_fg = '#faba4a' channel_mode_fg = '#1a1b26'
remote_control_mode_fg = '#9ece6a' channel_mode_bg = '#faba4a'
send_to_channel_mode_fg = '#a4daff' remote_control_mode_fg = '#1a1b26'
remote_control_mode_bg = '#9ece6a'