keyboard-codes 0.2.0

Cross-platform keyboard key code mapping and conversion
Documentation
use crate::error::KeyParseError;
use crate::types::Platform;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};

/// Custom key definition
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CustomKey {
    name: String,
    codes: HashMap<Platform, usize>,
}

// 手动实现 Hash,只基于 name 字段
impl Hash for CustomKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

impl CustomKey {
    /// Create a new custom key with the given name
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            codes: HashMap::new(),
        }
    }

    /// Add a platform-specific code mapping for this key
    pub fn add_platform_code(&mut self, platform: Platform, code: usize) -> &mut Self {
        self.codes.insert(platform, code);
        self
    }

    /// Get the key name
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the platform-specific code for this key
    pub fn code(&self, platform: Platform) -> Option<usize> {
        self.codes.get(&platform).copied()
    }

    /// Get all platform codes for this key
    pub fn codes(&self) -> &HashMap<Platform, usize> {
        &self.codes
    }
}

/// Custom key mapping manager
#[derive(Debug, Default)]
pub struct CustomKeyMap {
    name_to_key: HashMap<String, CustomKey>,
    code_to_key: HashMap<(usize, Platform), CustomKey>,
}

impl CustomKeyMap {
    /// Create a new empty custom key mapping manager
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a custom key to the mapping
    ///
    /// # Errors
    ///
    /// Returns `KeyParseError::DuplicateCustomKey` if a key with the same name already exists
    pub fn add_key(&mut self, key: CustomKey) -> Result<(), KeyParseError> {
        if self.name_to_key.contains_key(key.name()) {
            return Err(KeyParseError::DuplicateCustomKey(key.name().to_string()));
        }

        // Record name to key mapping
        self.name_to_key.insert(key.name().to_string(), key.clone());

        // Record (code, platform) to key mapping
        for (platform, code) in key.codes() {
            self.code_to_key.insert((*code, *platform), key.clone());
        }

        Ok(())
    }

    /// Remove a custom key by name
    pub fn remove_key(&mut self, name: &str) -> Option<CustomKey> {
        if let Some(key) = self.name_to_key.remove(name) {
            // Remove all code mappings for this key
            for (platform, code) in key.codes() {
                self.code_to_key.remove(&(*code, *platform));
            }
            Some(key)
        } else {
            None
        }
    }

    /// Parse a custom key by name
    pub fn parse_by_name(&self, name: &str) -> Option<&CustomKey> {
        self.name_to_key.get(name)
    }

    /// Parse a custom key by code and platform
    pub fn parse_by_code(&self, code: usize, platform: Platform) -> Option<&CustomKey> {
        self.code_to_key.get(&(code, platform))
    }

    /// Convert a custom key to a platform-specific code
    pub fn to_code(&self, key: &CustomKey, platform: Platform) -> Option<usize> {
        key.code(platform)
    }

    /// Get all custom keys
    pub fn keys(&self) -> impl Iterator<Item = &CustomKey> {
        self.name_to_key.values()
    }

    /// Check if a custom key with the given name exists
    pub fn contains_key(&self, name: &str) -> bool {
        self.name_to_key.contains_key(name)
    }

    /// Get the number of custom keys
    pub fn len(&self) -> usize {
        self.name_to_key.len()
    }

    /// Check if there are no custom keys
    pub fn is_empty(&self) -> bool {
        self.name_to_key.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_custom_key_creation() {
        let mut key = CustomKey::new("Macro1");
        key.add_platform_code(Platform::Windows, 0x100)
            .add_platform_code(Platform::Linux, 200);

        assert_eq!(key.name(), "Macro1");
        assert_eq!(key.code(Platform::Windows), Some(0x100));
        assert_eq!(key.code(Platform::Linux), Some(200));
        assert_eq!(key.code(Platform::MacOS), None);
    }

    #[test]
    fn test_custom_key_hash() {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut key1 = CustomKey::new("Test");
        key1.add_platform_code(Platform::Windows, 100);

        let mut key2 = CustomKey::new("Test");
        key2.add_platform_code(Platform::Linux, 200); // 不同的 codes,但相同的 name

        let mut hasher1 = DefaultHasher::new();
        let mut hasher2 = DefaultHasher::new();

        key1.hash(&mut hasher1);
        key2.hash(&mut hasher2);

        assert_eq!(hasher1.finish(), hasher2.finish()); // 应该相等,因为只基于 name
    }

    #[test]
    fn test_custom_key_map() {
        let mut custom_map = CustomKeyMap::new();

        // Create and add a custom key
        let mut custom_key = CustomKey::new("Macro1");
        custom_key
            .add_platform_code(Platform::Windows, 0x100)
            .add_platform_code(Platform::Linux, 200);

        assert!(custom_map.add_key(custom_key).is_ok());

        // Test parsing by name
        assert_eq!(custom_map.parse_by_name("Macro1").unwrap().name(), "Macro1");

        // Test parsing by code
        assert_eq!(
            custom_map
                .parse_by_code(0x100, Platform::Windows)
                .unwrap()
                .name(),
            "Macro1"
        );
        assert_eq!(
            custom_map
                .parse_by_code(200, Platform::Linux)
                .unwrap()
                .name(),
            "Macro1"
        );

        // Test code conversion
        let key = custom_map.parse_by_name("Macro1").unwrap();
        assert_eq!(custom_map.to_code(key, Platform::Linux), Some(200));

        // Test duplicate key error
        let duplicate_key = CustomKey::new("Macro1");
        assert!(custom_map.add_key(duplicate_key).is_err());
    }

    #[test]
    fn test_custom_key_map_removal() {
        let mut custom_map = CustomKeyMap::new();

        let mut key = CustomKey::new("Macro1");
        key.add_platform_code(Platform::Windows, 0x100);
        custom_map.add_key(key).unwrap();

        assert!(custom_map.contains_key("Macro1"));
        assert_eq!(custom_map.len(), 1);

        let removed = custom_map.remove_key("Macro1");
        assert!(removed.is_some());
        assert!(!custom_map.contains_key("Macro1"));
        assert!(custom_map.is_empty());
    }
}