diff --git a/Cargo.lock b/Cargo.lock index e5ced3d..18a77ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "lumberjack" -version = "0.2.4" +version = "0.3.0" dependencies = [ "arboard", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index a61de78..ed85546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lumberjack" -version = "0.2.4" +version = "0.3.0" edition = "2024" [dependencies] diff --git a/README.md b/README.md index 65df89a..ec4c0ce 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ Built in **Rust**, powered by **ratatui**, **crossterm**, and the **AWS SDK for - `1/2/3/4` for time presets - `t` to tail - `y` to copy all results + - `T` to cycle color themes (Dark → Light → Green CRT) +- 🎨 Theme support + - Dark (default) + - Light + - Retro Green CRT (phosphor-style, neon green on black) - 🌑 Focus-aware panes (Groups / Filter / Results) with clear borders and styles --- @@ -79,6 +84,7 @@ cargo run -- --profile= --region= - `s` – Save current filter (opens name popup; persists to `~/.config/lumberjack/filters.json`) - `F` – Load saved filter (opens popup with saved filter names) - `t` – Toggle tail/stream mode for results +- `T` – Cycle color themes (Dark → Light → Green CRT) - `Esc` – Cancel editing, group search, or close popups - `y` – Copy all Results to clipboard (when Results pane is focused) - `q` – Quit (except while editing or in group search) diff --git a/src/app/clipboard.rs b/src/app/clipboard.rs index 8c8abaf..6080eae 100644 --- a/src/app/clipboard.rs +++ b/src/app/clipboard.rs @@ -1,8 +1,6 @@ -use std::time::Instant; - -use arboard::Clipboard; - use crate::app::App; +use arboard::Clipboard; +use std::time::Instant; impl App { pub fn results_text(&self) -> String { @@ -28,6 +26,7 @@ impl App { #[cfg(test)] mod tests { use crate::app::{App, FilterField, Focus}; + use crate::ui::styles::Theme; use std::sync::mpsc; use std::time::Instant as StdInstant; @@ -36,6 +35,8 @@ mod tests { App { app_title: "Test".to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: lines.into_iter().map(|s| s.to_string()).collect(), filter_cursor_pos: 0, diff --git a/src/app/filters.rs b/src/app/filters.rs index 7971302..072c8a1 100644 --- a/src/app/filters.rs +++ b/src/app/filters.rs @@ -187,6 +187,7 @@ impl App { mod tests { use super::*; use crate::app::{App, Focus}; + use crate::ui::styles::Theme; use std::sync::mpsc; use std::time::Instant as StdInstant; @@ -195,6 +196,8 @@ mod tests { App { app_title: "Test".to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: Vec::new(), filter_cursor_pos: 0, diff --git a/src/app/keymap.rs b/src/app/keymap.rs index ce5492b..cc69a78 100644 --- a/src/app/keymap.rs +++ b/src/app/keymap.rs @@ -1,4 +1,5 @@ use super::{App, FilterField, Focus}; +use crate::ui::styles::Theme; use ratatui::crossterm::event::{KeyCode, KeyEventKind}; use std::io; @@ -192,6 +193,19 @@ impl App { self.apply_time_preset("-24m"); } + KeyCode::Char('T') if !self.editing => { + if self.theme_name == "dark" { + self.theme = Theme::light(); + self.theme_name = "light".to_string(); + } else if self.theme_name == "light" { + self.theme = Theme::green(); + self.theme_name = "green".to_string(); + } else { + self.theme = Theme::default_dark(); + self.theme_name = "dark".to_string(); + } + } + _ => {} } @@ -202,6 +216,7 @@ impl App { #[cfg(test)] mod tests { use crate::app::{App, FilterField, Focus}; + use crate::ui::styles::Theme; use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use std::sync::mpsc; use std::time::Instant as StdInstant; @@ -215,6 +230,8 @@ mod tests { App { app_title: "Test".to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: Vec::new(), filter_cursor_pos: 0, @@ -284,4 +301,23 @@ mod tests { assert_eq!(app.filter_query, "abc"); assert_eq!(app.filter_cursor_pos, 2); // back between 'b' and 'c' } + + #[test] + fn theme_cycles_dark_light_green_on_t() { + let mut app = app_with_filter_query(""); + // Ensure starting theme is dark + assert_eq!(app.theme_name, "dark"); + + // First T: dark -> light + app.handle_key_event(key(KeyCode::Char('T'))).unwrap(); + assert_eq!(app.theme_name, "light"); + + // Second T: light -> green + app.handle_key_event(key(KeyCode::Char('T'))).unwrap(); + assert_eq!(app.theme_name, "green"); + + // Third T: green -> dark + app.handle_key_event(key(KeyCode::Char('T'))).unwrap(); + assert_eq!(app.theme_name, "dark"); + } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 3ccd552..166afec 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,6 +7,7 @@ use std::sync::atomic::Ordering; use std::sync::mpsc::{Receiver, Sender}; use std::time::{Duration, Instant}; +use crate::ui::styles::Theme; use chrono::Utc; use ratatui::crossterm::event; use ratatui::prelude::Rect; @@ -43,6 +44,8 @@ pub struct SavedFilter { pub struct App { pub app_title: String, + pub theme: Theme, + pub theme_name: String, pub exit: bool, pub lines: Vec, pub filter_cursor_pos: usize, @@ -493,6 +496,8 @@ mod tests { App { app_title: "Test".to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: Vec::new(), filter_cursor_pos: 0, diff --git a/src/main.rs b/src/main.rs index e17258c..92e858c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod app; mod aws; mod ui; +use crate::ui::styles::Theme; use app::{App, FilterField, Focus}; use aws::fetch_log_groups; @@ -35,6 +36,8 @@ fn main() -> io::Result<()> { let mut app = App { app_title: APP_TITLE.to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: Vec::new(), filter_cursor_pos: 0, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0451c31..b770c1e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,5 +1,5 @@ mod results; -mod styles; +pub mod styles; use ratatui::layout::{Constraint, Layout}; use ratatui::prelude::Rect; @@ -11,6 +11,7 @@ use crate::app::{App, FilterField, Focus}; impl Widget for &App { fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { + let theme = self.theme.clone(); let chunks = Layout::vertical([ Constraint::Length(1), Constraint::Length(6), @@ -19,19 +20,19 @@ impl Widget for &App { ]) .split(area); - let header_style = styles::header(); - let footer_style = styles::footer(); + let header_style = theme.header; + let footer_style = theme.footer; - let groups_block_style = styles::groups_block(self.focus == Focus::Groups); - let filter_block_style = styles::filter_block(self.focus == Focus::Filter); - let results_block_style = styles::results_block(self.focus == Focus::Results); + let groups_block_style = styles::groups_block(&theme, self.focus == Focus::Groups); + let filter_block_style = styles::filter_block(&theme, self.focus == Focus::Filter); + let results_block_style = styles::results_block(&theme, self.focus == Focus::Results); - let groups_item_style = styles::group_item(self.focus == Focus::Groups); - let groups_selected_style = styles::groups_selected(self.focus == Focus::Groups); + let groups_item_style = styles::group_item(&theme, self.focus == Focus::Groups); + let groups_selected_style = styles::groups_selected(&theme, self.focus == Focus::Groups); - let groups_border = styles::pane_border(self.focus == Focus::Groups); - let filter_border = styles::pane_border(self.focus == Focus::Filter); - let results_border = styles::pane_border(self.focus == Focus::Results); + let groups_border = styles::pane_border(&theme, self.focus == Focus::Groups); + let filter_border = styles::pane_border(&theme, self.focus == Focus::Filter); + let results_border = styles::pane_border(&theme, self.focus == Focus::Results); buf.set_style(chunks[0], header_style); buf.set_style(chunks[3], footer_style); @@ -51,7 +52,9 @@ impl Widget for &App { ); Line::from(self.app_title.as_str()) .bold() + .style(theme.header) .render(header[0], buf); + Line::from(header_right_text) .right_aligned() .style(header_style) @@ -62,7 +65,7 @@ impl Widget for &App { } else if self.group_search_active { format!("Search groups: {}", self.group_search_input) } else { - "Tab Switch pane ↑↓ Move Enter Edit/Run t Tail y Copy Esc Cancel q Quit" + "Tab Switch pane ↑↓ Move Enter Edit/Run t Tail y Copy Esc Cancel T Themes q Quit" .to_string() }; @@ -138,7 +141,7 @@ impl Widget for &App { let dots = ".".repeat(self.dots); let msg = format!("Searching{dots}"); - Line::from(msg).style(styles::default_gray()).render( + Line::from(msg).style(styles::default_gray(&theme)).render( Rect { x: results_inner.x, y: results_inner.y, @@ -159,7 +162,7 @@ impl Widget for &App { let field_style = |field: FilterField| { let active = self.focus == Focus::Filter && field == self.filter_field; - styles::filter_field(active, active && self.editing) + styles::filter_field(&theme, active, active && self.editing) }; let line = |label: &str, value: &str| format!("{label}: {value}"); @@ -242,7 +245,7 @@ impl Widget for &App { // draw a vertical bar cursor if let Some(cell) = buf.cell_mut((x, y)) { - cell.set_char('▏').set_style(styles::cursor()); + cell.set_char('▏').set_style(styles::cursor(&theme)); } } } @@ -271,7 +274,7 @@ impl Widget for &App { let presets_x = filter_inner.x + pane_width.saturating_sub(text_width); Line::from(presets_text) - .style(styles::presets_hint()) + .style(styles::presets_hint(&theme)) .render( Rect { x: presets_x, @@ -299,8 +302,8 @@ impl Widget for &App { let block = Block::bordered() .title("Save filter") - .style(styles::popup_block()) - .border_style(styles::popup_border()); + .style(styles::popup_block(&theme)) + .border_style(styles::popup_border(&theme)); let inner = block.inner(popup_area); block.render(popup_area, buf); @@ -319,15 +322,17 @@ impl Widget for &App { ); let name_line = format!("{}", self.save_filter_name); - Line::from(name_line).style(styles::popup_border()).render( - Rect { - x: inner.x, - y: inner.y + 1, - width: inner.width, - height: 1, - }, - buf, - ); + Line::from(name_line) + .style(styles::popup_border(&theme)) + .render( + Rect { + x: inner.x, + y: inner.y + 1, + width: inner.width, + height: 1, + }, + buf, + ); // Hint line Line::from("Enter Save Esc Cancel") @@ -361,8 +366,8 @@ impl Widget for &App { let block = Block::bordered() .title("Load filter") - .style(styles::popup_block()) - .border_style(styles::popup_border()); + .style(styles::popup_block(&theme)) + .border_style(styles::popup_border(&theme)); let inner = block.inner(popup_area); block.render(popup_area, buf); @@ -380,7 +385,7 @@ impl Widget for &App { }; let line = format!("{marker} {}", f.name); let style = if idx == self.load_filter_selected { - styles::popup_border() + styles::popup_border(&theme) } else { Style::default().fg(Color::White) }; @@ -400,7 +405,7 @@ impl Widget for &App { // Hint line at the bottom of the popup Line::from("Enter Load Esc Cancel") - .style(styles::default_gray()) + .style(styles::default_gray(&theme)) .render( Rect { x: inner.x, @@ -417,6 +422,7 @@ impl Widget for &App { #[cfg(test)] mod ui_tests { use super::*; + use crate::ui::styles::Theme; use ratatui::{buffer::Buffer, layout::Rect}; use std::sync::atomic::AtomicBool; use std::sync::{Arc, mpsc}; @@ -428,6 +434,8 @@ mod ui_tests { App { app_title: "lumberjack".to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: vec![], filter_cursor_pos: 0, @@ -720,4 +728,30 @@ mod ui_tests { "should not render 'Iare' artifact" ); } + + #[test] + fn header_and_footer_use_current_theme() { + let mut app = make_app(); // starts with Theme::default_dark() + let area = Rect::new(0, 0, 80, 10); + + // Render with dark theme + let mut buf_dark = Buffer::empty(area); + (&app).render(area, &mut buf_dark); + + // Switch to light theme + app.theme = crate::ui::styles::Theme::light(); + + let mut buf_light = Buffer::empty(area); + (&app).render(area, &mut buf_light); + + // Compare a cell in the header (e.g., first column of first row) + let dark_cell = buf_dark.cell((area.x, area.y)).unwrap(); + let light_cell = buf_light.cell((area.x, area.y)).unwrap(); + + assert_ne!( + dark_cell.style().bg, + light_cell.style().bg, + "expected header background to change when theme changes" + ); + } } diff --git a/src/ui/results.rs b/src/ui/results.rs index 765d5b0..7a6032e 100644 --- a/src/ui/results.rs +++ b/src/ui/results.rs @@ -134,11 +134,14 @@ mod tests { use std::time::Instant; use crate::app::{App, FilterField, Focus}; + use crate::ui::styles::Theme; fn make_results_app(lines: Vec<&str>) -> App { let (tx, rx) = mpsc::channel(); App { app_title: "Test".to_string(), + theme: Theme::default_dark(), + theme_name: "dark".to_string(), exit: false, lines: lines.into_iter().map(|s| s.to_string()).collect(), filter_cursor_pos: 0, diff --git a/src/ui/styles.rs b/src/ui/styles.rs index 06c852b..9829373 100644 --- a/src/ui/styles.rs +++ b/src/ui/styles.rs @@ -1,102 +1,299 @@ -use ratatui::style::{Color, Style}; +use ratatui::style::{Color, Modifier, Style}; -pub fn header() -> Style { - Style::default().bg(Color::Rgb(10, 10, 10)).fg(Color::White) +#[derive(Clone, Debug)] +pub struct Theme { + pub header: Style, + pub footer: Style, + + pub groups_block_focused: Style, + pub groups_block_unfocused: Style, + pub groups_item_focused: Style, + pub groups_item_unfocused: Style, + pub groups_selected_focused: Style, + pub groups_selected_unfocused: Style, + + pub filter_block_focused: Style, + pub filter_block_unfocused: Style, + + pub results_block_focused: Style, + pub results_block_unfocused: Style, + + pub pane_border_focused: Style, + pub pane_border_unfocused: Style, + + pub default_gray: Style, + pub filter_field_active_editing: Style, + pub filter_field_active_idle: Style, + pub filter_field_inactive: Style, + + pub popup_block: Style, + pub popup_border: Style, + pub presets_hint: Style, + pub cursor: Style, } -pub fn footer() -> Style { - Style::default().bg(Color::Rgb(10, 10, 10)).fg(Color::Gray) +impl Theme { + pub fn default_dark() -> Self { + Theme { + header: Style::default().bg(Color::Rgb(10, 10, 10)).fg(Color::White), + footer: Style::default().bg(Color::Rgb(10, 10, 10)).fg(Color::Gray), + groups_block_focused: Style::default().bg(Color::Black).fg(Color::White), + groups_block_unfocused: Style::default() + .bg(Color::Rgb(14, 14, 14)) + .fg(Color::Rgb(140, 140, 140)), + + groups_item_focused: Style::default().bg(Color::Black).fg(Color::White), + groups_item_unfocused: Style::default() + .bg(Color::Rgb(14, 14, 14)) + .fg(Color::Rgb(140, 140, 140)), + + groups_selected_focused: Style::default() + .bg(Color::Rgb(40, 40, 40)) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + groups_selected_unfocused: Style::default().bg(Color::Rgb(18, 18, 18)).fg(Color::White), + + filter_block_focused: Style::default().bg(Color::Rgb(20, 20, 20)).fg(Color::White), + filter_block_unfocused: Style::default() + .bg(Color::Rgb(20, 20, 20)) + .fg(Color::Rgb(140, 140, 140)), + + results_block_focused: Style::default().bg(Color::Rgb(5, 5, 5)).fg(Color::White), + results_block_unfocused: Style::default() + .bg(Color::Rgb(14, 14, 14)) + .fg(Color::Rgb(140, 140, 140)), + + pane_border_focused: Style::default().fg(Color::Yellow), + pane_border_unfocused: Style::default(), + + default_gray: Style::default().fg(Color::Gray), + + filter_field_active_editing: Style::default().bg(Color::Gray).fg(Color::Black), + filter_field_active_idle: Style::default().fg(Color::White).bg(Color::Rgb(20, 20, 20)), + filter_field_inactive: Style::default() + .fg(Color::Rgb(100, 100, 100)) + .bg(Color::Rgb(20, 20, 20)), + + popup_block: Style::default().bg(Color::Rgb(30, 30, 30)).fg(Color::White), + popup_border: Style::default().fg(Color::Yellow), + + presets_hint: Style::default().fg(Color::Rgb(50, 50, 50)), + cursor: Style::default().fg(Color::White).bg(Color::Rgb(20, 20, 20)), + } + } + + pub fn light() -> Self { + // Start from dark to fill all fields, then override what we care about. + let mut t = Theme::default_dark(); + + let bg = Color::Rgb(240, 240, 240); + let bg_alt = Color::Rgb(230, 230, 230); + let text = Color::Rgb(30, 30, 30); + + // Header / footer + t.header = Style::default().bg(bg).fg(text); + t.footer = Style::default().bg(bg).fg(text); + + // Groups block background + t.groups_block_focused = Style::default().bg(bg_alt).fg(text); + t.groups_block_unfocused = Style::default().bg(bg_alt).fg(text); + + // Group items + t.groups_item_unfocused = Style::default().bg(bg_alt).fg(text); + t.groups_item_focused = t.groups_item_unfocused; + + t.groups_selected_focused = Style::default() + .bg(Color::Rgb(210, 210, 210)) + .fg(text) + .add_modifier(Modifier::BOLD); + t.groups_selected_unfocused = Style::default().bg(Color::Rgb(220, 220, 220)).fg(text); + + // Filter block + t.filter_block_focused = Style::default().bg(bg).fg(text); + t.filter_block_unfocused = Style::default().bg(bg).fg(text); + + // Results block + t.results_block_focused = Style::default().bg(bg).fg(text); + t.results_block_unfocused = Style::default().bg(bg).fg(text); + + // Borders + t.pane_border_focused = Style::default().fg(Color::Rgb(80, 80, 80)); + t.pane_border_unfocused = Style::default().fg(Color::Rgb(180, 180, 180)); + + // Default gray text (used for "Searching..." etc.) + t.default_gray = Style::default().fg(Color::Rgb(120, 120, 120)); + + // Filter fields + t.filter_field_inactive = Style::default().bg(bg).fg(Color::Rgb(120, 120, 120)); + t.filter_field_active_idle = Style::default().bg(Color::Rgb(220, 220, 220)).fg(text); + t.filter_field_active_editing = Style::default().bg(Color::Rgb(200, 200, 200)).fg(text); + + // Popup + t.popup_block = Style::default().bg(Color::Rgb(245, 245, 245)).fg(text); + t.popup_border = Style::default().fg(Color::Rgb(100, 100, 100)); + + // Presets hint, cursor + t.presets_hint = Style::default().fg(Color::Rgb(100, 100, 100)); + t.cursor = Style::default().fg(text).bg(Color::Rgb(220, 220, 220)); + + t + } + + pub fn green() -> Self { + // Start from dark to fill all fields, then override. + let mut t = Theme::default_dark(); + + // Neon-ish phosphor green: slightly yellowish, bright + let green = Color::Rgb(160, 255, 0); + let dark_bg = Color::Black; + let band_bg = Color::Rgb(0, 40, 0); // very dark green band + let bright_bg = Color::Rgb(0, 90, 0); // brighter band for strong highlight + + // Header / footer: pure phosphor look + t.header = Style::default() + .bg(dark_bg) + .fg(green) + .add_modifier(Modifier::BOLD); + t.footer = Style::default().bg(dark_bg).fg(green); + + // --- Groups pane --- + + // Block background + t.groups_block_focused = Style::default().bg(dark_bg).fg(green); + t.groups_block_unfocused = Style::default().bg(dark_bg).fg(green); + + // Unselected items + t.groups_item_unfocused = Style::default().bg(dark_bg).fg(green); + t.groups_item_focused = t.groups_item_unfocused; + + // Selected item (pane focused): bright band, bold + t.groups_selected_focused = Style::default() + .bg(bright_bg) + .fg(green) + .add_modifier(Modifier::BOLD); + + // Selected item (pane unfocused): darker band, still visible + t.groups_selected_unfocused = Style::default().bg(band_bg).fg(green); + + // --- Filter pane --- + + t.filter_block_focused = Style::default().bg(dark_bg).fg(green); + t.filter_block_unfocused = Style::default().bg(dark_bg).fg(green); + + // Inactive fields: black bg, green text + t.filter_field_inactive = Style::default().bg(dark_bg).fg(green); + + // Active (not editing): dark green band + t.filter_field_active_idle = Style::default().bg(band_bg).fg(green); + + // Active (editing): brighter band, bold + t.filter_field_active_editing = Style::default() + .bg(bright_bg) + .fg(green) + .add_modifier(Modifier::BOLD); + + // --- Results pane --- + + t.results_block_focused = Style::default().bg(dark_bg).fg(green); + t.results_block_unfocused = Style::default().bg(dark_bg).fg(green); + + // --- Borders, text, cursor, popups --- + + t.pane_border_focused = Style::default().fg(green); + t.pane_border_unfocused = Style::default().fg(green); + + // "Gray" in green mode is just green + t.default_gray = Style::default().fg(green); + + // Popups: same phosphor look + t.popup_block = Style::default().bg(dark_bg).fg(green); + t.popup_border = Style::default().fg(green); + + t.presets_hint = Style::default().fg(green); + + // Cursor: thin green bar on black + t.cursor = Style::default().fg(green).bg(dark_bg); + + t + } } -pub fn groups_block(focus: bool) -> Style { +pub fn groups_block(theme: &Theme, focus: bool) -> Style { if focus { - Style::default().bg(Color::Black).fg(Color::White) + theme.groups_block_focused } else { - Style::default() - .bg(Color::Rgb(14, 14, 14)) - .fg(Color::Rgb(140, 140, 140)) + theme.groups_block_unfocused } } -pub fn group_item(focused: bool) -> Style { +pub fn group_item(theme: &Theme, focused: bool) -> Style { if focused { - Style::default().bg(Color::Black).fg(Color::White) + theme.groups_item_focused } else { - Style::default() - .bg(Color::Rgb(14, 14, 14)) - .fg(Color::Rgb(140, 140, 140)) + theme.groups_item_unfocused } } -pub fn groups_selected(focus: bool) -> Style { +pub fn groups_selected(theme: &Theme, focus: bool) -> Style { if focus { - Style::default() - .bg(Color::Rgb(40, 40, 40)) - .fg(Color::White) - .add_modifier(ratatui::style::Modifier::BOLD) + theme.groups_selected_focused } else { - Style::default().bg(Color::Rgb(18, 18, 18)).fg(Color::White) + theme.groups_selected_unfocused } } -pub fn filter_block(focus: bool) -> Style { +pub fn filter_block(theme: &Theme, focus: bool) -> Style { if focus { - Style::default().bg(Color::Rgb(20, 20, 20)).fg(Color::White) + theme.filter_block_focused } else { - Style::default() - .bg(Color::Rgb(20, 20, 20)) - .fg(Color::Rgb(140, 140, 140)) + theme.filter_block_unfocused } } -pub fn results_block(focus: bool) -> Style { +pub fn results_block(theme: &Theme, focus: bool) -> Style { if focus { - Style::default().bg(Color::Rgb(5, 5, 5)).fg(Color::White) + theme.results_block_focused } else { - Style::default() - .bg(Color::Rgb(14, 14, 14)) - .fg(Color::Rgb(140, 140, 140)) + theme.results_block_unfocused } } -pub fn pane_border(focus: bool) -> Style { +pub fn pane_border(theme: &Theme, focus: bool) -> Style { if focus { - Style::default().fg(Color::Yellow) + theme.pane_border_focused } else { - Style::default() + theme.pane_border_unfocused } } -pub fn default_gray() -> Style { - Style::default().fg(Color::Gray) +pub fn default_gray(theme: &Theme) -> Style { + theme.default_gray } -pub fn filter_field(field_is_active: bool, editing: bool) -> Style { +pub fn filter_field(theme: &Theme, field_is_active: bool, editing: bool) -> Style { if field_is_active { if editing { - Style::default().bg(Color::Gray).fg(Color::Black) + theme.filter_field_active_editing } else { - Style::default().fg(Color::White).bg(Color::Rgb(20, 20, 20)) + theme.filter_field_active_idle } } else { - Style::default() - .fg(Color::Rgb(100, 100, 100)) - .bg(Color::Rgb(20, 20, 20)) + theme.filter_field_inactive } } -pub fn popup_block() -> Style { - Style::default().bg(Color::Rgb(30, 30, 30)).fg(Color::White) +pub fn popup_block(theme: &Theme) -> Style { + theme.popup_block } -pub fn popup_border() -> Style { - Style::default().fg(Color::Yellow) +pub fn popup_border(theme: &Theme) -> Style { + theme.popup_border } -pub fn presets_hint() -> Style { - Style::default().fg(Color::Rgb(50, 50, 50)) +pub fn presets_hint(theme: &Theme) -> Style { + theme.presets_hint } -pub fn cursor() -> Style { - Style::default().fg(Color::White).bg(Color::Rgb(20, 20, 20)) +pub fn cursor(theme: &Theme) -> Style { + theme.cursor }