1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2005, Sven2
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// Keyboard input mapping to engine functions
19
20#pragma once
21
22#include "C4ForwardDeclarations.h"
23#include "Standard.h"
24#include "StdBuf.h"
25
26#include <cassert>
27#include <map>
28#include <vector>
29
30// key context classifications
31enum C4KeyScope
32{
33 KEYSCOPE_None = 0,
34 KEYSCOPE_Control = 1, // player control (e.g. NUM1 to move left on keypad control)
35 KEYSCOPE_Gui = 2, // keys used to manipulate GUI elements (e.g. Tab to cycle through controls)
36 KEYSCOPE_Fullscreen = 4, // generic fullscreen-only keys (e.g. F9 for screenshot)
37 KEYSCOPE_Console = 8, // generic console-mode only keys (e.g. Space to switch edit cursor)
38 KEYSCOPE_Generic = 16, // generic keys available in fullscreen and console mode outside GUI (e.g. F1 for music on/off)
39 KEYSCOPE_FullSMenu = 32, // fullscreen menu control. If fullscreen menu is active, this disables viewport controls (e.g. Return to close player join menu)
40 KEYSCOPE_FilmView = 64, // ownerless viewport scrolling in film mode, player switching, etc. (e.g. Enter to switch to next player)
41 KEYSCOPE_FreeView = 128, // ownerless viewport scrolling, player switching, etc. (e.g. arrow left to scroll left in view)
42 KEYSCOPE_FullSView = 256, // player fullscreen viewport
43};
44
45// what can happen to keys
46enum C4KeyEventType
47{
48 KEYEV_None = 0, // no event
49 KEYEV_Down = 1, // in response to WM_KEYDOWN or joypad button pressed
50 KEYEV_Up = 2, // in response to WM_KEYUP or joypad button released
51 KEYEV_Pressed = 3, // in response to WM_KEYPRESSED
52};
53
54// keyboard code
55typedef unsigned long C4KeyCode;
56
57const C4KeyCode KEY_Default = 0, // no key
58 KEY_Any = ~0, // used for default key processing
59 KEY_Undefined = (~0) ^ 1, // used to indicate an unknown key
60 KEY_JOY_Left = 1, // joypad axis control: Any x axis min
61 KEY_JOY_Up = 2, // joypad axis control: Any y axis min
62 KEY_JOY_Right = 3, // joypad axis control: Any x axis max
63 KEY_JOY_Down = 4, // joypad axis control: Any y axis max
64 KEY_JOY_Button1 = 10, // key index of joypad buttons + button index for more buttons
65 KEY_JOY_ButtonMax = KEY_JOY_Button1 + 31, // maximum number of supported buttons on a gamepad
66 KEY_JOY_Axis1Min = 0x30,
67 KEY_JOY_AxisMax = KEY_JOY_Axis1Min + 0x20,
68 KEY_JOY_AnyButton = 0xff, // any joypad button (not axis)
69 KEY_JOY_AnyOddButton = 0xfe, // joypad buttons 1, 3, 5, etc.
70 KEY_JOY_AnyEvenButton = 0xfd, // joypad buttons 2, 4, 6, etc.
71 KEY_JOY_AnyLowButton = 0xfc, // joypad buttons 1 - 4
72 KEY_JOY_AnyHighButton = 0xfb; // joypad buttons > 4
73
74inline uint8_t KEY_JOY_Button(uint8_t idx) { return KEY_JOY_Button1 + idx; }
75inline uint8_t KEY_JOY_Axis(uint8_t idx, bool fMax) { return KEY_JOY_Axis1Min + 2 * idx + fMax; }
76
77inline C4KeyCode KEY_Gamepad(uint8_t idGamepad, uint8_t idButton) // convert gamepad key to Clonk-gamepad-keycode
78{
79 // mask key as 0x0042ggbb, where gg is gamepad ID and bb is button ID.
80 return 0x00420000 + (idGamepad << 8) + idButton;
81}
82
83inline bool Key_IsGamepad(C4KeyCode key)
84{
85 return (0xff0000 & key) == 0x420000;
86}
87
88inline uint8_t Key_GetGamepad(C4KeyCode key)
89{
90 return (static_cast<uint32_t>(key) >> 8) & 0xff;
91}
92
93inline uint8_t Key_GetGamepadButton(C4KeyCode key)
94{
95 return static_cast<uint32_t>(key) & 0xff;
96}
97
98inline bool Key_IsGamepadButton(C4KeyCode key)
99{
100 // whether this is a unique button event (AnyButton not included)
101 return Key_IsGamepad(key) && Inside<uint8_t>(ival: Key_GetGamepadButton(key), lbound: KEY_JOY_Button1, rbound: KEY_JOY_ButtonMax);
102}
103
104inline bool Key_IsGamepadAxis(C4KeyCode key)
105{
106 // whether this is a unique button event (AnyButton not included)
107 return Key_IsGamepad(key) && Inside<uint8_t>(ival: Key_GetGamepadButton(key), lbound: KEY_JOY_Axis1Min, rbound: KEY_JOY_AxisMax);
108}
109
110inline uint8_t Key_GetGamepadButtonIndex(C4KeyCode key)
111{
112 // get zero-based button index
113 return Key_GetGamepadButton(key) - KEY_JOY_Button1;
114}
115
116inline uint8_t Key_GetGamepadAxisIndex(C4KeyCode key)
117{
118 // get zero-based axis index
119 return (Key_GetGamepadButton(key) - KEY_JOY_Axis1Min) / 2;
120}
121
122inline bool Key_IsGamepadAxisHigh(C4KeyCode key)
123{
124 return !!(key & 1);
125}
126
127#ifdef _WIN32
128#define TOUPPERIFX11(key) (key)
129#else
130#define TOUPPERIFX11(key) toupper(key)
131#endif
132
133enum C4KeyShiftState
134{
135 KEYS_None = 0,
136 KEYS_First = 1,
137 KEYS_Alt = 1,
138 KEYS_Control = 2,
139 KEYS_Shift = 4,
140 KEYS_Max = KEYS_Shift,
141 KEYS_Undefined = 0xffff,
142};
143
144// extended key information containing shift state
145struct C4KeyCodeEx
146{
147 C4KeyCode Key; // the key
148 uint32_t dwShift; // the status of Alt, Shift, Control
149
150 // if set, the keycode was generated by a key that has been held down
151 // this flag is ignored in comparison operations
152 bool fRepeated;
153
154 // helpers
155 static C4KeyShiftState String2KeyShift(const StdStrBuf &sName);
156 static C4KeyCode String2KeyCode(const StdStrBuf &sName);
157 static std::string KeyCode2String(C4KeyCode wCode, bool fHumanReadable, bool fShort);
158 std::string ToString(bool fHumanReadable, bool fShort);
159 static std::string KeyShift2String(C4KeyShiftState eShift);
160
161 // comparison operator for map access
162 inline bool operator<(const C4KeyCodeEx &v2) const
163 {
164 return Key < v2.Key || (Key == v2.Key && dwShift < v2.dwShift);
165 }
166
167 inline bool operator==(const C4KeyCodeEx &v2) const
168 {
169 return Key == v2.Key && dwShift == v2.dwShift;
170 }
171
172 void CompileFunc(StdCompiler *pComp);
173
174 C4KeyCodeEx(C4KeyCode Key = KEY_Default, C4KeyShiftState Shift = KEYS_None, bool fIsRepeated = false)
175 : Key(Key), dwShift(Shift), fRepeated(fIsRepeated) {}
176
177 bool IsRepeated() { return fRepeated; }
178};
179
180// callback interface
181class C4KeyboardCallbackInterface
182{
183private:
184 int iRef;
185
186public:
187 class C4CustomKey *pOriginalKey;
188
189public:
190 virtual bool OnKeyEvent(C4KeyCodeEx key, C4KeyEventType eEv) = 0; // return true if processed
191
192 // reference counter
193 inline void Ref() { ++iRef; }
194 inline void Deref() { if (!--iRef) delete this; }
195
196 C4KeyboardCallbackInterface() : iRef(0), pOriginalKey(nullptr) {}
197 virtual ~C4KeyboardCallbackInterface() {}
198
199 bool IsOriginalKey(const class C4CustomKey *pCheckKey) const { return pCheckKey == pOriginalKey; }
200};
201
202// callback interface
203template <class TargetClass> class C4KeyCB : public C4KeyboardCallbackInterface
204{
205public:
206 typedef bool(TargetClass::*CallbackFunc)();
207
208protected:
209 TargetClass &rTarget;
210 CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
211
212protected:
213 virtual bool OnKeyEvent(C4KeyCodeEx key, C4KeyEventType eEv) override
214 {
215 if (!CheckCondition()) return false;
216 switch (eEv)
217 {
218 case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)() : false;
219 case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)() : false;
220 case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)() : false;
221 case KEYEV_None: assert(!"KeyEvent of type KEYEV_None");
222 }
223 return false;
224 }
225
226 virtual bool CheckCondition() { return true; }
227
228public:
229 C4KeyCB(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp = nullptr, CallbackFunc pFuncPressed = nullptr)
230 : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed) {}
231};
232
233// callback interface that passes the pressed key as a parameter
234template <class TargetClass> class C4KeyCBPassKey : public C4KeyboardCallbackInterface
235{
236public:
237 typedef bool(TargetClass::*CallbackFunc)(C4KeyCodeEx key);
238
239protected:
240 TargetClass &rTarget;
241 CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
242
243protected:
244 virtual bool OnKeyEvent(C4KeyCodeEx key, C4KeyEventType eEv) override
245 {
246 if (!CheckCondition()) return false;
247 switch (eEv)
248 {
249 case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(key) : false;
250 case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(key) : false;
251 case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(key) : false;
252 case KEYEV_None: assert(!"KeyEvent of type KEYEV_None");
253 }
254 return false;
255 }
256
257 virtual bool CheckCondition() { return true; }
258
259public:
260 C4KeyCBPassKey(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp = nullptr, CallbackFunc pFuncPressed = nullptr)
261 : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed) {}
262};
263
264// parameterized callback interface
265template <class TargetClass, class ParameterType> class C4KeyCBEx : public C4KeyboardCallbackInterface
266{
267public:
268 typedef bool(TargetClass::*CallbackFunc)(ParameterType par);
269
270protected:
271 TargetClass &rTarget;
272 CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
273 ParameterType par;
274
275protected:
276 virtual bool OnKeyEvent(C4KeyCodeEx key, C4KeyEventType eEv) override
277 {
278 if (!CheckCondition()) return false;
279 switch (eEv)
280 {
281 case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(par) : false;
282 case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(par) : false;
283 case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(par) : false;
284 case KEYEV_None: assert(!"KeyEvent of type KEYEV_None");
285 }
286 return false;
287 }
288
289 virtual bool CheckCondition() { return true; }
290
291public:
292 C4KeyCBEx(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp = nullptr, CallbackFunc pFuncPressed = nullptr)
293 : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), par(par) {}
294};
295
296template <class TargetClass, class ParameterType> class C4KeyCBExPassKey : public C4KeyboardCallbackInterface
297{
298public:
299 typedef bool(TargetClass::*CallbackFunc)(C4KeyCodeEx key, ParameterType par);
300
301protected:
302 TargetClass &rTarget;
303 CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
304 ParameterType par;
305
306protected:
307 virtual bool OnKeyEvent(C4KeyCodeEx key, C4KeyEventType eEv) override
308 {
309 if (!CheckCondition()) return false;
310 switch (eEv)
311 {
312 case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(key, par) : false;
313 case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(key, par) : false;
314 case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(key, par) : false;
315 case KEYEV_None: assert(!"KeyEvent of type KEYEV_None");
316 }
317 return false;
318 }
319
320 virtual bool CheckCondition() { return true; }
321
322public:
323 C4KeyCBExPassKey(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp = nullptr, CallbackFunc pFuncPressed = nullptr)
324 : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), par(par) {}
325};
326
327// one mapped keyboard entry
328class C4CustomKey
329{
330public:
331 typedef std::vector<C4KeyCodeEx> CodeList;
332
333private:
334 CodeList Codes, DefaultCodes; // keyboard scancodes of OS plus shift state
335 C4KeyScope Scope; // scope in which key is processed
336 StdStrBuf Name; // custom key name; used for association in config files
337 typedef std::vector<C4KeyboardCallbackInterface *> CBVec;
338 unsigned int uiPriority; // key priority: If multiple keys of same code are defined, high prio overwrites low prio keys
339
340public:
341 CBVec vecCallbacks; // a list of all callbacks assigned to that key
342
343 enum Priority
344 {
345 PRIO_None = 0u,
346 PRIO_Base = 1u,
347 PRIO_Dlg = 2u,
348 PRIO_Ctrl = 3u, // controls have higher priority than dialogs in GUI
349 PRIO_CtrlOverride = 4u, // dialog handlings of keys that overwrite regular control handlings
350 PRIO_FocusCtrl = 5u, // controls override special dialog handling keys (e.g., RenameEdit)
351 PRIO_Context = 6u, // context menus above controls
352 PRIO_PlrControl = 7u, // player controls overwrite any other controls
353 PRIO_MoreThanMax = 100u, // must be larger than otherwise largest used priority
354 };
355
356protected:
357 int iRef;
358
359public:
360 C4CustomKey(C4KeyCodeEx DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority = PRIO_Base); // ctor for default key
361 C4CustomKey(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority = PRIO_Base); // ctor for default key with multiple possible keys assigned
362 C4CustomKey(const C4CustomKey &rCpy, bool fCopyCallbacks);
363 virtual ~C4CustomKey();
364
365 inline void Ref() { ++iRef; }
366 inline void Deref() { if (!--iRef) delete this; }
367
368 const CodeList &GetCodes() const { return Codes.size() ? Codes : DefaultCodes; } // return assigned codes; default if no custom has been assigned
369 const StdStrBuf &GetName() const { return Name; }
370 C4KeyScope GetScope() const { return Scope; }
371 unsigned int GetPriority() const { return uiPriority; }
372
373 void Update(const C4CustomKey *pByKey); // merge given key into this
374 bool Execute(C4KeyEventType eEv, C4KeyCodeEx key);
375
376 void KillCallbacks(const C4CustomKey *pOfKey); // remove any callbacks that were created by given key
377
378 void CompileFunc(StdCompiler *pComp);
379};
380
381// a key that auto-registers itself into main game keyboard input class and does dereg when deleted
382class C4KeyBinding : protected C4CustomKey
383{
384public:
385 C4KeyBinding(C4KeyCodeEx DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority = PRIO_Base); // ctor for default key
386 C4KeyBinding(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority = PRIO_Base); // ctor for default key
387 ~C4KeyBinding();
388};
389
390// main keyboard mapping class
391class C4KeyboardInput
392{
393private:
394 // comparison fn for map
395 struct szLess
396 {
397 bool operator()(const char *p, const char *q) const { return p && q && (strcmp(s1: p, s2: q) < 0); }
398 };
399
400 typedef std::multimap<C4KeyCodeEx, C4CustomKey *> KeyCodeMap;
401 typedef std::map<const char *, C4CustomKey *, szLess> KeyNameMap;
402 // mapping of all keys by code and name
403 KeyCodeMap KeysByCode;
404 KeyNameMap KeysByName;
405
406public:
407 static bool IsValid; // global var to fix any deinitialization orders of key map and static keys
408
409 C4KeyboardInput() { IsValid = true; }
410 ~C4KeyboardInput() { Clear(); IsValid = false; }
411
412 void Clear(); // empty keyboard maps
413
414private:
415 // assign keycodes changed for a key: Update codemap
416 void UpdateKeyCodes(C4CustomKey *pKey, const C4CustomKey::CodeList &rOldCodes, const C4CustomKey::CodeList &rNewCodes);
417
418public:
419 void RegisterKey(C4CustomKey *pRegKey); // register key into code and name maps, or update specific key
420 void UnregisterKey(const StdStrBuf &rsName); // remove key from all maps
421 void UnregisterKeyBinding(C4CustomKey *pKey); // just remove callbacks from a key
422
423 bool DoInput(const C4KeyCodeEx &InKey, C4KeyEventType InEvent, uint32_t InScope);
424
425 void CompileFunc(StdCompiler *pComp);
426 bool LoadCustomConfig(); // load keyboard customization file
427
428 C4CustomKey *GetKeyByName(const char *szKeyName);
429 std::string GetKeyCodeNameByKeyName(const char *szKeyName, bool fShort = false, int32_t iIndex = 0);
430};
431
432// keyboardinput-initializer-helper
433C4KeyboardInput &C4KeyboardInput_Init();
434