forked from bl-sdk/mods_base
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeybinds.py
244 lines (199 loc) · 8.11 KB
/
keybinds.py
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
from __future__ import annotations
from collections.abc import Callable
from dataclasses import KW_ONLY, dataclass, field
from typing import TYPE_CHECKING, Any, overload
from unrealsdk import logging
from unrealsdk.hooks import Block
if TYPE_CHECKING:
from enum import auto
from unrealsdk.unreal._uenum import UnrealEnum # pyright: ignore[reportMissingModuleSource]
class EInputEvent(UnrealEnum):
IE_Pressed = auto()
IE_Released = auto()
IE_Repeat = auto()
IE_DoubleClick = auto()
IE_Axis = auto()
IE_MAX = auto()
else:
from unrealsdk import find_enum
EInputEvent = find_enum("EInputEvent")
type KeybindBlockSignal = None | Block | type[Block]
type KeybindCallback_Event = Callable[[EInputEvent], KeybindBlockSignal]
type KeybindCallback_NoArgs = Callable[[], KeybindBlockSignal]
@dataclass
class KeybindType:
"""
Represents a single keybind.
The input callback takes no args, and may return the Block sentinel to try prevent passing the
input back into the game. Note that this depends on the keybinds implementation, implementations
may not necessarily implement blocking. If it is implemented, standard blocking logic applies
when multiple keybinds use the same key.
Args:
identifier: The keybind's identifier.
key: The bound key, or None if unbound. Updated on rebind.
callback: The callback to run when the key is pressed.
Keyword Args:
display_name: The keybind name to use for display. Defaults to copying the identifier.
description: A short description about the bind.
description_title: The title of the description. Defaults to copying the display name.
is_hidden: If true, the keybind will not be shown in the options menu.
is_rebindable: If the key may be rebound.
event_filter: If not None, only runs the callback when the given event fires.
Extra Attributes:
is_enabled: True if this keybind has been enabled.
default_key: What the key was originally when registered. Does not update on rebind.
"""
identifier: str
key: str | None
# Putting this up here for a more convenient repr
is_enabled: bool = field(init=False, default=False)
# If `event_filter` is None, `callback` should be `KeybindCallback_Event | None`
# If `event_filter` is not None, `callback` should be `KeybindCallback_NoArgs | None`
# The decorator uses overloads to enforce this
callback: KeybindCallback_Event | KeybindCallback_NoArgs | None = None
_: KW_ONLY
display_name: str = None # type: ignore
description: str = ""
description_title: str = None # type: ignore
is_hidden: bool = False
is_rebindable: bool = True
event_filter: EInputEvent | None = EInputEvent.IE_Released
default_key: str | None = field(init=False)
def __post_init__(self) -> None:
if self.display_name is None: # type: ignore
self.display_name = self.identifier
if self.description_title is None: # type: ignore
self.description_title = self.display_name
self.default_key = self.key
def __setattr__(self, name: str, value: Any) -> None:
# Simpler to use `__setattr__` than a property to detect key changes
if name == "key" and not hasattr(self, "_rebind_recursion_guard"):
self._rebind_recursion_guard = True
self._rebind(value)
del self._rebind_recursion_guard
super().__setattr__(name, value)
def enable(self) -> None:
"""Enables this keybind."""
self._enable()
self.is_enabled = True
def disable(self) -> None:
"""Disables this keybind."""
self._disable()
self.is_enabled = False
# These three functions should get replaced by the keybind implementation
# The initialization script should make sure to load it before any mods, to make sure they don't
# end up with references to these functions
def _enable(self) -> None:
"""Enables this keybind."""
logging.error("No keybind implementation loaded, unable to enable binds")
def _disable(self) -> None:
"""Disables this keybind."""
logging.error("No keybind implementation loaded, unable to disable binds")
def _rebind(self, new_key: str | None) -> None:
"""
Called whenever this keybind is rebound, before the `key` attribute is updated.
May be used by the keybind implementation to rebind keys if needed.
Args:
new_key: The new key this keybind is being rebound to.
"""
@overload
def keybind(
identifier: str,
key: str | None,
callback: KeybindCallback_NoArgs,
*,
display_name: str | None = None,
description: str = "",
description_title: str | None = None,
is_hidden: bool = False,
is_rebindable: bool = True,
event_filter: EInputEvent = EInputEvent.IE_Pressed,
) -> KeybindType: ...
@overload
def keybind(
identifier: str,
key: str | None = None,
callback: None = None,
*,
display_name: str | None = None,
description: str = "",
description_title: str | None = None,
is_hidden: bool = False,
is_rebindable: bool = True,
event_filter: EInputEvent = EInputEvent.IE_Pressed,
) -> Callable[[KeybindCallback_NoArgs], KeybindType]: ...
@overload
def keybind(
identifier: str,
key: str | None,
callback: KeybindCallback_Event,
*,
display_name: str | None = None,
description: str = "",
description_title: str | None = None,
is_hidden: bool = False,
is_rebindable: bool = True,
event_filter: None,
) -> KeybindType: ...
@overload
def keybind(
identifier: str,
key: str | None = None,
callback: None = None,
*,
display_name: str | None = None,
description: str = "",
description_title: str | None = None,
is_hidden: bool = False,
is_rebindable: bool = True,
event_filter: None,
) -> Callable[[KeybindCallback_Event], KeybindType]: ...
def keybind(
identifier: str,
key: str | None = None,
callback: KeybindCallback_NoArgs | KeybindCallback_Event | None = None,
*,
display_name: str | None = None,
description: str = "",
description_title: str | None = None,
is_hidden: bool = False,
is_rebindable: bool = True,
event_filter: EInputEvent | None = EInputEvent.IE_Pressed,
) -> (
Callable[[KeybindCallback_NoArgs], KeybindType]
| Callable[[KeybindCallback_Event], KeybindType]
| KeybindType
):
"""
Decorator factory to construct a keybind.
The input callback usually takes no args, and may return the Block sentinel to prevent passing
the input back into the game. Standard blocking logic applies when multiple keybinds use the
same key. If the event filter is set to None, such that the callback is fired for all events, it
is instead passed a single positional arg, the event which occurred.
Args:
identifier: The keybind's identifier.
key: The bound key, or None if unbound.
callback: The callback to run when the key is pressed.
Keyword Args:
display_name: The keybind name to use for display. Defaults to copying the identifier.
description: A short description about the bind.
description_title: The title of the description. Defaults to copying the display name.
is_hidden: If true, the keybind will not be shown in the options menu.
is_rebindable: If the key may be rebound.
event_filter: If not None, only runs the callback when the given event fires.
"""
def decorator(func: KeybindCallback_NoArgs | KeybindCallback_Event) -> KeybindType:
kwargs: dict[str, Any] = {
"description": description,
"is_hidden": is_hidden,
"is_rebindable": is_rebindable,
"event_filter": event_filter,
}
if display_name is not None:
kwargs["display_name"] = display_name
if description_title is not None:
kwargs["description_title"] = description_title
return KeybindType(identifier, key, func, **kwargs)
if callback is None:
return decorator
return decorator(callback)