mouse_codes/mapping/
custom.rs

1//! Custom mouse button mapping support
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::fmt;
6
7use crate::{
8    error::MouseParseError,
9    types::{Button, CodeMapper, Platform},
10};
11
12/// A custom mouse button that extends the standard button set
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum CustomButton {
16    /// A standard mouse button
17    Standard(Button),
18    /// A custom-defined mouse button with a name
19    Custom(Cow<'static, str>),
20}
21
22impl CustomButton {
23    /// Create a custom button from a static string
24    pub fn custom_static(name: &'static str) -> Self {
25        CustomButton::Custom(Cow::Borrowed(name))
26    }
27
28    /// Create a custom button from a string
29    pub fn custom_string(name: String) -> Self {
30        CustomButton::Custom(Cow::Owned(name))
31    }
32}
33
34impl fmt::Display for CustomButton {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            CustomButton::Standard(btn) => write!(f, "{}", btn),
38            CustomButton::Custom(name) => write!(f, "{}", name),
39        }
40    }
41}
42
43/// A mapping for custom mouse buttons with platform-specific codes
44#[derive(Debug, Clone)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct CustomButtonMap {
47    name: String,
48    mappings: HashMap<CustomButton, [Option<usize>; 3]>, // [Windows, Linux, macOS]
49    reverse_mappings: [HashMap<usize, CustomButton>; 3], // [Windows, Linux, macOS]
50}
51
52impl CustomButtonMap {
53    /// Create a new empty custom button map
54    pub fn new(name: &str) -> Self {
55        Self {
56            name: name.to_string(),
57            mappings: HashMap::new(),
58            reverse_mappings: [HashMap::new(), HashMap::new(), HashMap::new()],
59        }
60    }
61
62    /// Add a custom button with platform-specific codes
63    pub fn add_button(
64        &mut self,
65        button: CustomButton,
66        windows_code: Option<usize>,
67        linux_code: Option<usize>,
68        macos_code: Option<usize>,
69    ) -> Result<(), MouseParseError> {
70        if self.mappings.contains_key(&button) {
71            return Err(MouseParseError::DuplicateCustomButton(button.to_string()));
72        }
73
74        // 先克隆按钮用于反向映射
75        let button_clone = button.clone();
76
77        // Store forward mapping
78        self.mappings
79            .insert(button, [windows_code, linux_code, macos_code]);
80
81        // Store reverse mappings for each platform
82        if let Some(code) = windows_code {
83            self.reverse_mappings[0].insert(code, button_clone.clone());
84        }
85        if let Some(code) = linux_code {
86            self.reverse_mappings[1].insert(code, button_clone.clone());
87        }
88        if let Some(code) = macos_code {
89            self.reverse_mappings[2].insert(code, button_clone);
90        }
91
92        Ok(())
93    }
94
95    /// Get the name of this custom map
96    pub fn name(&self) -> &str {
97        &self.name
98    }
99}
100
101// 明确使用 CodeMapper trait 的方法
102impl CodeMapper for CustomButton {
103    fn to_code(&self, platform: Platform) -> usize {
104        match self {
105            CustomButton::Standard(btn) => <Button as CodeMapper>::to_code(btn, platform),
106            CustomButton::Custom(_) => {
107                panic!("Custom buttons require a CustomButtonMap for conversion")
108            }
109        }
110    }
111
112    fn from_code(code: usize, platform: Platform) -> Option<Self> {
113        <Button as CodeMapper>::from_code(code, platform).map(CustomButton::Standard)
114    }
115}
116
117impl CustomButtonMap {
118    /// Get the platform-specific code for a custom button
119    pub fn get_code_for_button(&self, button: &CustomButton, platform: Platform) -> Option<usize> {
120        match button {
121            CustomButton::Standard(btn) => Some(<Button as CodeMapper>::to_code(btn, platform)),
122            CustomButton::Custom(_) => {
123                let idx = match platform {
124                    Platform::Windows => 0,
125                    Platform::Linux => 1,
126                    Platform::MacOS => 2,
127                };
128                self.mappings.get(button).and_then(|codes| codes[idx])
129            }
130        }
131    }
132
133    /// Get button from platform-specific code using custom mappings
134    pub fn from_code(&self, code: usize, platform: Platform) -> Option<CustomButton> {
135        let idx = match platform {
136            Platform::Windows => 0,
137            Platform::Linux => 1,
138            Platform::MacOS => 2,
139        };
140
141        // Check custom mappings first, then fall back to standard buttons
142        self.reverse_mappings[idx].get(&code).cloned().or_else(|| {
143            <Button as CodeMapper>::from_code(code, platform).map(CustomButton::Standard)
144        })
145    }
146}