project structure redesign
This commit is contained in:
68
client/src/config/colors.rs
Normal file
68
client/src/config/colors.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
// src/client/colors.rs
|
||||
use ratatui::style::Color;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Theme {
|
||||
pub bg: Color,
|
||||
pub fg: Color,
|
||||
pub accent: Color,
|
||||
pub secondary: Color,
|
||||
pub highlight: Color,
|
||||
pub warning: Color,
|
||||
pub border: Color,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn from_str(theme_name: &str) -> Self {
|
||||
match theme_name.to_lowercase().as_str() {
|
||||
"dark" => Self::dark(),
|
||||
"high_contrast" => Self::high_contrast(),
|
||||
_ => Self::light(),
|
||||
}
|
||||
}
|
||||
|
||||
// Default light theme
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
bg: Color::Rgb(245, 245, 245), // Light gray
|
||||
fg: Color::Rgb(64, 64, 64), // Dark gray
|
||||
accent: Color::Rgb(173, 216, 230), // Pastel blue
|
||||
secondary: Color::Rgb(255, 165, 0), // Orange for secondary
|
||||
highlight: Color::Rgb(152, 251, 152), // Pastel green
|
||||
warning: Color::Rgb(255, 182, 193), // Pastel pink
|
||||
border: Color::Rgb(220, 220, 220), // Light gray border
|
||||
}
|
||||
}
|
||||
|
||||
// High-contrast dark theme
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
bg: Color::Rgb(30, 30, 30), // Dark background
|
||||
fg: Color::Rgb(255, 255, 255), // White text
|
||||
accent: Color::Rgb(0, 191, 255), // Bright blue
|
||||
secondary: Color::Rgb(255, 215, 0), // Gold for secondary
|
||||
highlight: Color::Rgb(50, 205, 50), // Bright green
|
||||
warning: Color::Rgb(255, 99, 71), // Bright red
|
||||
border: Color::Rgb(100, 100, 100), // Medium gray border
|
||||
}
|
||||
}
|
||||
|
||||
// High-contrast light theme
|
||||
pub fn high_contrast() -> Self {
|
||||
Self {
|
||||
bg: Color::Rgb(255, 255, 255), // White background
|
||||
fg: Color::Rgb(0, 0, 0), // Black text
|
||||
accent: Color::Rgb(0, 0, 255), // Blue
|
||||
secondary: Color::Rgb(255, 140, 0), // Dark orange for secondary
|
||||
highlight: Color::Rgb(0, 128, 0), // Green
|
||||
warning: Color::Rgb(255, 0, 0), // Red
|
||||
border: Color::Rgb(0, 0, 0), // Black border
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Self::light() // Default to light theme
|
||||
}
|
||||
}
|
||||
134
client/src/config/config.rs
Normal file
134
client/src/config/config.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
// client/src/config.rs
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct ColorsConfig {
|
||||
#[serde(default = "default_theme")]
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
fn default_theme() -> String {
|
||||
"light".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub keybindings: HashMap<String, Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub colors: ColorsConfig,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Loads the configuration from "config.toml" in the client crate directory.
|
||||
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
// env!("CARGO_MANIFEST_DIR") resolves to the directory where the Cargo.toml of
|
||||
// the client crate is located.
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let config_path = Path::new(manifest_dir).join("config.toml");
|
||||
let config_str = std::fs::read_to_string(&config_path)
|
||||
.map_err(|e| format!("Failed to read config file at {:?}: {}", config_path, e))?;
|
||||
let config: Config = toml::from_str(&config_str)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn get_action_for_key(
|
||||
&self,
|
||||
key: KeyCode,
|
||||
modifiers: KeyModifiers,
|
||||
) -> Option<&str> {
|
||||
for (action, bindings) in &self.keybindings {
|
||||
// Skip mode toggle actions
|
||||
if action == "enter_edit_mode" || action == "exit_edit_mode" {
|
||||
continue;
|
||||
}
|
||||
for binding in bindings {
|
||||
if Self::matches_keybinding(binding, key, modifiers) {
|
||||
return Some(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn matches_keybinding(
|
||||
binding: &str,
|
||||
key: KeyCode,
|
||||
modifiers: KeyModifiers,
|
||||
) -> bool {
|
||||
let parts: Vec<&str> = binding.split('+').collect();
|
||||
let mut expected_modifiers = KeyModifiers::empty();
|
||||
let mut expected_key = None;
|
||||
|
||||
for part in parts {
|
||||
match part.to_lowercase().as_str() {
|
||||
"ctrl" => expected_modifiers |= KeyModifiers::CONTROL,
|
||||
"shift" => expected_modifiers |= KeyModifiers::SHIFT,
|
||||
"alt" => expected_modifiers |= KeyModifiers::ALT,
|
||||
"esc" => expected_key = Some(KeyCode::Esc),
|
||||
":" => expected_key = Some(KeyCode::Char(':')),
|
||||
part if part.len() == 1 => {
|
||||
let c = part.chars().next().unwrap();
|
||||
expected_key = Some(KeyCode::Char(c));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
modifiers == expected_modifiers && Some(key) == expected_key
|
||||
}
|
||||
|
||||
pub fn get_action_for_command(&self, command: &str) -> Option<&str> {
|
||||
for (action, bindings) in &self.keybindings {
|
||||
for binding in bindings {
|
||||
if binding.starts_with(':') && binding.trim_start_matches(':') == command {
|
||||
return Some(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_enter_edit_mode_before(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
|
||||
if let Some(bindings) = self.keybindings.get("enter_edit_mode_before") {
|
||||
bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_enter_edit_mode_after(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
|
||||
if let Some(bindings) = self.keybindings.get("enter_edit_mode_after") {
|
||||
bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_enter_edit_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
|
||||
self.is_enter_edit_mode_before(key, modifiers) ||
|
||||
self.is_enter_edit_mode_after(key, modifiers)
|
||||
}
|
||||
|
||||
pub fn is_exit_edit_mode(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
|
||||
if let Some(bindings) = self.keybindings.get("exit_edit_mode") {
|
||||
bindings.iter().any(|b| Self::matches_keybinding(b, key, modifiers))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_key_for_action(&self, action: &str, key_char: char) -> bool {
|
||||
if let Some(bindings) = self.keybindings.get(action) {
|
||||
for binding in bindings {
|
||||
if binding == &key_char.to_string() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
3
client/src/config/mod.rs
Normal file
3
client/src/config/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// src/config/mod.rs
|
||||
pub mod colors;
|
||||
pub mod config;
|
||||
Reference in New Issue
Block a user