From 78db70ae7c3501b0f3bcc2dc532b740f6d8de8ae Mon Sep 17 00:00:00 2001 From: Abhinav A <71514966+abhixdd@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:52:21 +0530 Subject: [PATCH] chore: move theme example and clean up theming docs --- README.md | 35 +++------- examples/theme.toml | 20 ++++++ exampletheme.toml | 24 ------- src/ui/components/input.rs | 8 ++- src/ui/components/repo_search.rs | 15 +++- src/ui/components/searching.rs | 4 +- src/ui/theme.rs | 114 ++++++++++++++++++++----------- 7 files changed, 125 insertions(+), 95 deletions(-) create mode 100644 examples/theme.toml delete mode 100644 exampletheme.toml diff --git a/README.md b/README.md index fcffb71..4b1bc6a 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,17 @@ ghgrab config unset token ghgrab config unset path ``` +### Theming + +ghgrab supports custom color themes via a TOML config file. + +- Linux/macOS: `~/.config/ghgrab/theme.toml` +- Windows: `%APPDATA%\ghgrab\theme.toml` + +Any missing key falls back to the default Tokyo Night theme. Colors must use `#RRGGBB` hex format. + +See [`examples/theme.toml`](examples/theme.toml) for a complete example. + ### Keyboard Shortcuts (How to move around) We've kept it pretty standard, but here's a quick cheat sheet: @@ -193,27 +204,3 @@ If you find a bug, have an idea for a cool new feature, or just want to help out ## License Distributed under the MIT License. It's open, free, and yours to play with. See [LICENSE](LICENSE) for the fine print. - -### Theming - -ghgrab supports custom color themes via a TOML config file. - -**Location:** -- Linux/macOS: `~/.config/ghgrab/theme.toml` -- Windows: `%APPDATA%\ghgrab\theme.toml` - -Variables can be changed individually — any missing key falls back to the default Tokyo Night color theme. Colors must be in `#RRGGBB` hex format. -```toml -bg_color = "#24283b" # Main background -fg_color = "#c0caf5" # Primary text -accent_color = "#7aa2f7" # Borders, highlights, active elements -warning_color = "#e0af68" # Warnings -error_color = "#f7768e" # Errors -success_color = "#9ece6a" # Success indicators -folder_color = "#82aaff" # Folder icons -selected_color = "#ff9e64" # Selected items -border_color = "#565f89" # Inactive borders -highlight_bg = "#292e42" # Highlighted row background -``` - -[`exampletheme.toml`](exampletheme.toml) for a example template. diff --git a/examples/theme.toml b/examples/theme.toml new file mode 100644 index 0000000..74ca990 --- /dev/null +++ b/examples/theme.toml @@ -0,0 +1,20 @@ +# ghgrab theme example +# +# Place this file at: +# - Linux/macOS: ~/.config/ghgrab/theme.toml +# - Windows: %APPDATA%\ghgrab\theme.toml +# +# Create the ghgrab directory if it does not exist. +# All values are optional. Missing keys fall back to the default Tokyo Night colors. +# Colors must use #RRGGBB hex format. + +bg_color = "#24283b" # Main background +fg_color = "#c0caf5" # Primary text +accent_color = "#7aa2f7" # Borders, highlights, active elements +warning_color = "#e0af68" # Warnings +error_color = "#f7768e" # Errors +success_color = "#9ece6a" # Success indicators +folder_color = "#82aaff" # Folder icons +selected_color = "#ff9e64" # Selected items +border_color = "#565f89" # Inactive borders +highlight_bg = "#292e42" # Highlighted row background diff --git a/exampletheme.toml b/exampletheme.toml deleted file mode 100644 index a74c635..0000000 --- a/exampletheme.toml +++ /dev/null @@ -1,24 +0,0 @@ -# ghgrab theme example - -# Place this file at ~/.config/ghgrab/theme.toml if linux and mac -# Place this file at %APPDATA%\ghgrab\theme.toml if windows -# If directory doesnt exists create it yourself - -# All values are optional - missing keys fall back to the default Tokyo Night colors -# Colors must be in #RRGGBB hex format. - -# R for red G for green and B for blue - -bg_color = "#24283b" # Main background -fg_color = "#c0caf5" # Primary text -accent_color = "#7aa2f7" # Borders, highlights, active elements -warning_color = "#e0af68" # Warnings -error_color = "#f7768e" # Errors -success_color = "#9ece6a" # Success indicators -folder_color = "#82aaff" # Folder icons -selected_color = "#ff9e64" # Selected items -border_color = "#565f89" # Inactive borders -highlight_bg = "#292e42" # Highlighted row background - - -# Matugen tested \ No newline at end of file diff --git a/src/ui/components/input.rs b/src/ui/components/input.rs index 063f108..cd4e6f8 100644 --- a/src/ui/components/input.rs +++ b/src/ui/components/input.rs @@ -85,7 +85,9 @@ pub fn render( let desc_text = Line::from(Span::styled( "Download any file or folder from GitHub. No full clones. Just what you need.", - Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC), + Style::default() + .fg(FG_COLOR()) + .add_modifier(Modifier::ITALIC), )); let desc = Paragraph::new(desc_text) .alignment(Alignment::Center) @@ -208,7 +210,9 @@ pub fn render( ), Span::styled( "Works with any public or private GitHub repository", - Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC), + Style::default() + .fg(FG_COLOR()) + .add_modifier(Modifier::ITALIC), ), ]), ]; diff --git a/src/ui/components/repo_search.rs b/src/ui/components/repo_search.rs index 7cc158a..7c18d31 100644 --- a/src/ui/components/repo_search.rs +++ b/src/ui/components/repo_search.rs @@ -140,7 +140,10 @@ pub fn render(f: &mut Frame, area: Rect, state: &RepoSearchState) { }; let status = Paragraph::new(Line::from(vec![ - Span::styled(" Search ", Style::default().fg(BG_COLOR()).bg(SUCCESS_COLOR())), + Span::styled( + " Search ", + Style::default().fg(BG_COLOR()).bg(SUCCESS_COLOR()), + ), Span::raw(" "), Span::styled(status_text, Style::default().fg(FG_COLOR())), ])) @@ -155,7 +158,11 @@ pub fn render(f: &mut Frame, area: Rect, state: &RepoSearchState) { if state.loading && state.total_results == 0 { let loading_widget = Paragraph::new("\nSearching GitHub repositories...") .alignment(Alignment::Center) - .style(Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC)) + .style( + Style::default() + .fg(FG_COLOR()) + .add_modifier(Modifier::ITALIC), + ) .block( Block::default() .borders(Borders::ALL) @@ -352,7 +359,9 @@ fn build_result_item(width: u16, is_selected: bool, item: &SearchItem) -> ListIt Span::raw(" "), Span::styled( trimmed_desc, - Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC), + Style::default() + .fg(FG_COLOR()) + .add_modifier(Modifier::ITALIC), ), ]), Line::from(meta_spans), diff --git a/src/ui/components/searching.rs b/src/ui/components/searching.rs index 507a182..76545f3 100644 --- a/src/ui/components/searching.rs +++ b/src/ui/components/searching.rs @@ -81,7 +81,9 @@ pub fn render(f: &mut Frame, area: Rect, frame_count: u64, status_msg: &str) { }; let status = Paragraph::new(Span::styled( msg, - Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC), + Style::default() + .fg(FG_COLOR()) + .add_modifier(Modifier::ITALIC), )) .alignment(Alignment::Center) .style(Style::default().bg(BG_COLOR())); diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 0961432..3e660d4 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use ratatui::style::Color; use serde::{Deserialize, Serialize}; use std::fs; @@ -5,29 +7,29 @@ use std::sync::OnceLock; #[derive(Serialize, Deserialize, Debug, Clone)] struct ThemeFile { - bg_color: Option, - fg_color: Option, - accent_color: Option, - warning_color: Option, - error_color: Option, - success_color: Option, - folder_color: Option, + bg_color: Option, + fg_color: Option, + accent_color: Option, + warning_color: Option, + error_color: Option, + success_color: Option, + folder_color: Option, selected_color: Option, - border_color: Option, - highlight_bg: Option, + border_color: Option, + highlight_bg: Option, } struct Theme { - pub bg_color: Color, - pub fg_color: Color, - pub accent_color: Color, - pub warning_color: Color, - pub error_color: Color, - pub success_color: Color, - pub folder_color: Color, + pub bg_color: Color, + pub fg_color: Color, + pub accent_color: Color, + pub warning_color: Color, + pub error_color: Color, + pub success_color: Color, + pub folder_color: Color, pub selected_color: Color, - pub border_color: Color, - pub highlight_bg: Color, + pub border_color: Color, + pub highlight_bg: Color, } fn parse_color(val: Option<&str>, default: [u8; 3]) -> Color { @@ -38,9 +40,17 @@ fn parse_color(val: Option<&str>, default: [u8; 3]) -> Color { u8::from_str_radix(&hex[0..2], 16), u8::from_str_radix(&hex[2..4], 16), u8::from_str_radix(&hex[4..6], 16), - ) { [r, g, b] } else { default } - } else { default } - } else { default }; + ) { + [r, g, b] + } else { + default + } + } else { + default + } + } else { + default + }; Color::Rgb(r, g, b) } @@ -54,29 +64,51 @@ fn load_theme() -> Theme { })(); let f = file.as_ref(); Theme { - bg_color: parse_color(f.and_then(|t| t.bg_color.as_deref()), [36, 40, 59]), - fg_color: parse_color(f.and_then(|t| t.fg_color.as_deref()), [192, 202, 245]), - accent_color: parse_color(f.and_then(|t| t.accent_color.as_deref()), [122, 162, 247]), - warning_color: parse_color(f.and_then(|t| t.warning_color.as_deref()), [224, 175, 104]), - error_color: parse_color(f.and_then(|t| t.error_color.as_deref()), [247, 120, 107]), - success_color: parse_color(f.and_then(|t| t.success_color.as_deref()), [158, 206, 106]), - folder_color: parse_color(f.and_then(|t| t.folder_color.as_deref()), [130, 170, 255]), + bg_color: parse_color(f.and_then(|t| t.bg_color.as_deref()), [36, 40, 59]), + fg_color: parse_color(f.and_then(|t| t.fg_color.as_deref()), [192, 202, 245]), + accent_color: parse_color(f.and_then(|t| t.accent_color.as_deref()), [122, 162, 247]), + warning_color: parse_color(f.and_then(|t| t.warning_color.as_deref()), [224, 175, 104]), + error_color: parse_color(f.and_then(|t| t.error_color.as_deref()), [247, 118, 142]), + success_color: parse_color(f.and_then(|t| t.success_color.as_deref()), [158, 206, 106]), + folder_color: parse_color(f.and_then(|t| t.folder_color.as_deref()), [130, 170, 255]), selected_color: parse_color(f.and_then(|t| t.selected_color.as_deref()), [255, 158, 100]), - border_color: parse_color(f.and_then(|t| t.border_color.as_deref()), [86, 95, 137]), - highlight_bg: parse_color(f.and_then(|t| t.highlight_bg.as_deref()), [41, 46, 66]), + border_color: parse_color(f.and_then(|t| t.border_color.as_deref()), [86, 95, 137]), + highlight_bg: parse_color(f.and_then(|t| t.highlight_bg.as_deref()), [41, 46, 66]), } } static THEME: OnceLock = OnceLock::new(); -fn t() -> &'static Theme { THEME.get_or_init(load_theme) } +fn t() -> &'static Theme { + THEME.get_or_init(load_theme) +} -pub fn BG_COLOR() -> Color { t().bg_color } -pub fn FG_COLOR() -> Color { t().fg_color } -pub fn ACCENT_COLOR() -> Color { t().accent_color } -pub fn WARNING_COLOR() -> Color { t().warning_color } -pub fn ERROR_COLOR() -> Color { t().error_color } -pub fn SUCCESS_COLOR() -> Color { t().success_color } -pub fn FOLDER_COLOR() -> Color { t().folder_color } -pub fn _SELECTED_COLOR() -> Color { t().selected_color } -pub fn BORDER_COLOR() -> Color { t().border_color } -pub fn HIGHLIGHT_BG() -> Color { t().highlight_bg } +pub fn BG_COLOR() -> Color { + t().bg_color +} +pub fn FG_COLOR() -> Color { + t().fg_color +} +pub fn ACCENT_COLOR() -> Color { + t().accent_color +} +pub fn WARNING_COLOR() -> Color { + t().warning_color +} +pub fn ERROR_COLOR() -> Color { + t().error_color +} +pub fn SUCCESS_COLOR() -> Color { + t().success_color +} +pub fn FOLDER_COLOR() -> Color { + t().folder_color +} +pub fn _SELECTED_COLOR() -> Color { + t().selected_color +} +pub fn BORDER_COLOR() -> Color { + t().border_color +} +pub fn HIGHLIGHT_BG() -> Color { + t().highlight_bg +}