diff --git a/src/ui/app.rs b/src/ui/app.rs index 2ccc163..d3e3928 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -9,7 +9,7 @@ use crate::db::{ ConnectionConfig, ConnectionManager, DatabaseInfo, QueryResult, SchemaInfo, SslMode, TableInfo, }; use crate::editor::{HistoryEntry, QueryHistory, TextBuffer}; -use crate::ui::Theme; +use crate::ui::{Theme, ThemeName}; pub const SPINNER_FRAMES: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; @@ -198,8 +198,13 @@ impl App { initial_config.password.len(), ]; + let theme = match Self::load_theme_preference() { + ThemeName::Dark => Theme::dark(), + ThemeName::Light => Theme::light(), + }; + Self { - theme: Theme::dark(), + theme, focus: Focus::ConnectionDialog, should_quit: false, @@ -298,9 +303,46 @@ impl App { } } + pub fn toggle_theme(&mut self) { + let new_name = self.theme.name.toggle(); + self.theme = match new_name { + ThemeName::Dark => Theme::dark(), + ThemeName::Light => Theme::light(), + }; + Self::save_theme_preference(new_name); + } + + fn save_theme_preference(theme_name: ThemeName) { + if let Some(config_dir) = dirs::config_dir() { + let pgrsql_dir = config_dir.join("pgrsql"); + let _ = std::fs::create_dir_all(&pgrsql_dir); + let _ = std::fs::write(pgrsql_dir.join("theme"), theme_name.as_str()); + } + } + + fn load_theme_preference() -> ThemeName { + if let Some(config_dir) = dirs::config_dir() { + let path = config_dir.join("pgrsql").join("theme"); + if let Ok(content) = std::fs::read_to_string(path) { + return match content.trim() { + "light" => ThemeName::Light, + _ => ThemeName::Dark, + }; + } + } + ThemeName::Dark + } + pub async fn handle_input(&mut self, key: KeyEvent) -> Result<()> { // Global shortcuts match (key.code, key.modifiers) { + // Ctrl+Shift+T: Toggle theme (works in all contexts except connection dialog text input) + // Note: most terminals report Ctrl+Shift+T as Char('T') with only CONTROL; + // the SHIFT modifier is implicit in the uppercase letter. + (KeyCode::Char('T'), m) if m.contains(KeyModifiers::CONTROL) => { + self.toggle_theme(); + return Ok(()); + } (KeyCode::Char('?'), _) if self.focus != Focus::Editor => { self.show_help = !self.show_help; if self.show_help { diff --git a/src/ui/components.rs b/src/ui/components.rs index da943dd..94d5dff 100644 --- a/src/ui/components.rs +++ b/src/ui/components.rs @@ -633,8 +633,12 @@ fn draw_status_bar(frame: &mut Frame, app: &App, area: Rect) { Style::default().fg(theme.text_muted).bg(theme.bg_secondary) }; - // Right section: help hints - let right_text = "? Help | Ctrl+Q/D Quit "; + // Right section: theme indicator + help hints + let theme_indicator = match app.theme.name { + crate::ui::ThemeName::Dark => "Dark", + crate::ui::ThemeName::Light => "Light", + }; + let right_text = format!("{} | ? Help | Ctrl+Q/D Quit ", theme_indicator); // Calculate padding let left_len = left_text.len() as u16; @@ -945,6 +949,7 @@ fn draw_help_overlay(frame: &mut Frame, app: &App) { " GLOBAL", " Ctrl+Q/D Quit", " Ctrl+C Connect dialog", + " Ctrl+Shift+T Toggle theme", " ? Toggle help", "", " NAVIGATION", diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 8651e67..a665056 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -1,7 +1,14 @@ use ratatui::style::{Color, Modifier, Style}; +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ThemeName { + Dark, + Light, +} + #[allow(dead_code)] pub struct Theme { + pub name: ThemeName, // Background colors pub bg_primary: Color, pub bg_secondary: Color, @@ -43,10 +50,27 @@ impl Default for Theme { } } +impl ThemeName { + pub fn toggle(self) -> Self { + match self { + ThemeName::Dark => ThemeName::Light, + ThemeName::Light => ThemeName::Dark, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + ThemeName::Dark => "dark", + ThemeName::Light => "light", + } + } +} + #[allow(dead_code)] impl Theme { pub fn dark() -> Self { Self { + name: ThemeName::Dark, // Background colors - dark blue-gray palette bg_primary: Color::Rgb(24, 26, 33), bg_secondary: Color::Rgb(30, 33, 43), @@ -85,6 +109,7 @@ impl Theme { pub fn light() -> Self { Self { + name: ThemeName::Light, bg_primary: Color::Rgb(250, 250, 252), bg_secondary: Color::Rgb(240, 240, 245), bg_tertiary: Color::Rgb(230, 230, 238),