Skip to main content

anvilkit_input/
action_map.rs

1//! # Action Mapping
2//!
3//! 将逻辑动作(如 "Jump", "MoveForward")映射到物理输入(按键/鼠标按钮),
4//! 实现输入重映射和多设备支持。
5
6use std::collections::HashMap;
7use bevy_ecs::prelude::*;
8
9use crate::input_state::{InputState, KeyCode, MouseButton};
10
11/// 输入绑定源
12///
13/// 一个逻辑动作可以绑定到键盘键或鼠标按钮。
14///
15/// # 示例
16///
17/// ```rust
18/// use anvilkit_input::action_map::InputBinding;
19/// use anvilkit_input::input_state::{KeyCode, MouseButton};
20///
21/// let key = InputBinding::Key(KeyCode::Space);
22/// let mouse = InputBinding::Mouse(MouseButton::Left);
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum InputBinding {
26    Key(KeyCode),
27    Mouse(MouseButton),
28}
29
30/// 动作状态
31///
32/// # 示例
33///
34/// ```rust
35/// use anvilkit_input::action_map::ActionState;
36///
37/// let state = ActionState::Pressed;
38/// assert!(state.is_active());
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum ActionState {
42    /// 未激活
43    Inactive,
44    /// 本帧刚按下
45    JustPressed,
46    /// 持续按下
47    Pressed,
48    /// 本帧刚松开
49    JustReleased,
50}
51
52impl ActionState {
53    /// 动作是否激活(按下或刚按下)
54    pub fn is_active(&self) -> bool {
55        matches!(self, ActionState::JustPressed | ActionState::Pressed)
56    }
57
58    /// 动作是否本帧刚触发
59    pub fn is_just_pressed(&self) -> bool {
60        matches!(self, ActionState::JustPressed)
61    }
62
63    /// 动作是否本帧刚结束
64    pub fn is_just_released(&self) -> bool {
65        matches!(self, ActionState::JustReleased)
66    }
67}
68
69/// 动作映射表
70///
71/// 将字符串命名的逻辑动作映射到一组输入绑定。
72/// 任一绑定激活即视为动作激活。
73///
74/// # 示例
75///
76/// ```rust
77/// use anvilkit_input::prelude::*;
78/// use anvilkit_input::action_map::InputBinding;
79///
80/// let mut map = ActionMap::new();
81/// map.add_binding("jump", InputBinding::Key(KeyCode::Space));
82/// map.add_binding("jump", InputBinding::Key(KeyCode::W));
83///
84/// let mut input = InputState::new();
85/// input.press_key(KeyCode::Space);
86///
87/// map.update(&input);
88/// assert!(map.is_action_active("jump"));
89/// assert!(map.is_action_just_pressed("jump"));
90/// ```
91#[derive(Resource)]
92pub struct ActionMap {
93    /// 动作名 → 绑定列表
94    bindings: HashMap<String, Vec<InputBinding>>,
95    /// 动作名 → 当前状态
96    states: HashMap<String, ActionState>,
97}
98
99impl ActionMap {
100    /// 创建空的动作映射表
101    pub fn new() -> Self {
102        Self {
103            bindings: HashMap::new(),
104            states: HashMap::new(),
105        }
106    }
107
108    /// 为动作添加输入绑定
109    pub fn add_binding(&mut self, action: &str, binding: InputBinding) {
110        self.bindings
111            .entry(action.to_string())
112            .or_default()
113            .push(binding);
114        self.states
115            .entry(action.to_string())
116            .or_insert(ActionState::Inactive);
117    }
118
119    /// 根据当前输入状态更新所有动作状态
120    pub fn update(&mut self, input: &InputState) {
121        for (action, bindings) in &self.bindings {
122            let any_active = bindings.iter().any(|b| match b {
123                InputBinding::Key(k) => input.is_key_pressed(*k),
124                InputBinding::Mouse(m) => input.is_mouse_pressed(*m),
125            });
126            let any_just_pressed = bindings.iter().any(|b| match b {
127                InputBinding::Key(k) => input.is_key_just_pressed(*k),
128                InputBinding::Mouse(m) => input.is_mouse_just_pressed(*m),
129            });
130            let any_just_released = bindings.iter().any(|b| match b {
131                InputBinding::Key(k) => input.is_key_just_released(*k),
132                InputBinding::Mouse(m) => input.is_mouse_just_released(*m),
133            });
134
135            let state = if any_just_pressed {
136                ActionState::JustPressed
137            } else if any_active {
138                ActionState::Pressed
139            } else if any_just_released {
140                ActionState::JustReleased
141            } else {
142                ActionState::Inactive
143            };
144
145            self.states.insert(action.clone(), state);
146        }
147    }
148
149    /// 查询动作状态
150    pub fn action_state(&self, action: &str) -> ActionState {
151        self.states.get(action).copied().unwrap_or(ActionState::Inactive)
152    }
153
154    /// 动作是否激活
155    pub fn is_action_active(&self, action: &str) -> bool {
156        self.action_state(action).is_active()
157    }
158
159    /// 动作是否本帧刚触发
160    pub fn is_action_just_pressed(&self, action: &str) -> bool {
161        self.action_state(action).is_just_pressed()
162    }
163
164    /// 动作是否本帧刚结束
165    pub fn is_action_just_released(&self, action: &str) -> bool {
166        self.action_state(action).is_just_released()
167    }
168
169    /// 获取动作的所有绑定
170    pub fn get_bindings(&self, action: &str) -> Option<&[InputBinding]> {
171        self.bindings.get(action).map(|v| v.as_slice())
172    }
173
174    /// 移除动作的所有绑定
175    pub fn clear_bindings(&mut self, action: &str) {
176        self.bindings.remove(action);
177        self.states.remove(action);
178    }
179}
180
181impl Default for ActionMap {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_action_state() {
193        assert!(ActionState::Pressed.is_active());
194        assert!(ActionState::JustPressed.is_active());
195        assert!(!ActionState::Inactive.is_active());
196        assert!(!ActionState::JustReleased.is_active());
197    }
198
199    #[test]
200    fn test_action_map_basic() {
201        let mut map = ActionMap::new();
202        map.add_binding("jump", InputBinding::Key(KeyCode::Space));
203
204        let mut input = InputState::new();
205        input.press_key(KeyCode::Space);
206
207        map.update(&input);
208        assert!(map.is_action_active("jump"));
209        assert!(map.is_action_just_pressed("jump"));
210
211        input.end_frame();
212        map.update(&input);
213        assert!(map.is_action_active("jump"));
214        assert!(!map.is_action_just_pressed("jump"));
215
216        input.release_key(KeyCode::Space);
217        map.update(&input);
218        assert!(!map.is_action_active("jump"));
219        assert!(map.is_action_just_released("jump"));
220    }
221
222    #[test]
223    fn test_multiple_bindings() {
224        let mut map = ActionMap::new();
225        map.add_binding("fire", InputBinding::Key(KeyCode::Space));
226        map.add_binding("fire", InputBinding::Mouse(MouseButton::Left));
227
228        let mut input = InputState::new();
229        input.press_mouse(MouseButton::Left);
230
231        map.update(&input);
232        assert!(map.is_action_active("fire"));
233    }
234
235    #[test]
236    fn test_unknown_action() {
237        let map = ActionMap::new();
238        assert_eq!(map.action_state("nonexistent"), ActionState::Inactive);
239        assert!(!map.is_action_active("nonexistent"));
240    }
241}