forked from Amulet-Team/Amulet-Map-Editor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbutton_input.py
More file actions
259 lines (201 loc) · 9.38 KB
/
button_input.py
File metadata and controls
259 lines (201 loc) · 9.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import wx
from typing import Set, Dict, Tuple
from .window_container import WindowContainer
from .key_config import KeyType, serialise_key, KeybindGroup
ActionIDType = str
_InputPressEventType = wx.NewEventType()
EVT_INPUT_PRESS = wx.PyEventBinder(_InputPressEventType)
class InputPressEvent(wx.PyEvent):
"""An action run by a button being pressed."""
def __init__(self, action_id: ActionIDType):
wx.PyEvent.__init__(self, eventType=_InputPressEventType)
self._action_id = action_id
@property
def action_id(self) -> ActionIDType:
"""The string id of the action triggered by the keypress"""
return self._action_id
_InputReleaseEventType = wx.NewEventType()
EVT_INPUT_RELEASE = wx.PyEventBinder(_InputReleaseEventType)
class InputReleaseEvent(wx.PyEvent):
"""An action run by a button being released."""
def __init__(self, action_id: ActionIDType):
wx.PyEvent.__init__(self, eventType=_InputReleaseEventType)
self._action_id = action_id
@property
def action_id(self) -> ActionIDType:
"""The string id of the action triggered by the key release"""
return self._action_id
_InputHeldEventType = wx.NewEventType()
EVT_INPUT_HELD = wx.PyEventBinder(_InputHeldEventType)
class InputHeldEvent(wx.PyEvent):
"""An action run by a button being held.
This action will run frequently."""
def __init__(self, action_ids: Set[ActionIDType]):
wx.PyEvent.__init__(self, eventType=_InputHeldEventType)
self._action_ids = action_ids
@property
def action_ids(self) -> Set[ActionIDType]:
"""The string ids of the actions that have been held."""
return self._action_ids
class Action:
"""A private class for helping with key management for an action."""
def __init__(self, trigger_key: KeyType, modifier_keys: Tuple[KeyType, ...]):
self._required_keys = set(modifier_keys + (trigger_key,))
self._trigger_key = trigger_key
self._modifier_keys = set(modifier_keys)
@property
def required_keys(self) -> Set[KeyType]:
"""The keys that need to remain pressed for this action to stay active."""
return self._required_keys
@property
def trigger_key(self) -> KeyType:
"""The key that needs pressing for this action to start."""
return self._trigger_key
@property
def modifier_keys(self) -> Set[KeyType]:
"""Additional keys that must already be pressed for this action to start."""
return self._modifier_keys
class ButtonInput(WindowContainer):
"""A class to detect and store user inputs."""
def __init__(self, window: wx.Window):
super().__init__(window)
self._registered_actions: Dict[ActionIDType, Action] = {}
self._pressed_keys: Set[KeyType] = set()
# The actions that have been started but not stopped.
self._continuous_actions: Set[ActionIDType] = set()
# timer to deal with persistent actions
self._input_timer = wx.Timer(self.window)
def bind_events(self):
"""Set up all events required to run."""
# key press actions
self.window.Bind(wx.EVT_LEFT_DOWN, self._press)
self.window.Bind(wx.EVT_LEFT_UP, self._release)
self.window.Bind(wx.EVT_MIDDLE_DOWN, self._press)
self.window.Bind(wx.EVT_MIDDLE_UP, self._release)
self.window.Bind(wx.EVT_RIGHT_DOWN, self._press)
self.window.Bind(wx.EVT_RIGHT_UP, self._release)
self.window.Bind(wx.EVT_KEY_DOWN, self._press)
self.window.Bind(wx.EVT_KEY_UP, self._release)
self.window.Bind(wx.EVT_NAVIGATION_KEY, self._release)
self.window.Bind(wx.EVT_NAVIGATION_KEY, self._press)
self.window.Bind(wx.EVT_MOUSEWHEEL, self._release)
self.window.Bind(wx.EVT_MOUSEWHEEL, self._press)
self.window.Bind(wx.EVT_MOUSE_AUX1_DOWN, self._press)
self.window.Bind(wx.EVT_MOUSE_AUX1_UP, self._release)
self.window.Bind(wx.EVT_MOUSE_AUX2_DOWN, self._press)
self.window.Bind(wx.EVT_MOUSE_AUX2_UP, self._release)
self.window.Bind(
wx.EVT_TIMER, self._process_continuous_inputs, self._input_timer
)
# save destruction
self.window.Bind(wx.EVT_WINDOW_DESTROY, self._on_destroy, self.window)
def enable(self):
self._input_timer.Start(33)
def disable(self):
self._input_timer.Stop()
def _on_destroy(self, evt):
self.disable()
evt.Skip()
@property
def pressed_keys(self) -> Set[KeyType]:
"""A tuple of all the keys that are currently pressed."""
return self._pressed_keys.copy()
@property
def pressed_actions(self) -> Set[ActionIDType]:
return self._continuous_actions.copy()
def is_key_pressed(self, key: KeyType):
"""Is the requested key currently in the pressed state."""
return key in self._pressed_keys
def unpress_all(self):
"""Unpress all keys.
This is useful if the window focus is lost because key release events will not be detected.
"""
self._pressed_keys.clear()
self._clean_up_actions()
def clear_registered_actions(self):
"""Clear the previously registered actions so that they can be repopulated."""
self._registered_actions.clear()
self._continuous_actions.clear()
def action_id_registered(self, action_id: ActionIDType):
"""Has the action id already been registered."""
return action_id in self._registered_actions
def register_action(
self,
action_id: ActionIDType,
modifier_keys: Tuple[KeyType, ...],
trigger_key: KeyType,
):
"""Register a new action for the given trigger key and optional modifier keys.
This action will be fired when the trigger key is pressed providing all the modifier keys are already pressed.
:param action_id: The unique action id. Will raise a ValueError if it is already taken.
:param modifier_keys: Other keys that need to be pressed for the action to happen.
:param trigger_key: The key that when pressed will start the action provided the modifier keys are pressed.
:return:
"""
if not isinstance(action_id, str):
raise TypeError("action_id must be a string.")
if (
not isinstance(trigger_key, (str, int))
or not isinstance(modifier_keys, tuple)
or not all(isinstance(k, (str, int)) for k in modifier_keys)
):
raise TypeError(
"The key inputs are not of the correct format. Expected Union[str, int], Tuple[Union[str, int], ...]"
)
if self.action_id_registered(action_id):
raise ValueError(f"{action_id} has already been registered.")
self._registered_actions[action_id] = Action(trigger_key, modifier_keys)
def register_actions(self, actions: KeybindGroup):
for action_id, (modifier_keys, trigger_key) in actions.items():
self.register_action(action_id, modifier_keys, trigger_key)
def _find_actions(self, key: KeyType) -> Tuple[ActionIDType, ...]:
"""A method to find all actions triggered by `key` with the modifier keys also pressed."""
return tuple(
action_id
for action_id, action in self._registered_actions.items()
if action.trigger_key == key
and action.modifier_keys.issubset(self._pressed_keys)
)
def _press(self, evt):
"""Event to handle a number of different key presses"""
key = serialise_key(evt)
if key is None:
return
if not self.is_key_pressed(key):
action_ids = self._find_actions(key)
self._continuous_actions.update(action_ids)
for action_id in action_ids:
wx.PostEvent(self.window, InputPressEvent(action_id))
self._pressed_keys.add(key)
evt.Skip()
def _release(self, evt):
"""Event to handle a number of different key releases"""
key = serialise_key(evt)
if key is None:
return
if self.is_key_pressed(key):
# remove the pressed key
self._pressed_keys.remove(key)
self._clean_up_actions()
evt.Skip()
def _clean_up_actions(self):
# find all actions that are now not valid and remove them
for action_id in list(self._continuous_actions):
if not self._registered_actions[action_id].required_keys.issubset(
self._pressed_keys
):
self._continuous_actions.remove(action_id)
wx.PostEvent(self.window, InputReleaseEvent(action_id))
def _process_continuous_inputs(self, evt):
wx.PostEvent(self.window, InputHeldEvent(self._continuous_actions.copy()))
# Programmatic control for virtual/touch inputs
def press_action(self, action_id: ActionIDType):
"""Programmatically start an action as if its key was pressed."""
if action_id in self._registered_actions and action_id not in self._continuous_actions:
self._continuous_actions.add(action_id)
wx.PostEvent(self.window, InputPressEvent(action_id))
def release_action(self, action_id: ActionIDType):
"""Programmatically stop an action as if its key was released."""
if action_id in self._continuous_actions:
self._continuous_actions.remove(action_id)
wx.PostEvent(self.window, InputReleaseEvent(action_id))