fix: update tests and resolve clippy warnings after theme system refactor

- Fix theme test imports to use new module paths
- Update tests to use actual theme file format with named colors
- Fix clippy warnings in config, color_mode, theme, and loading views
- Replace manual Default implementations with derive macros
- Simplify conditional color logic in loading description view

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yuji Ueki
2025-09-21 14:05:13 +09:00
parent 633d257314
commit d43b47a204
33 changed files with 927 additions and 421 deletions
+1 -1
View File
@@ -18,4 +18,4 @@ fn main() {
println!("cargo:rerun-if-changed=assets/languages/lang_dark.json");
println!("cargo:rerun-if-changed=assets/languages/lang_light.json");
println!("cargo:rerun-if-changed=assets/languages/lang_ascii.json");
}
}
-1
View File
@@ -104,4 +104,3 @@ pub enum RepoCommands {
/// Play a cached repository interactively
Play,
}
-1
View File
@@ -94,4 +94,3 @@ fn run_repo_command(repo_command: &RepoCommands) -> Result<()> {
RepoCommands::Play => run_repo_play(),
}
}
+16 -6
View File
@@ -59,7 +59,10 @@ pub fn render_repo_list(repositories: Vec<StoredRepositoryWithLanguages>) -> Res
let home_dir = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
let cache_dir = home_dir.join(".gittype").join("repos");
let cache_line = Line::from(vec![
Span::styled("Cache Directory: ", Style::default().fg(Colors::text_secondary())),
Span::styled(
"Cache Directory: ",
Style::default().fg(Colors::text_secondary()),
),
Span::styled(
cache_dir.to_string_lossy().to_string(),
Style::default().fg(Colors::text()),
@@ -114,8 +117,10 @@ pub fn render_repo_list(repositories: Vec<StoredRepositoryWithLanguages>) -> Res
for (i, lang) in repo.languages.iter().enumerate() {
if i > 0 {
if current_length + 2 <= lang_width {
line_spans
.push(Span::styled(", ", Style::default().fg(Colors::text_secondary())));
line_spans.push(Span::styled(
", ",
Style::default().fg(Colors::text_secondary()),
));
current_length += 2;
} else {
break;
@@ -129,8 +134,10 @@ pub fn render_repo_list(repositories: Vec<StoredRepositoryWithLanguages>) -> Res
));
current_length += lang_name.len();
} else if current_length + 3 <= lang_width {
line_spans
.push(Span::styled("...", Style::default().fg(Colors::text_secondary())));
line_spans.push(Span::styled(
"...",
Style::default().fg(Colors::text_secondary()),
));
current_length += 3;
break;
} else {
@@ -144,7 +151,10 @@ pub fn render_repo_list(repositories: Vec<StoredRepositoryWithLanguages>) -> Res
}
line_spans.push(Span::styled(" ", Style::default()));
line_spans.push(Span::styled(url, Style::default().fg(Colors::text_secondary())));
line_spans.push(Span::styled(
url,
Style::default().fg(Colors::text_secondary()),
));
ListItem::new(Line::from(line_spans))
})
+4 -1
View File
@@ -94,7 +94,10 @@ pub fn render_repo_play_ui(
let mut spans = Vec::new();
for (i, lang) in repo.languages.iter().enumerate() {
if i > 0 {
spans.push(Span::styled(", ", Style::default().fg(Colors::text_secondary())));
spans.push(Span::styled(
", ",
Style::default().fg(Colors::text_secondary()),
));
}
spans.push(Span::styled(
LanguageRegistry::get_display_name(Some(lang)),
+14 -20
View File
@@ -3,32 +3,20 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
pub theme: ThemeConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ThemeConfig {
#[serde(default = "default_theme_id")]
pub current_theme_id: String,
pub current_color_mode: ColorMode,
}
impl Default for Config {
fn default() -> Self {
Config {
theme: ThemeConfig::default(),
}
}
}
impl Default for ThemeConfig {
fn default() -> Self {
ThemeConfig {
current_theme_id: "default".to_string(),
current_color_mode: ColorMode::default(),
}
}
fn default_theme_id() -> String {
"default".to_string()
}
pub struct ConfigManager {
@@ -52,7 +40,10 @@ impl ConfigManager {
Config::default()
};
Ok(ConfigManager { config, config_path })
Ok(ConfigManager {
config,
config_path,
})
}
pub fn with_config_path(config_path: PathBuf) -> anyhow::Result<Self> {
@@ -63,7 +54,10 @@ impl ConfigManager {
Config::default()
};
Ok(ConfigManager { config, config_path })
Ok(ConfigManager {
config,
config_path,
})
}
pub fn get_config(&self) -> &Config {
@@ -79,4 +73,4 @@ impl ConfigManager {
fs::write(&self.config_path, content)?;
Ok(())
}
}
}
+92 -23
View File
@@ -52,7 +52,10 @@ fn get_rank_messages() -> &'static HashMap<&'static str, Vec<(&'static str, Colo
vec![
("> explaining bug to inanimate object...", Colors::info()),
("> duck stares judgmentally at your code...", Colors::text()),
("> realizing bug while talking to duck...", Colors::warning()),
(
"> realizing bug while talking to duck...",
Colors::warning(),
),
(
"> duck takes full credit for the solution.",
Colors::success(),
@@ -82,7 +85,10 @@ fn get_rank_messages() -> &'static HashMap<&'static str, Vec<(&'static str, Colo
messages.insert(
"Bash Newbie",
vec![
("> typing 'cd ..' until something happens...", Colors::info()),
(
"> typing 'cd ..' until something happens...",
Colors::info(),
),
(
"> using 'ls' every 3 seconds to see where you are...",
Colors::text(),
@@ -153,8 +159,14 @@ fn get_rank_messages() -> &'static HashMap<&'static str, Vec<(&'static str, Colo
"Copy-Paste Engineer",
vec![
("> opening 50 tabs from stack overflow...", Colors::text()),
("> copying code from highest voted answer...", Colors::text()),
("> praying it works in your specific case...", Colors::text()),
(
"> copying code from highest voted answer...",
Colors::text(),
),
(
"> praying it works in your specific case...",
Colors::text(),
),
("> it works! time to copy more code.", Colors::success()),
],
);
@@ -186,8 +198,14 @@ fn get_rank_messages() -> &'static HashMap<&'static str, Vec<(&'static str, Colo
Colors::text(),
),
("> testing happy path exclusively...", Colors::text()),
("> achieving 100% code coverage on 5 lines...", Colors::info()),
("> testing complete. bugs remain untested.", Colors::success()),
(
"> achieving 100% code coverage on 5 lines...",
Colors::info(),
),
(
"> testing complete. bugs remain untested.",
Colors::success(),
),
],
);
@@ -199,8 +217,14 @@ fn get_rank_messages() -> &'static HashMap<&'static str, Vec<(&'static str, Colo
"> changing tutorial example from 'foo' to 'bar'...",
Colors::text(),
),
("> calling yourself a full-stack developer...", Colors::info()),
("> development skills: youtube certified.", Colors::success()),
(
"> calling yourself a full-stack developer...",
Colors::info(),
),
(
"> development skills: youtube certified.",
Colors::success(),
),
],
);
@@ -443,9 +467,15 @@ fn get_rank_messages_2() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> finding vulnerabilities in your personality...",
Colors::error(),
),
("> implementing security through obscurity...", Colors::info()),
(
"> implementing security through obscurity...",
Colors::info(),
),
("> penetration testing your patience...", Colors::text()),
("> security hardened. usability softened.", Colors::success()),
(
"> security hardened. usability softened.",
Colors::success(),
),
],
);
@@ -456,7 +486,10 @@ fn get_rank_messages_2() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> profiling bottlenecks in the profiler...",
Colors::warning(),
),
("> optimizing code that runs once per year...", Colors::info()),
(
"> optimizing code that runs once per year...",
Colors::info(),
),
(
"> caching everything including this message...",
Colors::text(),
@@ -520,7 +553,10 @@ fn get_rank_messages_2() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> choosing technologies based on latest blog posts...",
Colors::text(),
),
("> planning for scale that will never come...", Colors::text()),
(
"> planning for scale that will never come...",
Colors::text(),
),
(
"> architecture complete. implementation someone else's problem.",
Colors::success(),
@@ -547,12 +583,18 @@ fn get_rank_messages_2() -> &'static HashMap<&'static str, Vec<(&'static str, Co
messages.insert(
"Kernel Hacker",
vec![
("> compiling kernels that boot sometimes...", Colors::score()),
(
"> compiling kernels that boot sometimes...",
Colors::score(),
),
(
"> patching system calls with hopes and dreams...",
Colors::info(),
),
("> debugging at 3am with print statements...", Colors::text()),
(
"> debugging at 3am with print statements...",
Colors::text(),
),
(
"> kernel hacked successfully. computer may explode.",
Colors::success(),
@@ -584,7 +626,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> building AST while judging your variable names...",
Colors::info(),
),
("> optimizing away your inefficient loops...", Colors::text()),
(
"> optimizing away your inefficient loops...",
Colors::text(),
),
("> compiled successfully (somehow)", Colors::success()),
],
);
@@ -618,8 +663,14 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> virtualizing your already virtual environment...",
Colors::info(),
),
("> emulating hardware that doesn't exist...", Colors::score()),
("> allocating memory for your memory leaks...", Colors::text()),
(
"> emulating hardware that doesn't exist...",
Colors::score(),
),
(
"> allocating memory for your memory leaks...",
Colors::text(),
),
(
"> VM inception achieved. we need to go deeper.",
Colors::success(),
@@ -630,7 +681,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
messages.insert(
"Operating System",
vec![
("> scheduling processes that never finish...", Colors::score()),
(
"> scheduling processes that never finish...",
Colors::score(),
),
("> managing resources you don't have...", Colors::info()),
(
"> handling interrupts from impatient users...",
@@ -654,7 +708,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> implementing permissions nobody understands...",
Colors::info(),
),
("> fragmenting data across the entire disk...", Colors::text()),
(
"> fragmenting data across the entire disk...",
Colors::text(),
),
(
"> filesystem complete. good luck finding anything.",
Colors::success(),
@@ -665,7 +722,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
messages.insert(
"Network Stack",
vec![
("> layering protocols like a network cake...", Colors::info()),
(
"> layering protocols like a network cake...",
Colors::info(),
),
(
"> routing packets through the internet tubes...",
Colors::text(),
@@ -704,7 +764,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> analyzing execution plans nobody will read...",
Colors::text(),
),
("> optimizing joins that should be avoided...", Colors::info()),
(
"> optimizing joins that should be avoided...",
Colors::info(),
),
(
"> indexing everything (storage is cheap, right?)...",
Colors::text(),
@@ -934,7 +997,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"> pointing to nothing... and everything...",
Colors::warning(),
),
("> accessing the null space of reality...", Colors::warning()),
(
"> accessing the null space of reality...",
Colors::warning(),
),
(
"> FATAL: tried to read from /dev/null/universe",
Colors::error(),
@@ -965,7 +1031,10 @@ fn get_rank_messages_3() -> &'static HashMap<&'static str, Vec<(&'static str, Co
"Heisenbug",
vec![
("> bug exists in quantum superposition...", Colors::score()),
("> observation collapses the wave function...", Colors::info()),
(
"> observation collapses the wave function...",
Colors::info(),
),
(
"> Schrödinger's error: both fixed and broken...",
Colors::warning(),
+4 -1
View File
@@ -186,7 +186,10 @@ impl ScreenManager {
}
// Register Settings screen
self.register_screen(ScreenType::Settings, Box::new(crate::game::screens::SettingsScreen::default()));
self.register_screen(
ScreenType::Settings,
Box::new(crate::game::screens::SettingsScreen::default()),
);
Ok(())
}
+260 -65
View File
@@ -80,7 +80,10 @@ impl HelpScreen {
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("Base Score = CPM × (Accuracy / 100) × 10", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"Base Score = CPM × (Accuracy / 100) × 10",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![
Span::styled("CPM", Style::default().fg(Colors::cpm_wpm())),
@@ -115,11 +118,23 @@ impl HelpScreen {
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• Consistency Bonus: Up to 70% extra for high accuracy", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Time Bonus: Extra points for fast completion", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Mistake Penalty: -5 points per error", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Consistency Bonus: Up to 70% extra for high accuracy",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Time Bonus: Extra points for fast completion",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Mistake Penalty: -5 points per error",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("Final Score = (Base + Consistency + Time - Penalties) × 2 + 100", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"Final Score = (Base + Consistency + Time - Penalties) × 2 + 100",
Style::default().fg(Colors::text()),
)),
])
}
@@ -266,58 +281,148 @@ impl HelpScreen {
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• Standard: Type code from popular repositories", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Difficulty: Choose Easy, Normal, Hard, Wild, or Zen", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Standard: Type code from popular repositories",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Difficulty: Choose Easy, Normal, Hard, Wild, or Zen",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Typing Tips:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• Focus on accuracy over speed initially", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Use proper finger positioning", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Practice regularly to improve muscle memory", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Don't look at the keyboard while typing", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Take breaks to avoid fatigue", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Focus on accuracy over speed initially",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Use proper finger positioning",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Practice regularly to improve muscle memory",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Don't look at the keyboard while typing",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Take breaks to avoid fatigue",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Code Challenge Types:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("GitType extracts real code constructs from repositories:", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"GitType extracts real code constructs from repositories:",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("• Functions, methods, and procedures", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Classes, structs, and interfaces", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Enums, traits, and type definitions", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Variables, constants, and modules", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• React components and namespaces", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Control flow (loops, conditionals)", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Functions, methods, and procedures",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Classes, structs, and interfaces",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Enums, traits, and type definitions",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Variables, constants, and modules",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• React components and namespaces",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Control flow (loops, conditionals)",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Advanced Typing Tips:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• Use simultaneous key presses for efficiency:", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - For 'knock': press 'kno' with right hand almost simultaneously,", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" then 'ck' together", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Practice common letter combinations as single motions", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Use simultaneous key presses for efficiency:",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - For 'knock': press 'kno' with right hand almost simultaneously,",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" then 'ck' together",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Practice common letter combinations as single motions",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("• Master Shift key timing:", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Press Shift slightly before the target letter", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Use the opposite hand's Shift when possible", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Release Shift immediately after the letter", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Master Shift key timing:",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Press Shift slightly before the target letter",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Use the opposite hand's Shift when possible",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Release Shift immediately after the letter",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("• Optimize hand movement:", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Keep wrists straight and hands relaxed", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Use minimal finger movement", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Practice chord-like movements for common patterns", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Optimize hand movement:",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Keep wrists straight and hands relaxed",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Use minimal finger movement",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Practice chord-like movements for common patterns",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("• Code-specific techniques:", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Learn bracket/brace patterns as single motions", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Practice common variable naming conventions", Style::default().fg(Colors::text()))),
Line::from(Span::styled(" - Master punctuation placement without looking", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Code-specific techniques:",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Learn bracket/brace patterns as single motions",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Practice common variable naming conventions",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
" - Master punctuation placement without looking",
Style::default().fg(Colors::text()),
)),
])
}
@@ -481,7 +586,10 @@ impl HelpScreen {
format!("{:<33}", "~/.gittype/cache/"),
Style::default().fg(Colors::info()),
),
Span::styled("# Challenge cache", Style::default().fg(Colors::text_secondary())),
Span::styled(
"# Challenge cache",
Style::default().fg(Colors::text_secondary()),
),
]),
Line::from(vec![
Span::styled(
@@ -525,11 +633,23 @@ impl HelpScreen {
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("A CLI code-typing game that turns your source code into typing challenges", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"A CLI code-typing game that turns your source code into typing challenges",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("Practice typing with your own code repositories -", Style::default().fg(Colors::text()))),
Line::from(Span::styled("improve your speed and accuracy while working with", Style::default().fg(Colors::text()))),
Line::from(Span::styled("real functions, classes, and methods from your actual projects.", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"Practice typing with your own code repositories -",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"improve your speed and accuracy while working with",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"real functions, classes, and methods from your actual projects.",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Development Team:",
@@ -549,22 +669,52 @@ impl HelpScreen {
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• All open-source repository maintainers", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• The Rust community for excellent tooling", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Tree-sitter for code parsing capabilities", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Ratatui for terminal UI framework", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• All contributors and users providing feedback", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• All open-source repository maintainers",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• The Rust community for excellent tooling",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Tree-sitter for code parsing capabilities",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Ratatui for terminal UI framework",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• All contributors and users providing feedback",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Built with:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• Rust - Systems programming language", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Ratatui - Terminal user interface library", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Tree-sitter - Code parsing and syntax highlighting", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• SQLite - Local data storage", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Git2 - Repository cloning and management", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Rust - Systems programming language",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Ratatui - Terminal user interface library",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Tree-sitter - Code parsing and syntax highlighting",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• SQLite - Local data storage",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Git2 - Repository cloning and management",
Style::default().fg(Colors::text()),
)),
])
}
@@ -579,46 +729,91 @@ impl HelpScreen {
"GitHub Repository:",
Style::default().fg(Colors::success()).bold(),
)]),
Line::from(Span::styled("https://github.com/unhappychoice/gittype", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"https://github.com/unhappychoice/gittype",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(Span::styled("⭐ Star the repository if you enjoy GitType!", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"⭐ Star the repository if you enjoy GitType!",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Contributing:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("• Report bugs and suggest features via GitHub Issues", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Submit pull requests for improvements", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Add support for new programming languages", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Improve code extraction algorithms", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Enhance UI/UX design", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"• Report bugs and suggest features via GitHub Issues",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Submit pull requests for improvements",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Add support for new programming languages",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Improve code extraction algorithms",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Enhance UI/UX design",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Bug Reporting:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("When reporting bugs, please include:", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Operating system and terminal details", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Steps to reproduce the issue", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Expected vs actual behavior", Style::default().fg(Colors::text()))),
Line::from(Span::styled("• Any error messages or logs", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"When reporting bugs, please include:",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Operating system and terminal details",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Steps to reproduce the issue",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Expected vs actual behavior",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"• Any error messages or logs",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"Social Media:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("Share your progress with #gittype", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"Share your progress with #gittype",
Style::default().fg(Colors::text()),
)),
Line::from(""),
Line::from(vec![Span::styled(
"License:",
Style::default().fg(Colors::title()).bold(),
)]),
Line::from(""),
Line::from(Span::styled("GitType is open-source software.", Style::default().fg(Colors::text()))),
Line::from(Span::styled("Check the LICENSE file for details.", Style::default().fg(Colors::text()))),
Line::from(Span::styled(
"GitType is open-source software.",
Style::default().fg(Colors::text()),
)),
Line::from(Span::styled(
"Check the LICENSE file for details.",
Style::default().fg(Colors::text()),
)),
])
}
+2 -2
View File
@@ -10,13 +10,13 @@ pub mod session_details_dialog;
pub mod session_failure_screen;
pub mod session_summary_screen;
pub mod session_summary_share_screen;
pub mod settings_screen;
pub mod stage_summary_screen;
pub mod title_screen;
pub mod total_summary_screen;
pub mod total_summary_share_screen;
pub mod typing_screen;
pub mod version_check_screen;
pub mod settings_screen;
pub use analytics_screen::{AnalyticsAction, AnalyticsScreen};
pub use animation_screen::AnimationScreen;
@@ -30,10 +30,10 @@ pub use session_details_dialog::SessionDetailsDialog;
pub use session_failure_screen::SessionFailureScreen;
pub use session_summary_screen::{ResultAction, SessionSummaryScreen};
pub use session_summary_share_screen::SessionSummaryShareScreen;
pub use settings_screen::SettingsScreen;
pub use stage_summary_screen::StageSummaryScreen;
pub use title_screen::{TitleAction, TitleScreen};
pub use total_summary_screen::{ExitAction, TotalSummaryScreen};
pub use total_summary_share_screen::{ShareAction, TotalSummaryShareScreen};
pub use typing_screen::TypingScreen;
pub use version_check_screen::{VersionCheckResult, VersionCheckScreen};
pub use settings_screen::SettingsScreen;
+4 -1
View File
@@ -126,7 +126,10 @@ impl SessionDetailScreen {
);
let controls_line = Line::from(vec![
Span::styled("[↑↓/JK] Scroll Stages ", Style::default().fg(Colors::text())),
Span::styled(
"[↑↓/JK] Scroll Stages ",
Style::default().fg(Colors::text()),
),
Span::styled("[ESC]", Style::default().fg(Colors::error())),
Span::styled(" Back", Style::default().fg(Colors::text())),
]);
+98 -110
View File
@@ -1,8 +1,8 @@
use crate::game::models::{Screen, ScreenTransition};
use crate::ui::color_mode::ColorMode;
use crate::ui::colors::Colors;
use crate::ui::theme::Theme;
use crate::ui::theme_manager::THEME_MANAGER;
use crate::ui::colors::Colors;
use crate::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::{
@@ -64,7 +64,6 @@ impl Default for SettingsScreen {
let themes = theme_manager.get_available_themes();
drop(theme_manager); // Release the lock early
// Set initial selections
if let Some(pos) = color_modes.iter().position(|m| m == &current_color_mode) {
color_mode_state.select(Some(pos));
@@ -110,34 +109,34 @@ impl SettingsScreen {
}
fn save_settings(&mut self) {
self.is_preview_mode = false;
// Save theme and color mode to config file
if let Ok(mut config_manager) = crate::config::ConfigManager::new() {
let selected_color_mode = self.get_selected_color_mode();
let selected_theme = self.get_selected_theme();
if let (Some(color_mode), Some(theme)) = (selected_color_mode, selected_theme) {
config_manager.get_config_mut().theme.current_color_mode = color_mode.clone();
config_manager.get_config_mut().theme.current_theme_id = theme.id.clone();
let _ = config_manager.save();
self.is_preview_mode = false;
// Save theme and color mode to config file
if let Ok(mut config_manager) = crate::config::ConfigManager::new() {
let selected_color_mode = self.get_selected_color_mode();
let selected_theme = self.get_selected_theme();
if let (Some(color_mode), Some(theme)) = (selected_color_mode, selected_theme) {
config_manager.get_config_mut().theme.current_color_mode = color_mode.clone();
config_manager.get_config_mut().theme.current_theme_id = theme.id.clone();
let _ = config_manager.save();
}
}
}
}
fn get_selected_color_mode(&self) -> Option<&ColorMode> {
self.color_mode_state.selected()
self.color_mode_state
.selected()
.and_then(|i| self.color_modes.get(i))
}
fn get_selected_theme(&self) -> Option<&Theme> {
self.theme_state.selected()
.and_then(|i| self.themes.get(i))
self.theme_state.selected().and_then(|i| self.themes.get(i))
}
fn render_color_mode_section(&self, f: &mut Frame, area: Rect) {
let items: Vec<ListItem> = self.color_modes
let items: Vec<ListItem> = self
.color_modes
.iter()
.map(|mode| {
let text = match mode {
@@ -153,23 +152,18 @@ impl SettingsScreen {
Block::default()
.title("Color Mode")
.borders(Borders::ALL)
.border_style(Style::default().fg(Colors::border()))
.border_style(Style::default().fg(Colors::border())),
)
.highlight_style(
Style::default()
.bg(Colors::text())
.fg(Colors::background())
);
.highlight_style(Style::default().bg(Colors::text()).fg(Colors::background()));
f.render_stateful_widget(list, area, &mut self.color_mode_state.clone());
}
fn render_theme_section(&self, f: &mut Frame, area: Rect) {
let items: Vec<ListItem> = self.themes
let items: Vec<ListItem> = self
.themes
.iter()
.map(|theme| {
ListItem::new(theme.name.as_str())
})
.map(|theme| ListItem::new(theme.name.as_str()))
.collect();
let list = List::new(items)
@@ -177,76 +171,74 @@ impl SettingsScreen {
Block::default()
.title("Theme")
.borders(Borders::ALL)
.border_style(Style::default().fg(Colors::border()))
.border_style(Style::default().fg(Colors::border())),
)
.highlight_style(
Style::default()
.bg(Colors::text())
.fg(Colors::background())
);
.highlight_style(Style::default().bg(Colors::text()).fg(Colors::background()));
f.render_stateful_widget(list, area, &mut self.theme_state.clone());
}
fn render_description(&self, f: &mut Frame, area: Rect) {
let content = match self.current_section {
SettingsSection::ColorMode => {
vec![Line::from(self.current_section.description())]
},
SettingsSection::Theme => {
let mut lines = vec![Line::from(self.current_section.description())];
if let Some(theme) = self.get_selected_theme() {
lines.push(Line::from(""));
lines.push(Line::from(theme.description.as_str()));
lines.push(Line::from(""));
lines.push(Line::from("Color Preview:"));
// Add color preview lines with actual colors
let color_examples = vec![
("Border", Colors::border()),
("Title", Colors::title()),
("Text", Colors::text()),
("Text Secondary", Colors::text_secondary()),
("Success", Colors::success()),
("Error", Colors::error()),
("Warning", Colors::warning()),
("Info", Colors::info()),
("Key Action", Colors::key_action()),
("Key Navigation", Colors::key_navigation()),
("Key Back", Colors::key_back()),
("Typed Text", Colors::typed_text()),
("Cursor", Colors::current_cursor()),
("Mistake", Colors::mistake_bg()),
("Untyped Text", Colors::untyped_text()),
];
for (name, color) in color_examples {
lines.push(Line::from(vec![
Span::styled("", Style::default().fg(color)),
Span::styled(format!("This is {} color", name), Style::default().fg(color)),
]));
}
let content = match self.current_section {
SettingsSection::ColorMode => {
vec![Line::from(self.current_section.description())]
}
lines
}
};
let paragraph = Paragraph::new(content)
.style(Style::default().fg(Colors::text()))
.block(
Block::default()
.title("Description")
.borders(Borders::ALL)
.border_style(Style::default().fg(Colors::border()))
)
.wrap(Wrap { trim: true })
.alignment(Alignment::Left);
SettingsSection::Theme => {
let mut lines = vec![Line::from(self.current_section.description())];
f.render_widget(paragraph, area);
}
if let Some(theme) = self.get_selected_theme() {
lines.push(Line::from(""));
lines.push(Line::from(theme.description.as_str()));
lines.push(Line::from(""));
lines.push(Line::from("Color Preview:"));
// Add color preview lines with actual colors
let color_examples = vec![
("Border", Colors::border()),
("Title", Colors::title()),
("Text", Colors::text()),
("Text Secondary", Colors::text_secondary()),
("Success", Colors::success()),
("Error", Colors::error()),
("Warning", Colors::warning()),
("Info", Colors::info()),
("Key Action", Colors::key_action()),
("Key Navigation", Colors::key_navigation()),
("Key Back", Colors::key_back()),
("Typed Text", Colors::typed_text()),
("Cursor", Colors::current_cursor()),
("Mistake", Colors::mistake_bg()),
("Untyped Text", Colors::untyped_text()),
];
for (name, color) in color_examples {
lines.push(Line::from(vec![
Span::styled("", Style::default().fg(color)),
Span::styled(
format!("This is {} color", name),
Style::default().fg(color),
),
]));
}
}
lines
}
};
let paragraph = Paragraph::new(content)
.style(Style::default().fg(Colors::text()))
.block(
Block::default()
.title("Description")
.borders(Borders::ALL)
.border_style(Style::default().fg(Colors::border())),
)
.wrap(Wrap { trim: true })
.alignment(Alignment::Left);
f.render_widget(paragraph, area);
}
fn render_tabs(&self, f: &mut Frame, area: Rect) {
let sections = SettingsSection::all();
@@ -274,27 +266,23 @@ impl SettingsScreen {
}
fn render_content(&mut self, f: &mut Frame, area: Rect) {
let content_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(50),
Constraint::Percentage(50),
])
.margin(1)
.split(area);
let content_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.margin(1)
.split(area);
match self.current_section {
SettingsSection::ColorMode => {
self.render_color_mode_section(f, content_chunks[0]);
self.render_description(f, content_chunks[1]);
}
SettingsSection::Theme => {
self.render_theme_section(f, content_chunks[0]);
self.render_description(f, content_chunks[1]);
match self.current_section {
SettingsSection::ColorMode => {
self.render_color_mode_section(f, content_chunks[0]);
self.render_description(f, content_chunks[1]);
}
SettingsSection::Theme => {
self.render_theme_section(f, content_chunks[0]);
self.render_description(f, content_chunks[1]);
}
}
}
}
fn render_footer(&self, f: &mut Frame, area: Rect) {
let chunks = Layout::default()
@@ -440,4 +428,4 @@ impl Screen for SettingsScreen {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
}
+12 -4
View File
@@ -263,7 +263,10 @@ impl LanguagesView {
]),
Line::from(vec![
Span::raw(" "),
Span::styled("• Total Stages: ", Style::default().fg(Colors::stage_info())),
Span::styled(
"• Total Stages: ",
Style::default().fg(Colors::stage_info()),
),
Span::styled(
format!(
"{}/{} completed",
@@ -293,7 +296,10 @@ impl LanguagesView {
]),
Line::from(vec![
Span::raw(" "),
Span::styled("• Session Count: ", Style::default().fg(Colors::stage_info())),
Span::styled(
"• Session Count: ",
Style::default().fg(Colors::stage_info()),
),
Span::styled(
format!("{}", lang_data.2),
Style::default().fg(Colors::text()),
@@ -302,14 +308,16 @@ impl LanguagesView {
]);
}
lines
} else {
vec![
Line::from(""),
Line::from(vec![
Span::raw(" "),
Span::styled("No Language Selected", Style::default().fg(Colors::text_secondary())),
Span::styled(
"No Language Selected",
Style::default().fg(Colors::text_secondary()),
),
]),
Line::from(""),
Line::from(vec![
@@ -258,7 +258,10 @@ impl RepositoriesView {
]),
Line::from(vec![
Span::raw(" "),
Span::styled("• Total Stages: ", Style::default().fg(Colors::stage_info())),
Span::styled(
"• Total Stages: ",
Style::default().fg(Colors::stage_info()),
),
Span::styled(
format!(
"{}/{} completed",
@@ -289,14 +292,16 @@ impl RepositoriesView {
]);
}
lines
} else {
vec![
Line::from(""),
Line::from(vec![
Span::raw(" "),
Span::styled("No Repository Selected", Style::default().fg(Colors::text_secondary())),
Span::styled(
"No Repository Selected",
Style::default().fg(Colors::text_secondary()),
),
]),
Line::from(""),
Line::from(vec![
+8 -2
View File
@@ -103,12 +103,18 @@ impl TrendsView {
.style(Style::default().fg(Colors::text_secondary()))
.bounds([min_cpm - cpm_range * 0.1, max_cpm + cpm_range * 0.1])
.labels(vec![
Span::styled(format!("{:.0}", min_cpm), Style::default().fg(Colors::text())),
Span::styled(
format!("{:.0}", min_cpm),
Style::default().fg(Colors::text()),
),
Span::styled(
format!("{:.0}", (min_cpm + max_cpm) / 2.0),
Style::default().fg(Colors::text()),
),
Span::styled(format!("{:.0}", max_cpm), Style::default().fg(Colors::text())),
Span::styled(
format!("{:.0}", max_cpm),
Style::default().fg(Colors::text()),
),
]),
);
+2 -7
View File
@@ -45,11 +45,7 @@ impl DialogView {
.add_modifier(Modifier::BOLD),
)
} else {
Span::styled(
"[S] ",
Style::default()
.fg(Colors::text_secondary())
)
Span::styled("[S] ", Style::default().fg(Colors::text_secondary()))
},
if skips_remaining > 0 {
Span::styled(
@@ -59,8 +55,7 @@ impl DialogView {
} else {
Span::styled(
"No skips remaining",
Style::default()
.fg(Colors::text_secondary())
Style::default().fg(Colors::text_secondary()),
)
},
]),
@@ -56,11 +56,7 @@ impl LoadingDescriptionView {
Span::styled(format!("{} ", icon), Style::default().fg(color)),
Span::styled(
step_info.description.clone(),
Style::default().fg(if is_completed || is_current {
Colors::text_secondary()
} else {
Colors::text_secondary()
}),
Style::default().fg(Colors::text_secondary()),
),
]));
}
@@ -12,7 +12,10 @@ pub struct LoadingRepoInfoView;
impl LoadingRepoInfoView {
pub fn render(frame: &mut Frame, area: Rect, repo_info: &str) {
// Use same style as title_screen: DarkGrey color and centered
let repo_line = Line::from(Span::styled(repo_info, Style::default().fg(Colors::text_secondary())));
let repo_line = Line::from(Span::styled(
repo_info,
Style::default().fg(Colors::text_secondary()),
));
let repo_widget = Paragraph::new(vec![repo_line]).alignment(Alignment::Center);
@@ -84,7 +84,10 @@ impl PerformanceMetricsView {
metrics_lines.push(Line::from(vec![
Span::raw(" "),
Span::styled("Completed Stage: ", Style::default().fg(Colors::stage_info())),
Span::styled(
"Completed Stage: ",
Style::default().fg(Colors::stage_info()),
),
Span::styled(
result.stages_completed.to_string(),
Style::default().fg(Colors::text()),
@@ -90,7 +90,10 @@ impl BestRecordsView {
));
spans.push(Span::styled(" | ", Style::default().fg(Colors::text())));
spans.push(Span::styled("Acc ", Style::default().fg(Colors::accuracy())));
spans.push(Span::styled(
"Acc ",
Style::default().fg(Colors::accuracy()),
));
spans.push(Span::styled(
format!("{:.1}%", record.accuracy),
Style::default().fg(Colors::text()),
@@ -52,21 +52,30 @@ impl StageResultsView {
let mut metrics_spans = vec![];
metrics_spans.push(Span::styled(" ", Style::default()));
metrics_spans.push(Span::styled("Score: ", Style::default().fg(Colors::score())));
metrics_spans.push(Span::styled(
"Score: ",
Style::default().fg(Colors::score()),
));
metrics_spans.push(Span::styled(
format!("{:.0}", stage_result.challenge_score),
Style::default().fg(Colors::text()),
));
metrics_spans.push(Span::styled(" | ", Style::default().fg(Colors::text())));
metrics_spans.push(Span::styled("CPM: ", Style::default().fg(Colors::cpm_wpm())));
metrics_spans.push(Span::styled(
"CPM: ",
Style::default().fg(Colors::cpm_wpm()),
));
metrics_spans.push(Span::styled(
format!("{:.0}", stage_result.cpm),
Style::default().fg(Colors::text()),
));
metrics_spans.push(Span::styled(" | ", Style::default().fg(Colors::text())));
metrics_spans.push(Span::styled("Acc: ", Style::default().fg(Colors::accuracy())));
metrics_spans.push(Span::styled(
"Acc: ",
Style::default().fg(Colors::accuracy()),
));
metrics_spans.push(Span::styled(
format!("{:.1}%", stage_result.accuracy),
Style::default().fg(Colors::text()),
+8 -11
View File
@@ -148,8 +148,7 @@ impl TypingContentView {
);
let content_span = Span::styled(
pre_line.clone(),
Style::default()
.fg(Colors::text_secondary())
Style::default().fg(Colors::text_secondary()),
);
lines.push(Line::from(vec![line_num_span, content_span]));
}
@@ -174,8 +173,7 @@ impl TypingContentView {
);
let content_span = Span::styled(
post_line.clone(),
Style::default()
.fg(Colors::text_secondary())
Style::default().fg(Colors::text_secondary()),
);
lines.push(Line::from(vec![line_num_span, content_span]));
}
@@ -367,11 +365,9 @@ impl TypingContentView {
current_mistake_position: Option<usize>,
) -> Style {
if is_in_comment {
Style::default()
.fg(Colors::text_secondary())
Style::default().fg(Colors::text_secondary())
} else if char_index < current_display_position {
Style::default()
.fg(Colors::typed_text())
Style::default().fg(Colors::typed_text())
} else if char_index == current_display_position {
if let Some(mistake_pos) = current_mistake_position {
if char_index == mistake_pos {
@@ -384,11 +380,12 @@ impl TypingContentView {
.bg(Colors::cursor_bg())
}
} else {
Style::default().fg(Colors::current_cursor()).bg(Colors::cursor_bg())
Style::default()
.fg(Colors::current_cursor())
.bg(Colors::cursor_bg())
}
} else {
Style::default()
.fg(Colors::untyped_text())
Style::default().fg(Colors::untyped_text())
}
}
+2 -7
View File
@@ -45,11 +45,7 @@ impl TypingDialogView {
.add_modifier(Modifier::BOLD),
)
} else {
Span::styled(
"[S] ",
Style::default()
.fg(Colors::text_secondary())
)
Span::styled("[S] ", Style::default().fg(Colors::text_secondary()))
},
if skips_remaining > 0 {
Span::styled(
@@ -59,8 +55,7 @@ impl TypingDialogView {
} else {
Span::styled(
"No skips remaining",
Style::default()
.fg(Colors::text_secondary())
Style::default().fg(Colors::text_secondary()),
)
},
]),
+16 -4
View File
@@ -27,14 +27,20 @@ impl TypingHeaderView {
let base_title = challenge.get_display_title_with_repo(&git_repository.cloned());
// Create spans for colored language display before difficulty
let mut spans = vec![Span::styled(base_title, Style::default().fg(Colors::text_secondary()))];
let mut spans = vec![Span::styled(
base_title,
Style::default().fg(Colors::text_secondary()),
)];
// Add language with color if available
if let Some(ref language) = challenge.language {
use crate::extractor::models::language::LanguageRegistry;
let language_color = LanguageRegistry::get_color(Some(language));
let display_name = LanguageRegistry::get_display_name(Some(language));
spans.push(Span::styled(" ", Style::default().fg(Colors::text_secondary())));
spans.push(Span::styled(
" ",
Style::default().fg(Colors::text_secondary()),
));
spans.push(Span::styled(
format!("[{}]", display_name),
Style::default().fg(language_color),
@@ -42,11 +48,17 @@ impl TypingHeaderView {
}
// Add difficulty at the end
spans.push(Span::styled(format!(" [{}]", difficulty_text), Style::default().fg(Colors::text_secondary())));
spans.push(Span::styled(
format!(" [{}]", difficulty_text),
Style::default().fg(Colors::text_secondary()),
));
Line::from(spans)
} else {
Line::from(vec![Span::styled("[Challenge]", Style::default().fg(Colors::text_secondary()))])
Line::from(vec![Span::styled(
"[Challenge]",
Style::default().fg(Colors::text_secondary()),
)])
};
let header = Paragraph::new(vec![header_text]).block(
+2 -7
View File
@@ -1,13 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum ColorMode {
#[default]
Dark,
Light,
}
impl Default for ColorMode {
fn default() -> Self {
ColorMode::Dark
}
}
+169 -46
View File
@@ -1,7 +1,7 @@
use crate::ui::color_mode::ColorMode;
use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::ui::color_mode::ColorMode;
const LANG_DARK: &str = include_str!("../../assets/languages/lang_dark.json");
const LANG_LIGHT: &str = include_str!("../../assets/languages/lang_light.json");
@@ -28,7 +28,9 @@ impl CustomThemeFile {
ThemeFile {
id: "custom".to_string(),
name: "Custom".to_string(),
description: "Your personal custom theme - edit ~/.gittype/custom-theme.json to customize".to_string(),
description:
"Your personal custom theme - edit ~/.gittype/custom-theme.json to customize"
.to_string(),
dark: self.dark.clone(),
light: self.light.clone(),
}
@@ -87,7 +89,7 @@ impl From<SerializableColor> for Color {
// Fallback to white for unknown color names
Color::White
}
}
},
}
}
}
@@ -157,56 +159,178 @@ impl ColorScheme {
let lang_colors = Self::load_language_colors(theme_file, color_mode);
Self {
border: colors.get("border").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
title: colors.get("title").cloned().unwrap_or(SerializableColor::Name("white".to_string())),
text: colors.get("text").cloned().unwrap_or(SerializableColor::Name("white".to_string())),
text_secondary: colors.get("text_secondary").cloned().unwrap_or(SerializableColor::Name("gray".to_string())),
background: colors.get("background").cloned().unwrap_or(SerializableColor::Name("black".to_string())),
background_secondary: colors.get("background_secondary").cloned().unwrap_or(SerializableColor::Name("black".to_string())),
border: colors
.get("border")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
title: colors
.get("title")
.cloned()
.unwrap_or(SerializableColor::Name("white".to_string())),
text: colors
.get("text")
.cloned()
.unwrap_or(SerializableColor::Name("white".to_string())),
text_secondary: colors
.get("text_secondary")
.cloned()
.unwrap_or(SerializableColor::Name("gray".to_string())),
background: colors
.get("background")
.cloned()
.unwrap_or(SerializableColor::Name("black".to_string())),
background_secondary: colors
.get("background_secondary")
.cloned()
.unwrap_or(SerializableColor::Name("black".to_string())),
status_success: colors.get("status_success").cloned().unwrap_or(SerializableColor::Name("green".to_string())),
status_error: colors.get("status_error").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
status_warning: colors.get("status_warning").cloned().unwrap_or(SerializableColor::Name("yellow".to_string())),
status_info: colors.get("status_info").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
status_success: colors
.get("status_success")
.cloned()
.unwrap_or(SerializableColor::Name("green".to_string())),
status_error: colors
.get("status_error")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
status_warning: colors
.get("status_warning")
.cloned()
.unwrap_or(SerializableColor::Name("yellow".to_string())),
status_info: colors
.get("status_info")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
key_action: colors.get("key_action").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
key_navigation: colors.get("key_navigation").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
key_back: colors.get("key_back").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
key_action: colors
.get("key_action")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
key_navigation: colors
.get("key_navigation")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
key_back: colors
.get("key_back")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
metrics_score: colors.get("metrics_score").cloned().unwrap_or(SerializableColor::Name("magenta".to_string())),
metrics_cpm_wpm: colors.get("metrics_cpm_wpm").cloned().unwrap_or(SerializableColor::Name("green".to_string())),
metrics_accuracy: colors.get("metrics_accuracy").cloned().unwrap_or(SerializableColor::Name("yellow".to_string())),
metrics_duration: colors.get("metrics_duration").cloned().unwrap_or(SerializableColor::Name("cyan".to_string())),
metrics_stage_info: colors.get("metrics_stage_info").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
metrics_score: colors
.get("metrics_score")
.cloned()
.unwrap_or(SerializableColor::Name("magenta".to_string())),
metrics_cpm_wpm: colors
.get("metrics_cpm_wpm")
.cloned()
.unwrap_or(SerializableColor::Name("green".to_string())),
metrics_accuracy: colors
.get("metrics_accuracy")
.cloned()
.unwrap_or(SerializableColor::Name("yellow".to_string())),
metrics_duration: colors
.get("metrics_duration")
.cloned()
.unwrap_or(SerializableColor::Name("cyan".to_string())),
metrics_stage_info: colors
.get("metrics_stage_info")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
typing_typed_text: colors.get("typing_typed_text").cloned().unwrap_or(SerializableColor::Name("green".to_string())),
typing_cursor_fg: colors.get("typing_cursor_fg").cloned().unwrap_or(SerializableColor::Name("white".to_string())),
typing_cursor_bg: colors.get("typing_cursor_bg").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
typing_mistake_bg: colors.get("typing_mistake_bg").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
typing_untyped_text: colors.get("typing_untyped_text").cloned().unwrap_or(SerializableColor::Name("gray".to_string())),
typing_typed_text: colors
.get("typing_typed_text")
.cloned()
.unwrap_or(SerializableColor::Name("green".to_string())),
typing_cursor_fg: colors
.get("typing_cursor_fg")
.cloned()
.unwrap_or(SerializableColor::Name("white".to_string())),
typing_cursor_bg: colors
.get("typing_cursor_bg")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
typing_mistake_bg: colors
.get("typing_mistake_bg")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
typing_untyped_text: colors
.get("typing_untyped_text")
.cloned()
.unwrap_or(SerializableColor::Name("gray".to_string())),
lang_rust: lang_colors.get("lang_rust").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
lang_python: lang_colors.get("lang_python").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
lang_javascript: lang_colors.get("lang_javascript").cloned().unwrap_or(SerializableColor::Name("yellow".to_string())),
lang_typescript: lang_colors.get("lang_typescript").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
lang_go: lang_colors.get("lang_go").cloned().unwrap_or(SerializableColor::Name("cyan".to_string())),
lang_java: lang_colors.get("lang_java").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
lang_c: lang_colors.get("lang_c").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
lang_cpp: lang_colors.get("lang_cpp").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
lang_csharp: lang_colors.get("lang_csharp").cloned().unwrap_or(SerializableColor::Name("green".to_string())),
lang_php: lang_colors.get("lang_php").cloned().unwrap_or(SerializableColor::Name("magenta".to_string())),
lang_ruby: lang_colors.get("lang_ruby").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
lang_swift: lang_colors.get("lang_swift").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
lang_kotlin: lang_colors.get("lang_kotlin").cloned().unwrap_or(SerializableColor::Name("magenta".to_string())),
lang_scala: lang_colors.get("lang_scala").cloned().unwrap_or(SerializableColor::Name("red".to_string())),
lang_haskell: lang_colors.get("lang_haskell").cloned().unwrap_or(SerializableColor::Name("magenta".to_string())),
lang_dart: lang_colors.get("lang_dart").cloned().unwrap_or(SerializableColor::Name("blue".to_string())),
lang_default: lang_colors.get("lang_default").cloned().unwrap_or(SerializableColor::Name("white".to_string())),
lang_rust: lang_colors
.get("lang_rust")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
lang_python: lang_colors
.get("lang_python")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
lang_javascript: lang_colors
.get("lang_javascript")
.cloned()
.unwrap_or(SerializableColor::Name("yellow".to_string())),
lang_typescript: lang_colors
.get("lang_typescript")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
lang_go: lang_colors
.get("lang_go")
.cloned()
.unwrap_or(SerializableColor::Name("cyan".to_string())),
lang_java: lang_colors
.get("lang_java")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
lang_c: lang_colors
.get("lang_c")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
lang_cpp: lang_colors
.get("lang_cpp")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
lang_csharp: lang_colors
.get("lang_csharp")
.cloned()
.unwrap_or(SerializableColor::Name("green".to_string())),
lang_php: lang_colors
.get("lang_php")
.cloned()
.unwrap_or(SerializableColor::Name("magenta".to_string())),
lang_ruby: lang_colors
.get("lang_ruby")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
lang_swift: lang_colors
.get("lang_swift")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
lang_kotlin: lang_colors
.get("lang_kotlin")
.cloned()
.unwrap_or(SerializableColor::Name("magenta".to_string())),
lang_scala: lang_colors
.get("lang_scala")
.cloned()
.unwrap_or(SerializableColor::Name("red".to_string())),
lang_haskell: lang_colors
.get("lang_haskell")
.cloned()
.unwrap_or(SerializableColor::Name("magenta".to_string())),
lang_dart: lang_colors
.get("lang_dart")
.cloned()
.unwrap_or(SerializableColor::Name("blue".to_string())),
lang_default: lang_colors
.get("lang_default")
.cloned()
.unwrap_or(SerializableColor::Name("white".to_string())),
}
}
fn load_language_colors(theme_file: &ThemeFile, color_mode: &ColorMode) -> HashMap<String, SerializableColor> {
fn load_language_colors(
theme_file: &ThemeFile,
color_mode: &ColorMode,
) -> HashMap<String, SerializableColor> {
let lang_json = match (theme_file.id.as_str(), color_mode) {
("ascii", _) => LANG_ASCII,
(_, ColorMode::Light) => LANG_LIGHT,
@@ -214,5 +338,4 @@ impl ColorScheme {
};
serde_json::from_str(lang_json).unwrap_or_default()
}
}
+133 -45
View File
@@ -1,6 +1,6 @@
use ratatui::style::Color;
use crate::ui::color_scheme::ColorScheme;
use crate::ui::theme_manager::ThemeManager;
use ratatui::style::Color;
/// UI color scheme for gittype application
pub struct Colors;
@@ -12,62 +12,150 @@ impl Colors {
}
// Primary colors for main UI elements
pub fn border() -> Color { Self::get_color_scheme().border.into() }
pub fn title() -> Color { Self::get_color_scheme().title.into() }
pub fn text() -> Color { Self::get_color_scheme().text.into() }
pub fn text_secondary() -> Color { Self::get_color_scheme().text_secondary.into() }
pub fn background() -> Color { Self::get_color_scheme().background.into() }
pub fn background_secondary() -> Color { Self::get_color_scheme().background_secondary.into() }
pub fn border() -> Color {
Self::get_color_scheme().border.into()
}
pub fn title() -> Color {
Self::get_color_scheme().title.into()
}
pub fn text() -> Color {
Self::get_color_scheme().text.into()
}
pub fn text_secondary() -> Color {
Self::get_color_scheme().text_secondary.into()
}
pub fn background() -> Color {
Self::get_color_scheme().background.into()
}
pub fn background_secondary() -> Color {
Self::get_color_scheme().background_secondary.into()
}
// Status and feedback colors
pub fn success() -> Color { Self::get_color_scheme().status_success.into() }
pub fn info() -> Color { Self::get_color_scheme().status_info.into() }
pub fn error() -> Color { Self::get_color_scheme().status_error.into() }
pub fn warning() -> Color { Self::get_color_scheme().status_warning.into() }
pub fn success() -> Color {
Self::get_color_scheme().status_success.into()
}
pub fn info() -> Color {
Self::get_color_scheme().status_info.into()
}
pub fn error() -> Color {
Self::get_color_scheme().status_error.into()
}
pub fn warning() -> Color {
Self::get_color_scheme().status_warning.into()
}
// Specific UI element colors
pub fn key_action() -> Color { Self::get_color_scheme().key_action.into() }
pub fn key_navigation() -> Color { Self::get_color_scheme().key_navigation.into() }
pub fn key_back() -> Color { Self::get_color_scheme().key_back.into() }
pub fn key_action() -> Color {
Self::get_color_scheme().key_action.into()
}
pub fn key_navigation() -> Color {
Self::get_color_scheme().key_navigation.into()
}
pub fn key_back() -> Color {
Self::get_color_scheme().key_back.into()
}
// Metrics and performance colors
pub fn score() -> Color { Self::get_color_scheme().metrics_score.into() }
pub fn cpm_wpm() -> Color { Self::get_color_scheme().metrics_cpm_wpm.into() }
pub fn accuracy() -> Color { Self::get_color_scheme().metrics_accuracy.into() }
pub fn duration() -> Color { Self::get_color_scheme().metrics_duration.into() }
pub fn stage_info() -> Color { Self::get_color_scheme().metrics_stage_info.into() }
pub fn score() -> Color {
Self::get_color_scheme().metrics_score.into()
}
pub fn cpm_wpm() -> Color {
Self::get_color_scheme().metrics_cpm_wpm.into()
}
pub fn accuracy() -> Color {
Self::get_color_scheme().metrics_accuracy.into()
}
pub fn duration() -> Color {
Self::get_color_scheme().metrics_duration.into()
}
pub fn stage_info() -> Color {
Self::get_color_scheme().metrics_stage_info.into()
}
// Typing interface colors
pub fn typed_text() -> Color { Self::get_color_scheme().typing_typed_text.into() }
pub fn current_cursor() -> Color { Self::get_color_scheme().typing_cursor_fg.into() }
pub fn cursor_bg() -> Color { Self::get_color_scheme().typing_cursor_bg.into() }
pub fn mistake_bg() -> Color { Self::get_color_scheme().typing_mistake_bg.into() }
pub fn untyped_text() -> Color { Self::get_color_scheme().typing_untyped_text.into() }
pub fn typed_text() -> Color {
Self::get_color_scheme().typing_typed_text.into()
}
pub fn current_cursor() -> Color {
Self::get_color_scheme().typing_cursor_fg.into()
}
pub fn cursor_bg() -> Color {
Self::get_color_scheme().typing_cursor_bg.into()
}
pub fn mistake_bg() -> Color {
Self::get_color_scheme().typing_mistake_bg.into()
}
pub fn untyped_text() -> Color {
Self::get_color_scheme().typing_untyped_text.into()
}
// Countdown colors - using status colors in sequence
pub fn countdown_3() -> Color { Self::success() }
pub fn countdown_2() -> Color { Self::info() }
pub fn countdown_1() -> Color { Self::warning() }
pub fn countdown_go() -> Color { Self::error() }
pub fn countdown_3() -> Color {
Self::success()
}
pub fn countdown_2() -> Color {
Self::info()
}
pub fn countdown_1() -> Color {
Self::warning()
}
pub fn countdown_go() -> Color {
Self::error()
}
// Programming language colors
pub fn lang_rust() -> Color { Self::get_color_scheme().lang_rust.into() }
pub fn lang_python() -> Color { Self::get_color_scheme().lang_python.into() }
pub fn lang_javascript() -> Color { Self::get_color_scheme().lang_javascript.into() }
pub fn lang_typescript() -> Color { Self::get_color_scheme().lang_typescript.into() }
pub fn lang_go() -> Color { Self::get_color_scheme().lang_go.into() }
pub fn lang_java() -> Color { Self::get_color_scheme().lang_java.into() }
pub fn lang_c() -> Color { Self::get_color_scheme().lang_c.into() }
pub fn lang_cpp() -> Color { Self::get_color_scheme().lang_cpp.into() }
pub fn lang_csharp() -> Color { Self::get_color_scheme().lang_csharp.into() }
pub fn lang_php() -> Color { Self::get_color_scheme().lang_php.into() }
pub fn lang_ruby() -> Color { Self::get_color_scheme().lang_ruby.into() }
pub fn lang_swift() -> Color { Self::get_color_scheme().lang_swift.into() }
pub fn lang_kotlin() -> Color { Self::get_color_scheme().lang_kotlin.into() }
pub fn lang_scala() -> Color { Self::get_color_scheme().lang_scala.into() }
pub fn lang_haskell() -> Color { Self::get_color_scheme().lang_haskell.into() }
pub fn lang_dart() -> Color { Self::get_color_scheme().lang_dart.into() }
pub fn lang_default() -> Color { Self::get_color_scheme().lang_default.into() }
pub fn lang_rust() -> Color {
Self::get_color_scheme().lang_rust.into()
}
pub fn lang_python() -> Color {
Self::get_color_scheme().lang_python.into()
}
pub fn lang_javascript() -> Color {
Self::get_color_scheme().lang_javascript.into()
}
pub fn lang_typescript() -> Color {
Self::get_color_scheme().lang_typescript.into()
}
pub fn lang_go() -> Color {
Self::get_color_scheme().lang_go.into()
}
pub fn lang_java() -> Color {
Self::get_color_scheme().lang_java.into()
}
pub fn lang_c() -> Color {
Self::get_color_scheme().lang_c.into()
}
pub fn lang_cpp() -> Color {
Self::get_color_scheme().lang_cpp.into()
}
pub fn lang_csharp() -> Color {
Self::get_color_scheme().lang_csharp.into()
}
pub fn lang_php() -> Color {
Self::get_color_scheme().lang_php.into()
}
pub fn lang_ruby() -> Color {
Self::get_color_scheme().lang_ruby.into()
}
pub fn lang_swift() -> Color {
Self::get_color_scheme().lang_swift.into()
}
pub fn lang_kotlin() -> Color {
Self::get_color_scheme().lang_kotlin.into()
}
pub fn lang_scala() -> Color {
Self::get_color_scheme().lang_scala.into()
}
pub fn lang_haskell() -> Color {
Self::get_color_scheme().lang_haskell.into()
}
pub fn lang_dart() -> Color {
Self::get_color_scheme().lang_dart.into()
}
pub fn lang_default() -> Color {
Self::get_color_scheme().lang_default.into()
}
}
impl Colors {
+1 -1
View File
@@ -1,6 +1,6 @@
pub mod colors;
pub mod color_mode;
pub mod color_scheme;
pub mod colors;
pub mod theme;
pub mod theme_manager;
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::ui::color_scheme::ColorScheme;
use crate::ui::color_mode::ColorMode;
use crate::ui::color_scheme::ColorScheme;
use serde::{Deserialize, Serialize};
const THEME_FILES: &[&str] = &[
@@ -42,7 +42,7 @@ impl Theme {
/// Get all builtin themes
pub fn all_themes() -> Vec<Self> {
THEME_FILES
.into_iter()
.iter()
.map(|json| {
let theme_file: crate::ui::color_scheme::ThemeFile =
serde_json::from_str(json).expect("Failed to parse theme JSON");
+10 -9
View File
@@ -34,14 +34,14 @@ impl ThemeManager {
let current_color_mode = config.theme.current_color_mode.clone();
let mut manager = THEME_MANAGER.write().unwrap();
// Find theme by ID
let available_themes = manager.get_available_themes();
let current_theme = available_themes
.into_iter()
.find(|t| t.id == current_theme_id)
.unwrap_or_else(|| Theme::default());
.unwrap_or_else(Theme::default);
manager.current_theme = current_theme;
manager.current_color_mode = current_color_mode;
Ok(())
@@ -61,7 +61,6 @@ impl ThemeManager {
}
}
/// Get all available themes
pub fn get_available_themes(&self) -> Vec<Theme> {
Theme::all_themes()
@@ -84,7 +83,7 @@ impl ThemeManager {
dark,
light,
}
})
}),
)
.collect()
}
@@ -92,13 +91,15 @@ impl ThemeManager {
/// Get the custom theme file path
fn get_custom_theme_path() -> std::path::PathBuf {
let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
std::path::PathBuf::from(home_dir).join(".gittype").join("custom-theme.json")
std::path::PathBuf::from(home_dir)
.join(".gittype")
.join("custom-theme.json")
}
/// Create default custom theme file if it doesn't exist
fn create_default_custom_theme_file() -> anyhow::Result<()> {
let custom_theme_path = Self::get_custom_theme_path();
if custom_theme_path.exists() {
return Ok(());
}
@@ -111,7 +112,7 @@ impl ThemeManager {
// Create default custom theme based on the default theme
let default_theme_json = include_str!("../../assets/themes/default.json");
let default_theme_file: ThemeFile = serde_json::from_str(default_theme_json)?;
let custom_theme = CustomThemeFile {
dark: default_theme_file.dark,
light: default_theme_file.light,
@@ -119,7 +120,7 @@ impl ThemeManager {
let custom_theme_json = serde_json::to_string_pretty(&custom_theme)?;
std::fs::write(&custom_theme_path, custom_theme_json)?;
Ok(())
}
}
+1 -1
View File
@@ -1 +1 @@
pub mod theme_tests;
pub mod theme_tests;
+31 -28
View File
@@ -1,43 +1,40 @@
use gittype::config::{ColorScheme, SerializableColor, Theme, ThemeConfig, ThemeFile, ThemeManager};
use gittype::config::ThemeConfig;
use gittype::ui::color_mode::ColorMode;
use gittype::ui::color_scheme::{ColorScheme, SerializableColor, ThemeFile};
use ratatui::style::Color;
use std::collections::HashMap;
use tempfile::tempdir;
#[test]
fn test_color_scheme_conversion() {
let scheme = ColorScheme::ascii();
// Load ascii theme file and create color scheme
let ascii_json = include_str!("../../../assets/themes/ascii.json");
let theme_file: ThemeFile = serde_json::from_str(ascii_json).unwrap();
let scheme = ColorScheme::from_theme_file(&theme_file, &ColorMode::Dark);
let color: Color = scheme.border.into();
assert!(matches!(color, Color::Rgb(100, 149, 237))); // Should be RGB color now
// ASCII theme uses named colors like "blue"
assert_eq!(color, Color::Blue);
}
#[test]
fn test_theme_config_default() {
let config = ThemeConfig::default();
assert_eq!(config.current_theme_id, "default");
assert!(config.custom_themes.is_empty());
// ThemeConfig no longer has custom_themes field
}
// #[test]
// fn test_theme_manager_with_temp_config() {
// // TODO: Update this test after ThemeManager API changes
// }
#[test]
fn test_predefined_themes() {
let ascii = ColorScheme::ascii();
let custom_scheme = ColorScheme::ascii();
// Load ascii theme file and create color schemes
let ascii_json = include_str!("../../../assets/themes/ascii.json");
let theme_file: ThemeFile = serde_json::from_str(ascii_json).unwrap();
let ascii = ColorScheme::from_theme_file(&theme_file, &ColorMode::Dark);
// Test that color conversion works
let ascii_bg: Color = ascii.background.clone().into();
let ascii_text: Color = ascii.text.clone().into();
// Should be RGB colors now
matches!(ascii_bg, Color::Rgb(0, 0, 0));
matches!(ascii_text, Color::Rgb(255, 255, 255));
// ASCII theme should use RGB colors
assert!(matches!(ascii_bg, Color::Rgb(0, 0, 0))); // Black background
assert!(matches!(ascii_text, Color::Rgb(255, 255, 255))); // White text
// ASCII theme uses named colors
assert_eq!(ascii_bg, Color::Black);
assert_eq!(ascii_text, Color::White);
}
// #[test]
@@ -72,19 +69,21 @@ fn test_theme_file_parsing() {
#[test]
fn test_embedded_themes_load_correctly() {
// Test that both embedded themes load without panicking
let ascii_scheme = ColorScheme::ascii();
// Test that embedded themes load without panicking
let ascii_json = include_str!("../../../assets/themes/ascii.json");
let theme_file: ThemeFile = serde_json::from_str(ascii_json).unwrap();
let ascii_scheme = ColorScheme::from_theme_file(&theme_file, &ColorMode::Dark);
// Verify RGB color loading
// Verify color loading
let ascii_bg: Color = ascii_scheme.background.clone().into();
assert!(matches!(ascii_bg, Color::Rgb(0, 0, 0)));
assert_eq!(ascii_bg, Color::Black);
// Test some specific colors to ensure JSON loading worked
let ascii_bg: Color = ascii_scheme.background.into();
let ascii_text: Color = ascii_scheme.text.into();
assert!(matches!(ascii_bg, Color::Rgb(0, 0, 0))); // Should be RGB black
assert!(matches!(ascii_text, Color::Rgb(255, 255, 255))); // Should be RGB white
assert_eq!(ascii_bg, Color::Black); // Should be black
assert_eq!(ascii_text, Color::White); // Should be white
}
#[test]
@@ -133,11 +132,15 @@ fn test_hex_color_parsing() {
#[test]
fn test_rgb_and_name_serialization() {
// Test that both RGB and name formats work
let rgb_color = SerializableColor::Rgb { r: 255, g: 128, b: 0 };
let rgb_color = SerializableColor::Rgb {
r: 255,
g: 128,
b: 0,
};
let color: Color = rgb_color.into();
assert_eq!(color, Color::Rgb(255, 128, 0));
let name_color = SerializableColor::Name("cyan".to_string());
let color: Color = name_color.into();
assert_eq!(color, Color::Cyan);
}
}
+2 -1
View File
@@ -56,5 +56,6 @@ fn rank_for_score_returns_correct_rank() {
fn rank_for_score_defaults_to_highest_when_exceeded() {
let rank = Rank::for_score(999_999.0);
assert_eq!(rank.tier(), &RankTier::Legendary);
assert_eq!(rank.terminal_color(), TerminalColor::Red);
// Legendary tier uses error color which is now RGB
assert!(matches!(rank.terminal_color(), TerminalColor::Rgb { .. }));
}