project structure redesign

This commit is contained in:
filipriec
2025-02-25 23:20:29 +01:00
parent 08bbd9bd29
commit 5010b73bab
15 changed files with 67 additions and 14 deletions

View 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
View 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
View File

@@ -0,0 +1,3 @@
// src/config/mod.rs
pub mod colors;
pub mod config;