-
Notifications
You must be signed in to change notification settings - Fork 162
/
Copy pathsys_events.cpp
1070 lines (964 loc) · 36.2 KB
/
sys_events.cpp
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//=============================================================================
//
// Adventure Game Studio (AGS)
//
// Copyright (C) 1999-2011 Chris Jones and 2011-2024 various contributors
// The full list of copyright holders can be found in the Copyright.txt
// file, which is part of this source code distribution.
//
// The AGS source code is provided under the Artistic License 2.0.
// A copy of this license can be found in the file License.txt and at
// https://opensource.org/license/artistic-2-0/
//
//=============================================================================
#include "ac/sys_events.h"
#include <chrono>
#include <deque>
#include <math.h>
#include <SDL.h>
#include "core/platform.h"
#include "ac/common.h"
#include "ac/gamesetup.h"
#include "ac/gamesetupstruct.h"
#include "ac/keycode.h"
#include "ac/mouse.h"
#include "ac/timer.h"
#include "device/mousew32.h"
#include "gfx/graphicsdriver.h"
#include "platform/base/agsplatformdriver.h"
#include "platform/base/sys_main.h"
#include "main/engine.h"
#include "util/string_utils.h"
#include "util/utf8.h"
using namespace AGS::Common;
using namespace AGS::Engine;
extern GameSetupStruct game;
extern IGraphicsDriver *gfxDriver;
eAGSKeyCode sdl_key_to_ags_key(const SDL_KeyboardEvent &kbevt, bool old_keyhandle);
int sdl_mod_to_ags_mod(const SDL_KeyboardEvent &kbevt);
// Converts SDL scan and key codes to the ags keycode
KeyInput sdl_keyevt_to_ags_key(const SDL_Event &event, bool old_keyhandle)
{
KeyInput ki;
// Normally SDL_TEXTINPUT is meant for handling the printable characters,
// and not the actual key presses.
// But in the "old key handle" mode we use it also to get the full range
// of key codes corresponding to chars, including combos (SHIFT + 3 = #).
// TODO: find out if it's feasible to just convert keydown + SHIFT combos
// ourselves: then we can utilize SDL_KEYDOWN for both modes and leave
// TEXTINPUT for char print only.
switch (event.type)
{
case SDL_TEXTINPUT:
char ascii[sizeof(SDL_TextInputEvent::text)];
StrUtil::ConvertUtf8ToAscii(event.text.text, "C", &ascii[0], sizeof(ascii));
if (old_keyhandle && (ascii[0] >= 32))
{
ki.Key = static_cast<eAGSKeyCode>(ascii[0]);
ki.CompatKey = ki.Key;
}
snprintf(ki.Text, KeyInput::UTF8_ARR_SIZE, "%s", event.text.text);
Utf8::GetChar(event.text.text, sizeof(SDL_TextInputEvent::text), &ki.UChar);
return ki;
case SDL_KEYDOWN:
ki.Mod = sdl_mod_to_ags_mod(event.key);
ki.Key = sdl_key_to_ags_key(event.key, old_keyhandle);
ki.CompatKey = sdl_key_to_ags_key(event.key, true);
if (!old_keyhandle && (ki.CompatKey == eAGSKeyCodeNone))
ki.CompatKey = ki.Key; // in the new mode also assign letter-range keys
return ki;
default:
return ki;
}
}
int sdl_mod_to_ags_mod(const SDL_KeyboardEvent &kbevt)
{
const SDL_Keysym key = kbevt.keysym;
const Uint16 mod = key.mod;
int ags_mod = 0;
if (mod & KMOD_LSHIFT) ags_mod |= eAGSModLShift;
if (mod & KMOD_RSHIFT) ags_mod |= eAGSModRShift;
if (mod & KMOD_LCTRL) ags_mod |= eAGSModLCtrl;
if (mod & KMOD_RCTRL) ags_mod |= eAGSModRCtrl;
if (mod & KMOD_LALT) ags_mod |= eAGSModLAlt;
if (mod & KMOD_RALT) ags_mod |= eAGSModRAlt;
if (mod & KMOD_NUM) ags_mod |= eAGSModNum;
if (mod & KMOD_CAPS) ags_mod |= eAGSModCaps;
return ags_mod;
}
eAGSKeyCode sdl_key_to_ags_key(const SDL_KeyboardEvent &kbevt, bool old_keyhandle)
{
const SDL_Keysym key = kbevt.keysym;
const SDL_Keycode sym = key.sym;
const Uint16 mod = key.mod;
// NOTE: keycodes such as SDLK_EXCLAIM ('!') may be misleading, as they are NOT
// received when user presses for example Shift + 1 on regular keyboard, but only on
// systems where single keypress can produce that symbol.
// Old mode: Ctrl and Alt combinations realign the letter code to certain offset
if (old_keyhandle && (sym >= SDLK_a && sym <= SDLK_z))
{
if ((mod & KMOD_CTRL) != 0) // align letters to code 1
return static_cast<eAGSKeyCode>( 0 + (sym - SDLK_a) + 1 );
else if ((mod & KMOD_ALT) != 0) // align letters to code 301
return static_cast<eAGSKeyCode>( AGS_EXT_KEY_SHIFT + (sym - SDLK_a) + 1 );
}
// New mode: also handle common key range
else if (!old_keyhandle && (sym >= SDLK_SPACE && sym <= SDLK_z))
{
return static_cast<eAGSKeyCode>(sym);
}
// NumPad with NumLock on
if ((sym >= SDLK_KP_1 && sym <= SDLK_KP_PERIOD) && (mod & KMOD_NUM) != 0)
{
switch (sym)
{
case SDLK_KP_1: case SDLK_KP_2: case SDLK_KP_3: case SDLK_KP_4:
case SDLK_KP_5: case SDLK_KP_6: case SDLK_KP_7: case SDLK_KP_8:
case SDLK_KP_9:
return static_cast<eAGSKeyCode>(sym - SDLK_KP_1 + SDLK_1);
case SDLK_KP_0:
return eAGSKeyCode0;
case SDLK_KP_PERIOD:
return eAGSKeyCodePeriod;
default:
return eAGSKeyCodeNone;
}
}
// Remaining codes may match or not, but we use a big table anyway.
// TODO: this is code by [sonneveld],
// double check that we must use scan codes here, maybe can use sdl key (sym) too?
switch (key.scancode)
{
case SDL_SCANCODE_BACKSPACE: return eAGSKeyCodeBackspace;
case SDL_SCANCODE_TAB:
case SDL_SCANCODE_KP_TAB: return eAGSKeyCodeTab;
case SDL_SCANCODE_RETURN:
case SDL_SCANCODE_RETURN2:
case SDL_SCANCODE_KP_ENTER: return eAGSKeyCodeReturn;
case SDL_SCANCODE_ESCAPE: return eAGSKeyCodeEscape;
case SDL_SCANCODE_F1: return eAGSKeyCodeF1;
case SDL_SCANCODE_F2: return eAGSKeyCodeF2;
case SDL_SCANCODE_F3: return eAGSKeyCodeF3;
case SDL_SCANCODE_F4: return eAGSKeyCodeF4;
case SDL_SCANCODE_F5: return eAGSKeyCodeF5;
case SDL_SCANCODE_F6: return eAGSKeyCodeF6;
case SDL_SCANCODE_F7: return eAGSKeyCodeF7;
case SDL_SCANCODE_F8: return eAGSKeyCodeF8;
case SDL_SCANCODE_F9: return eAGSKeyCodeF9;
case SDL_SCANCODE_F10: return eAGSKeyCodeF10;
case SDL_SCANCODE_F11: return eAGSKeyCodeF11;
case SDL_SCANCODE_F12: return eAGSKeyCodeF12;
case SDL_SCANCODE_KP_7:
case SDL_SCANCODE_HOME: return eAGSKeyCodeHome;
case SDL_SCANCODE_KP_8:
case SDL_SCANCODE_UP: return eAGSKeyCodeUpArrow;
case SDL_SCANCODE_KP_9:
case SDL_SCANCODE_PAGEUP: return eAGSKeyCodePageUp;
case SDL_SCANCODE_KP_4:
case SDL_SCANCODE_LEFT: return eAGSKeyCodeLeftArrow;
case SDL_SCANCODE_KP_5: return eAGSKeyCodeNumPad5;
case SDL_SCANCODE_KP_6:
case SDL_SCANCODE_RIGHT: return eAGSKeyCodeRightArrow;
case SDL_SCANCODE_KP_1:
case SDL_SCANCODE_END: return eAGSKeyCodeEnd;
case SDL_SCANCODE_KP_2:
case SDL_SCANCODE_DOWN: return eAGSKeyCodeDownArrow;
case SDL_SCANCODE_KP_3:
case SDL_SCANCODE_PAGEDOWN: return eAGSKeyCodePageDown;
case SDL_SCANCODE_KP_0:
case SDL_SCANCODE_INSERT: return eAGSKeyCodeInsert;
case SDL_SCANCODE_KP_PERIOD:
case SDL_SCANCODE_DELETE: return eAGSKeyCodeDelete;
// KeyPad (remaining keys, not handled above)
case SDL_SCANCODE_KP_DIVIDE: return eAGSKeyCodeForwardSlash;
case SDL_SCANCODE_KP_MULTIPLY: return eAGSKeyCodeAsterisk;
case SDL_SCANCODE_KP_MINUS: return eAGSKeyCodeHyphen;
case SDL_SCANCODE_KP_PLUS: return eAGSKeyCodePlus;
case SDL_SCANCODE_LSHIFT: return eAGSKeyCodeLShift;
case SDL_SCANCODE_RSHIFT: return eAGSKeyCodeRShift;
case SDL_SCANCODE_LCTRL: return eAGSKeyCodeLCtrl;
case SDL_SCANCODE_RCTRL: return eAGSKeyCodeRCtrl;
case SDL_SCANCODE_LALT: return eAGSKeyCodeLAlt;
case SDL_SCANCODE_RALT: return eAGSKeyCodeRAlt;
default: return eAGSKeyCodeNone;
}
}
// Converts ags key to SDL key scans (up to 3 values, because this is not a 1:1 match);
// NOTE: will ignore Ctrl+ or Alt+ script keys.
// TODO: double check and ammend later if anything is missing
bool ags_key_to_sdl_scan(eAGSKeyCode key, SDL_Scancode(&scan)[3])
{
scan[0] = SDL_SCANCODE_UNKNOWN;
scan[1] = SDL_SCANCODE_UNKNOWN;
scan[2] = SDL_SCANCODE_UNKNOWN;
SDL_Keycode sym = SDLK_UNKNOWN;
// Process series of keys that match certain sym code
// SDL sym codes happen to match small ASCII letters, so lowercase ours if necessary
if (key >= eAGSKeyCodeA && key <= eAGSKeyCodeZ)
{
sym = static_cast<SDL_Keycode>(key - eAGSKeyCodeA + SDLK_a);
}
// Rest of the printable characters seem to match (and match ascii codes)
else if (key >= eAGSKeyCodeSpace && key <= eAGSKeyCodeBackquote)
{
sym = static_cast<SDL_Keycode>(key);
}
// If we have got key sym, convert it to SDL scancode using library's function
if (sym != SDLK_UNKNOWN)
{
scan[0] = SDL_GetScancodeFromKey(sym);
// Numpad keys that do not depend on NumLock state, so should always be checked
// TODO: I was not certain whether to handle numeric keys here, because they
// are only normally generated by numpad when NumLock is on, and that classifies
// as "using modifier key". OTOH, there is a distinct AGS KeyCode for NumPad5,
// but not others. Perhaps, it would be desired to add other NumPad* keycodes
// to let user combine them with System.NumLock mod to know the correct key state.
switch (key)
{
case eAGSKeyCodeAsterisk: scan[1] = SDL_SCANCODE_KP_MULTIPLY; break;
case eAGSKeyCodePlus: scan[1] = SDL_SCANCODE_KP_PLUS; break;
case eAGSKeyCodeHyphen: scan[1] = SDL_SCANCODE_KP_MINUS; break;
case eAGSKeyCodeForwardSlash: scan[1] = SDL_SCANCODE_KP_DIVIDE; break;
default: break;
}
return true;
}
// Handle keys that are mapped directly to scancode (based on [sonneveld]'s code),
// and those that require special treatment (multiple scancode variants)
switch (key)
{
// Various service keys
case eAGSKeyCodeBackspace: scan[0] = SDL_SCANCODE_BACKSPACE; scan[1] = SDL_SCANCODE_KP_BACKSPACE; return true;
case eAGSKeyCodeTab: scan[0] = SDL_SCANCODE_TAB; scan[1] = SDL_SCANCODE_KP_TAB; return true;
case eAGSKeyCodeReturn: scan[0] = SDL_SCANCODE_RETURN; scan[1] = SDL_SCANCODE_RETURN2; scan[2] = SDL_SCANCODE_KP_ENTER; return true;
case eAGSKeyCodeEscape: scan[0] = SDL_SCANCODE_ESCAPE; return true;
// Functional keys
case eAGSKeyCodeF1: scan[0] = SDL_SCANCODE_F1; return true;
case eAGSKeyCodeF2: scan[0] = SDL_SCANCODE_F2; return true;
case eAGSKeyCodeF3: scan[0] = SDL_SCANCODE_F3; return true;
case eAGSKeyCodeF4: scan[0] = SDL_SCANCODE_F4; return true;
case eAGSKeyCodeF5: scan[0] = SDL_SCANCODE_F5; return true;
case eAGSKeyCodeF6: scan[0] = SDL_SCANCODE_F6; return true;
case eAGSKeyCodeF7: scan[0] = SDL_SCANCODE_F7; return true;
case eAGSKeyCodeF8: scan[0] = SDL_SCANCODE_F8; return true;
case eAGSKeyCodeF9: scan[0] = SDL_SCANCODE_F9; return true;
case eAGSKeyCodeF10: scan[0] = SDL_SCANCODE_F10; return true;
case eAGSKeyCodeF11: scan[0] = SDL_SCANCODE_F11; return true;
case eAGSKeyCodeF12: scan[0] = SDL_SCANCODE_F12; return true;
// Keys that may be found on both main keyboard and NumPad
case eAGSKeyCodeHome: scan[0] = SDL_SCANCODE_KP_7; scan[1] = SDL_SCANCODE_HOME; return true;
case eAGSKeyCodeUpArrow: scan[0] = SDL_SCANCODE_KP_8; scan[1] = SDL_SCANCODE_UP; return true;
case eAGSKeyCodePageUp: scan[0] = SDL_SCANCODE_KP_9; scan[1] = SDL_SCANCODE_PAGEUP; return true;
case eAGSKeyCodeLeftArrow: scan[0] = SDL_SCANCODE_KP_4; scan[1] = SDL_SCANCODE_LEFT; return true;
case eAGSKeyCodeNumPad5: scan[0] = SDL_SCANCODE_KP_5; return true;
case eAGSKeyCodeRightArrow: scan[0] = SDL_SCANCODE_KP_6; scan[1] = SDL_SCANCODE_RIGHT; return true;
case eAGSKeyCodeEnd: scan[0] = SDL_SCANCODE_KP_1; scan[1] = SDL_SCANCODE_END; return true;
case eAGSKeyCodeDownArrow: scan[0] = SDL_SCANCODE_KP_2; scan[1] = SDL_SCANCODE_DOWN; return true;
case eAGSKeyCodePageDown: scan[0] = SDL_SCANCODE_KP_3; scan[1] = SDL_SCANCODE_PAGEDOWN; return true;
case eAGSKeyCodeInsert: scan[0] = SDL_SCANCODE_KP_0; scan[1] = SDL_SCANCODE_INSERT; return true;
case eAGSKeyCodeDelete: scan[0] = SDL_SCANCODE_KP_PERIOD; scan[1] = SDL_SCANCODE_DELETE; return true;
// Modifier keys
case eAGSKeyCodeLShift: scan[0] = SDL_SCANCODE_LSHIFT; return true;
case eAGSKeyCodeRShift: scan[0] = SDL_SCANCODE_RSHIFT; return true;
case eAGSKeyCodeLCtrl: scan[0] = SDL_SCANCODE_LCTRL; return true;
case eAGSKeyCodeRCtrl: scan[0] = SDL_SCANCODE_RCTRL; return true;
case eAGSKeyCodeLAlt: scan[0] = SDL_SCANCODE_LALT; return true;
case eAGSKeyCodeRAlt: scan[0] = SDL_SCANCODE_RALT; return true;
default: return false;
}
}
// ----------------------------------------------------------------------------
// KEYBOARD INPUT
// ----------------------------------------------------------------------------
// Because our game engine still uses input polling, we have to accumulate
// input events for our internal use whenever engine have to query player input.
static std::deque<SDL_Event> g_inputEvtQueue;
int sys_modkeys = 0; // saved accumulated key mods
bool sys_modkeys_fired = false; // saved mod key combination already fired
InputType ags_inputevent_ready()
{
if (g_inputEvtQueue.size() == 0)
return kInputNone;
switch (g_inputEvtQueue.front().type)
{
case SDL_KEYDOWN:
case SDL_KEYUP:
case SDL_TEXTINPUT:
return kInputKeyboard;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
return kInputMouse;
default:
return kInputNone;
}
}
SDL_Event ags_get_next_inputevent()
{
if (!g_inputEvtQueue.empty())
{
const auto evt = g_inputEvtQueue.front();
g_inputEvtQueue.pop_front();
return evt;
}
return {};
}
int ags_iskeydown(eAGSKeyCode ags_key)
{
// old input handling: update key state in realtime
// left only in case if necessary for some ancient game, but
// this really may only be required if there's a key waiting loop in
// script without Wait(1) to let engine poll events in a natural way.
if (game.options[OPT_KEYHANDLEAPI] == 0)
SDL_PumpEvents();
const Uint8 *state = SDL_GetKeyboardState(NULL);
SDL_Scancode scan[3];
if (!ags_key_to_sdl_scan(ags_key, scan))
return 0;
return (state[scan[0]] || state[scan[1]] || state[scan[2]]);
}
void ags_simulate_keypress(eAGSKeyCode ags_key)
{
SDL_Scancode scan[3];
if (!ags_key_to_sdl_scan(ags_key, scan))
return;
// Push a key event to the event queue; note that this won't affect the key states array
SDL_Event sdlevent = {};
sdlevent.type = SDL_KEYDOWN;
sdlevent.key.keysym.sym = SDL_GetKeyFromScancode(scan[0]);
sdlevent.key.keysym.scancode = scan[0];
SDL_PushEvent(&sdlevent);
sdlevent.type = SDL_KEYUP;
SDL_PushEvent(&sdlevent);
}
static void on_sdl_key_down(const SDL_Event &event)
{
// Engine is not structured very well yet, and we cannot pass this event where it's needed;
// instead we save it in the queue where it will be ready whenever any component asks for one.
g_inputEvtQueue.push_back(event);
}
static void on_sdl_key_up(const SDL_Event &event)
{
// Key up events are only used for reacting on mod key combinations at the moment.
g_inputEvtQueue.push_back(event);
}
static void on_sdl_textinput(const SDL_Event &event)
{
// We also push text input events to the same queue, as this is only valid way to get proper
// text interpretation of the pressed key combination based on current system locale.
g_inputEvtQueue.push_back(event);
}
// ----------------------------------------------------------------------------
// MOUSE INPUT
// ----------------------------------------------------------------------------
// TODO: check later, if this may be useful in other places (then move to header)
enum eAGSMouseButtonMask
{
MouseBitLeft = 0x01,
MouseBitRight = 0x02,
MouseBitMiddle = 0x04,
MouseBitX1 = 0x08,
MouseBitX2 = 0x10
};
eAGSMouseButton sdl_mbut_to_ags_but(int sdl_mbut)
{
switch (sdl_mbut)
{
case SDL_BUTTON_LEFT: return kMouseLeft;
case SDL_BUTTON_RIGHT: return kMouseRight;
case SDL_BUTTON_MIDDLE: return kMouseMiddle;
default: return kMouseNone;
}
}
static int sdl_button_to_mask(int button)
{
switch (button)
{
case SDL_BUTTON_LEFT: return MouseBitLeft;
case SDL_BUTTON_RIGHT: return MouseBitRight;
case SDL_BUTTON_MIDDLE: return MouseBitMiddle;
case SDL_BUTTON_X1: return MouseBitX1;
case SDL_BUTTON_X2: return MouseBitX2;
}
return 0;
}
static int ags_button_to_sdl(eAGSMouseButton but)
{
switch (but)
{
case kMouseLeft: return SDL_BUTTON_LEFT;
case kMouseRight: return SDL_BUTTON_RIGHT;
case kMouseMiddle: return SDL_BUTTON_MIDDLE;
default: return 0;
}
}
// Convert mouse button id to flags
const int MouseButton2Bits[kNumMouseButtons] =
{ 0, MouseBitLeft, MouseBitRight, MouseBitMiddle };
// Latest mouse button state
static int mouse_button_state = 0;
// Accumulated absolute and relative mouse device motion.
// May be retrieved by calling *acquire_absxy and *acquire_relxy functions,
// after which these are reset, until next motion event is received.
volatile int sys_mouse_x = 0; // mouse x position
volatile int sys_mouse_y = 0; // mouse y position
volatile int sys_mouse_z = 0; // mouse wheel position
// Relative x and y deltas
static int mouse_accum_relx = 0, mouse_accum_rely = 0;
// ID of a device, which mouse events will be ignored (for easier testing)
static int disabled_mouse_device = UINT32_MAX - 10;
// Cached values, remember old mouse state
static int mouse_z_was = 0;
bool ags_misbuttondown(eAGSMouseButton but)
{
return (mouse_button_state & MouseButton2Bits[but]) != 0;
}
void ags_simulate_mouseclick(eAGSMouseButton but)
{
SDL_Event sdlevent = {};
sdlevent.type = SDL_MOUSEBUTTONDOWN;
sdlevent.button.button = ags_button_to_sdl(but);
sdlevent.button.x = sys_mouse_x; // CHECKME later if this is okay...
sdlevent.button.y = sys_mouse_y;
SDL_PushEvent(&sdlevent);
sdlevent.type = SDL_MOUSEBUTTONUP;
SDL_PushEvent(&sdlevent);
}
// Syncs all the emulated mouse devices with the real sys_mouse_* coords
static void sync_sys_mouse_pos();
static void on_sdl_mouse_motion(const SDL_MouseMotionEvent &event)
{
if (event.which == disabled_mouse_device)
return;
sys_mouse_x = event.x;
sys_mouse_y = event.y;
mouse_accum_relx += event.xrel;
mouse_accum_rely += event.yrel;
sync_sys_mouse_pos();
}
static void on_sdl_mouse_down(const SDL_Event &event)
{
if (event.button.which == disabled_mouse_device)
return;
sys_mouse_x = event.button.x;
sys_mouse_y = event.button.y;
mouse_button_state |= sdl_button_to_mask(event.button.button);
g_inputEvtQueue.push_back(event);
}
static void on_sdl_mouse_up(const SDL_Event &event)
{
sys_mouse_x = event.button.x;
sys_mouse_y = event.button.y;
mouse_button_state &= ~sdl_button_to_mask(event.button.button);
// Uncomment when need to handle mouse up event in the game states
//g_inputEvtQueue.push_back(event);
}
static void on_sdl_mouse_wheel(const SDL_MouseWheelEvent &event)
{
if (event.which == disabled_mouse_device)
return;
sys_mouse_z += event.y;
}
void ags_mouse_acquire_relxy(int &x, int &y)
{
x = mouse_accum_relx;
y = mouse_accum_rely;
mouse_accum_relx = 0;
mouse_accum_rely = 0;
}
void ags_domouse()
{
Mouse::Poll();
}
int ags_check_mouse_wheel()
{
if (game.options[OPT_MOUSEWHEEL] == 0)
return 0;
if (sys_mouse_z == mouse_z_was)
return 0;
int result = 0;
if (sys_mouse_z > mouse_z_was)
result = 1; // eMouseWheelNorth
else
result = -1; // eMouseWheelSouth
mouse_z_was = sys_mouse_z;
return result;
}
// ----------------------------------------------------------------------------
// TOUCH INPUT
// ----------------------------------------------------------------------------
// SDL_FingerIDs are unique for down-motion-up events, but beyond this there is no cross-platform guarantee
// this struct tracks currently active fingers on the touch screen, and matches a slot index for each
// we will use this to implement the logic in this example
// - first finger down (consider LMB, index 0);
// - second finger down (consider RMB, index 1);
// - first finger up (consider LMB up);
// - second finger is still pressed and still considered RMB;
// - third(?) finger down, takes the free role of LMB (index 0).
struct Fingers
{
public:
static const int MAX_FINGERS = 2;
static const int NO_INDEX = -1;
// store fingerId, return given finger index
int push(SDL_FingerID fingerId)
{
if (contains(fingerId))
return NO_INDEX; // invalid, fingerId already present
auto it = std::find(_fingers.begin(), _fingers.end(), NO_ID);
if(it == _fingers.end())
return NO_INDEX; // no slot for new finger
*it = fingerId;
return it - _fingers.begin();
};
int get_index(SDL_FingerID fingerId) const
{
auto it = std::find(_fingers.begin(), _fingers.end(), fingerId);
if(it != _fingers.end())
return it - _fingers.begin();
return NO_INDEX;
};
void pop(SDL_FingerID fingerId)
{
int idx = get_index(fingerId);
assert(idx != NO_INDEX);
if (idx != NO_INDEX) {
_fingers[idx] = NO_ID;
}
};
private:
const SDL_FingerID NO_ID = -1; // std::find reads by reference, can't be static
std::array<SDL_FingerID, MAX_FINGERS> _fingers{{NO_ID, NO_ID}};
bool contains(SDL_FingerID fingerId) const
{
return std::find(_fingers.begin(), _fingers.end(), fingerId) != _fingers.end();
}
};
// Touch input state
struct TouchState
{
// on-screen fingers, used to remember the finger's role until all of them are released
Fingers fingers;
// Accumulated finger bits (a collection of bits shifted by finger index)
int fingers_down = 0;
// Double tap detection
// Max delay between finger actions which will still count as double tap
const std::chrono::milliseconds quick_tap_delay = std::chrono::milliseconds(300);
// The last tapping action timestamp
AGS_Clock::time_point last_tap_ts = AGS_Clock::now();
// The last tap's finger index
int last_tap_finger = 0;
// Same finger consecutive taps counter
int tap_count = 0;
} static touch;
// Touch-to-mouse emulation
struct Touch2Mouse
{
TouchMouseEmulation mode = kTouchMouse_None;
// Relative mode switch
bool is_relative = false;
// Speed of the relative movement
float speed = 1.f;
// Current (last) received touch position
Point pos;
// Starting touch position (needed for drag detection)
Point start_pos;
// Tells if the emulated mouse position has been initialized
bool emul_pos_init = false;
// Current emulated mouse position (eq to pos in absolute mode)
Point emul_pos;
// Last emulated mouse movement delta
Point emul_delta;
// Accumulated relative movement, for higher rounding accuracy
float rel_accum_x = 0.f;
float rel_accum_y = 0.f;
// Minimal finger motion required to begin cursor drag
const float drag_trigger_dist = 0.01f; // relative to screen size
// Accumulated drag distance (in absolute value)
float drag_dist_accum = 0.f;
// Tells to not translation finger motion events into the emulated mouse
bool ignore_motion = false;
// Tells to ignore any finger action other than the finger that
// emulates given button (SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT etc)
int force_button = 0;
// Tells that the cursor is being dragged around
bool is_dragging = false;
// Which emulated mouse button is down while dragging the cursor
int drag_down = 0;
} static t2m;
void ags_touch_set_mouse_emulation(TouchMouseEmulation mode,
bool relative, float speed)
{
t2m.mode = mode;
t2m.is_relative = relative;
t2m.speed = speed;
}
// Converts touch finger index to the emulated mouse button
static int tfinger_to_mouse_but(int finger_index)
{
switch (finger_index)
{
case 0: return SDL_BUTTON_LEFT;
case 1: return SDL_BUTTON_RIGHT;
default: return 0;
}
}
static void send_mouse_button_event(int evt_type, int button, int x, int y)
{
SDL_Event evt = {};
evt.type = evt_type;
evt.button.which = SDL_TOUCH_MOUSEID;
evt.button.state = evt_type == SDL_MOUSEBUTTONDOWN ? SDL_PRESSED : SDL_RELEASED;
evt.button.button = button;
evt.button.x = x;
evt.button.y = y;
SDL_PushEvent(&evt);
}
static void send_mouse_motion_event(int x, int y, int xrel, int yrel)
{
SDL_Event evt = {};
evt.type = SDL_MOUSEMOTION;
evt.motion.which = SDL_TOUCH_MOUSEID;
evt.motion.x = x;
evt.motion.y = y;
evt.motion.xrel = xrel;
evt.motion.yrel = yrel;
SDL_PushEvent(&evt);
}
// Handles double tap detection (multiple touch downs and ups)
static void detect_double_tap(const SDL_TouchFingerEvent &event, bool down)
{
auto tap_ts = AGS_Clock::now();
if ((touch.last_tap_finger == event.fingerId) &&
(tap_ts < (touch.last_tap_ts + touch.quick_tap_delay)))
{
if (down) touch.tap_count++;
}
else
{
touch.tap_count = down ? 1 : 0;
}
touch.last_tap_ts = tap_ts;
touch.last_tap_finger = event.fingerId;
}
// Calculates relative emulated mouse delta,
// utilizes speed scale and accumulated touch delta;
// based on SDL2's GetScaledMouseDelta
static int calc_relative_delta(float value, float scale, float &accum)
{
if ((value > 0.f) != (accum > 0.f))
accum = 0.f; // reset accumulated delta if direction has changed
accum += value * scale;
value = std::truncf(accum); // get nearest full integer value
accum -= value; // subtract used part from accumulation
return value;
}
// Calculates emulated mouse position and movement delta,
// based on touch position and delta, and t2m emulation settings
static void set_t2m_pos(float x, float y, float dx, float dy)
{
// TODO: better way to get SDL's logical size? we cannot access sdl renderer here
int w = gfxDriver->GetDisplayMode().Width;
int h = gfxDriver->GetDisplayMode().Height;
// Save real touch pos
t2m.pos = Point(std::roundf(x * w), std::roundf(y * h));
// No deltas imply the drag start: in such case reset relative deltas
if ((dx == 0.f) && (dy == 0.f))
{
t2m.rel_accum_x = 0.f;
t2m.rel_accum_y = 0.f;
t2m.emul_delta = Point();
}
else
{
// TODO: add a separate t2m speed setting
int rel_x = calc_relative_delta((dx * w), t2m.speed, t2m.rel_accum_x);
int rel_y = calc_relative_delta((dy * h), t2m.speed, t2m.rel_accum_y);
t2m.emul_delta = Point(rel_x, rel_y);
}
// Save emulated mouse pos
if (t2m.is_relative)
{
// In relative mode, if no position was init yet, then apply last known sys mouse pos
if (!t2m.emul_pos_init)
t2m.emul_pos = Point(sys_mouse_x, sys_mouse_y);
// delta from the last remembered pos
t2m.emul_pos = t2m.emul_pos + t2m.emul_delta;
}
else
{ // translate 1:1 to the real touch pos
t2m.emul_pos = t2m.pos;
}
t2m.emul_pos = Point::Clamp(t2m.emul_pos, Point(), Point(w - 1, h - 1));
t2m.emul_pos_init = true;
}
// Syncs all the emulated mouse devices with the real sys_mouse_* coords
static void sync_sys_mouse_pos()
{
if (t2m.emul_pos_init)
{
t2m.emul_pos = Point(sys_mouse_x, sys_mouse_y);
}
}
static void on_sdl_touch_down(const SDL_TouchFingerEvent &event)
{
int finger_index = touch.fingers.push(event.fingerId);
if(finger_index == Fingers::NO_INDEX) return;
touch.fingers_down |= 1 << finger_index;
detect_double_tap(event, true);
switch (t2m.mode)
{
case kTouchMouse_OneFingerDrag:
{
// Touch down means LMB down
int mouse_but = tfinger_to_mouse_but(finger_index);
if (mouse_but == SDL_BUTTON_LEFT)
{
set_t2m_pos(event.x, event.y, 0.f, 0.f);
t2m.start_pos = t2m.pos;
send_mouse_button_event(SDL_MOUSEBUTTONDOWN, mouse_but,
t2m.emul_pos.X, t2m.emul_pos.Y);
}
break;
}
case kTouchMouse_TwoFingersTap:
{
int mouse_but = tfinger_to_mouse_but(finger_index);
// Handle double tap for drag-n-drop movement
if ((mouse_but == SDL_BUTTON_LEFT) && (touch.tap_count == 2))
{
t2m.drag_down = SDL_BUTTON_LEFT;
send_mouse_button_event(SDL_MOUSEBUTTONDOWN, t2m.drag_down, t2m.emul_pos.X, t2m.emul_pos.Y);
}
// If another finger was down, then unpress the drag
else if (t2m.drag_down > 0)
{
send_mouse_button_event(SDL_MOUSEBUTTONUP, t2m.drag_down, t2m.emul_pos.X, t2m.emul_pos.Y);
t2m.drag_down = 0;
}
// If only the first finger is down: allow to move the cursor;
// otherwise, ignore the movement for now
if ((!t2m.ignore_motion) && (mouse_but == SDL_BUTTON_LEFT))
{
set_t2m_pos(event.x, event.y, 0.f, 0.f);
t2m.start_pos = t2m.pos;
t2m.drag_dist_accum = 0.f;
t2m.is_dragging = false;
// CHECKME: do we have to send dx/dy here (calculate as diff since last sys_mouse_xy?)
send_mouse_motion_event(t2m.emul_pos.X, t2m.emul_pos.Y, 0, 0);
}
// If more than one finger was down, lock the cursor motion,
// and force any following emulated clicks to RMB,
// until *all* the fingers are released
if (touch.fingers_down != 1)
{
t2m.ignore_motion = true;
t2m.force_button = SDL_BUTTON_RIGHT;
}
break;
}
default: break; // do nothing
}
}
static void on_sdl_touch_up(const SDL_TouchFingerEvent &event)
{
int finger_index = touch.fingers.get_index(event.fingerId);
if(finger_index == Fingers::NO_INDEX) return;
touch.fingers_down &= ~(1 << finger_index);
detect_double_tap(event, false);
switch (t2m.mode)
{
case kTouchMouse_OneFingerDrag:
{
// Touch up means LMB up
int mouse_but = tfinger_to_mouse_but(finger_index);
if (mouse_but == SDL_BUTTON_LEFT)
{
send_mouse_button_event(SDL_MOUSEBUTTONUP, mouse_but, t2m.emul_pos.X, t2m.emul_pos.Y);
t2m.is_dragging = false;
t2m.emul_pos_init = false; // force init at the next finger down
}
break;
}
case kTouchMouse_TwoFingersTap:
{
int mouse_but = tfinger_to_mouse_but(finger_index);
// If there's a force button set, then click only if the current button matches it;
// otherwise use whatever was released
if ((mouse_but > 0) &&
((t2m.force_button == 0) || (mouse_but == t2m.force_button)))
{
// If was dragging, then only release
if (t2m.drag_down)
{
send_mouse_button_event(SDL_MOUSEBUTTONUP, t2m.drag_down, t2m.emul_pos.X, t2m.emul_pos.Y);
t2m.drag_down = 0;
}
// Else, if was not dragging around, then perform a "click"
// (send both mouse button down and up)
else if (!t2m.is_dragging)
{
send_mouse_button_event(SDL_MOUSEBUTTONDOWN, mouse_but, t2m.emul_pos.X, t2m.emul_pos.Y);
send_mouse_button_event(SDL_MOUSEBUTTONUP, mouse_but, t2m.emul_pos.X, t2m.emul_pos.Y);
}
}
// If all fingers are up, reset the locked mouse motion and forced button
if (touch.fingers_down == 0)
{
t2m.ignore_motion = false;
t2m.force_button = 0;
t2m.is_dragging = false;
t2m.emul_pos_init = false; // force init at the next finger down
}
break;
}
default: break; // do nothing
}
touch.fingers.pop(event.fingerId);
}
static void on_sdl_touch_motion(const SDL_TouchFingerEvent &event)
{
int finger_index = touch.fingers.get_index(event.fingerId);
if(finger_index == Fingers::NO_INDEX) return;
switch (t2m.mode)
{
case kTouchMouse_OneFingerDrag:
case kTouchMouse_TwoFingersTap:
{
// Touch motion means mouse motion;
// move only if it's the first finger, and motion is not ignored
int mouse_but = tfinger_to_mouse_but(finger_index);
if ((!t2m.ignore_motion) && (mouse_but == SDL_BUTTON_LEFT))
{
// Absolute positioning
set_t2m_pos(event.x, event.y, event.dx, event.dy);
send_mouse_motion_event(t2m.emul_pos.X, t2m.emul_pos.Y, t2m.emul_delta.X, t2m.emul_delta.Y);
// Test the absolute value of the touch drag so far
t2m.drag_dist_accum += std::sqrt((event.dx * event.dx) + (event.dy * event.dy));
if (t2m.drag_dist_accum > t2m.drag_trigger_dist)
{
t2m.is_dragging = true;
}
}
}
default: break; // do nothing
}
}
void ags_clear_input_state()
{
// clear everything related to the input state
g_inputEvtQueue.clear();
sys_modkeys = 0;
sys_modkeys_fired = false;
mouse_button_state = 0;
ags_clear_mouse_movement();
}
void ags_clear_input_buffer()
{
g_inputEvtQueue.clear();
// accumulated mod keys have to be cleared because they depend on key evt queue
sys_modkeys = 0;
sys_modkeys_fired = false;
// forget about accumulated mouse movement too
ags_clear_mouse_movement();
}
void ags_clear_mouse_movement()
{
mouse_accum_relx = 0;
mouse_accum_rely = 0;
}
// ----------------------------------------------------------------------------
// EVENTS
// ----------------------------------------------------------------------------
static void(*_on_quit_callback)(void) = nullptr;
static void(*_on_switchin_callback)(void) = nullptr;
static void(*_on_switchout_callback)(void) = nullptr;
void sys_evt_set_quit_callback(void(*proc)(void)) {
_on_quit_callback = proc;
}
void sys_evt_set_focus_callbacks(void(*switch_in)(void), void(*switch_out)(void)) {
_on_switchin_callback = switch_in;
_on_switchout_callback = switch_out;
}
void sys_evt_process_one(const SDL_Event &event) {
switch (event.type) {
// GENERAL
case SDL_QUIT:
Debug::Printf("SDL event: quit");
if (_on_quit_callback) {
_on_quit_callback();
}
break;
// WINDOW
case SDL_WINDOWEVENT:
switch (event.window.event)
{
case SDL_WINDOWEVENT_FOCUS_GAINED:
Debug::Printf("Window event: focus gained");