1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
43 /* forward declarations for internal use */
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 /* event filter especially needed for SDL event filtering due to
49 delay problems with lots of mouse motion events when mouse button
50 not pressed (X11 can handle this with 'PointerMotionHintMask') */
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53 or disable keyboard auto-repeat, filter repeated keyboard events instead */
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 /* skip repeated key press events if keyboard auto-repeat is disabled */
61 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 /* non-motion events are directly passed to event handler functions */
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 /* do no reset mouse cursor before all pending events have been processed */
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 /* skip mouse motion events without pressed button outside level editor */
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 /* to prevent delay problems, skip mouse motion events if the very next
110 event is also a mouse motion event (and therefore effectively only
111 handling the last of a row of mouse motion events in the event queue) */
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 /* nothing to do if the current event is not a mouse motion event */
116 if (event->type != EVENT_MOTIONNOTIFY)
119 /* only skip motion events with pressed button outside the game */
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 /* if next event is also a mouse motion event, skip the current one */
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 /* This causes the game to stop not only when iconified, but also
242 when on another virtual desktop, which might be not desired. */
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 void HandleMouseCursor()
288 if (game_status == GAME_MODE_TITLE)
290 /* when showing title screens, hide mouse pointer (if not moved) */
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 /* when playing, display a special mouse pointer inside the playfield */
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM)
308 SetMouseCursor(CURSOR_PLAYFIELD);
311 else if (gfx.cursor_mode != CURSOR_DEFAULT)
313 SetMouseCursor(CURSOR_DEFAULT);
316 /* this is set after all pending events have been processed */
317 cursor_mode_last = gfx.cursor_mode;
329 /* execute event related actions after pending events have been processed */
330 HandleEventActions();
332 /* don't use all CPU time when idle; the main loop while playing
333 has its own synchronization and is CPU friendly, too */
335 if (game_status == GAME_MODE_PLAYING)
338 /* always copy backbuffer to visible screen for every video frame */
341 /* reset video frame delay to default (may change again while playing) */
342 SetVideoFrameDelay(MenuFrameDelay);
344 if (game_status == GAME_MODE_QUIT)
349 void ClearEventQueue()
353 while (NextValidEvent(&event))
357 case EVENT_BUTTONRELEASE:
358 button_status = MB_RELEASED;
361 case EVENT_KEYRELEASE:
365 #if defined(TARGET_SDL2)
366 case SDL_CONTROLLERBUTTONUP:
367 HandleJoystickEvent(&event);
373 HandleOtherEvents(&event);
379 void ClearPlayerMouseAction()
381 local_player->mouse_action.lx = 0;
382 local_player->mouse_action.ly = 0;
383 local_player->mouse_action.button = 0;
386 void ClearPlayerAction()
390 /* simulate key release events for still pressed keys */
391 key_joystick_mapping = 0;
392 for (i = 0; i < MAX_PLAYERS; i++)
393 stored_player[i].action = 0;
395 ClearJoystickState();
396 ClearPlayerMouseAction();
399 void SetPlayerMouseAction(int mx, int my, int button)
401 int lx = getLevelFromScreenX(mx);
402 int ly = getLevelFromScreenY(my);
404 ClearPlayerMouseAction();
406 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
409 local_player->mouse_action.lx = lx;
410 local_player->mouse_action.ly = ly;
411 local_player->mouse_action.button = button;
413 if (tape.recording && tape.pausing && tape.use_mouse)
415 /* prevent button release or motion events from un-pausing a paused game */
416 if (button && !motion_status)
417 TapeTogglePause(TAPE_TOGGLE_MANUAL);
421 void SleepWhileUnmapped()
423 boolean window_unmapped = TRUE;
425 KeyboardAutoRepeatOn();
427 while (window_unmapped)
431 if (!WaitValidEvent(&event))
436 case EVENT_BUTTONRELEASE:
437 button_status = MB_RELEASED;
440 case EVENT_KEYRELEASE:
441 key_joystick_mapping = 0;
444 #if defined(TARGET_SDL2)
445 case SDL_CONTROLLERBUTTONUP:
446 HandleJoystickEvent(&event);
447 key_joystick_mapping = 0;
451 case EVENT_MAPNOTIFY:
452 window_unmapped = FALSE;
455 case EVENT_UNMAPNOTIFY:
456 /* this is only to surely prevent the 'should not happen' case
457 * of recursively looping between 'SleepWhileUnmapped()' and
458 * 'HandleOtherEvents()' which usually calls this funtion.
463 HandleOtherEvents(&event);
468 if (game_status == GAME_MODE_PLAYING)
469 KeyboardAutoRepeatOffUnlessAutoplay();
472 void HandleExposeEvent(ExposeEvent *event)
476 void HandleButtonEvent(ButtonEvent *event)
478 #if DEBUG_EVENTS_BUTTON
479 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
481 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
485 #if defined(HAS_SCREEN_KEYBOARD)
486 if (video.shifted_up)
487 event->y += video.shifted_up_pos;
490 motion_status = FALSE;
492 if (event->type == EVENT_BUTTONPRESS)
493 button_status = event->button;
495 button_status = MB_RELEASED;
497 HandleButton(event->x, event->y, button_status, event->button);
500 void HandleMotionEvent(MotionEvent *event)
502 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
505 motion_status = TRUE;
507 #if DEBUG_EVENTS_MOTION
508 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
509 button_status, event->x, event->y);
512 HandleButton(event->x, event->y, button_status, button_status);
515 #if defined(TARGET_SDL2)
517 void HandleWheelEvent(WheelEvent *event)
521 #if DEBUG_EVENTS_WHEEL
523 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
524 event->which, event->x, event->y);
526 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
527 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
528 event->which, event->x, event->y,
529 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
530 "SDL_MOUSEWHEEL_FLIPPED"));
534 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
535 event->x > 0 ? MB_WHEEL_RIGHT :
536 event->y < 0 ? MB_WHEEL_DOWN :
537 event->y > 0 ? MB_WHEEL_UP : 0);
539 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
540 // accelerated mouse wheel available on Mac and Windows
541 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
543 // no accelerated mouse wheel available on Unix/Linux
544 wheel_steps = DEFAULT_WHEEL_STEPS;
547 motion_status = FALSE;
549 button_status = button_nr;
550 HandleButton(0, 0, button_status, -button_nr);
552 button_status = MB_RELEASED;
553 HandleButton(0, 0, button_status, -button_nr);
556 void HandleWindowEvent(WindowEvent *event)
558 #if DEBUG_EVENTS_WINDOW
559 int subtype = event->event;
562 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
563 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
564 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
565 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
566 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
567 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
568 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
569 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
570 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
571 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
572 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
573 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
574 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
575 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
578 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
579 event_name, event->data1, event->data2);
583 // (not needed, as the screen gets redrawn every 20 ms anyway)
584 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
585 event->event == SDL_WINDOWEVENT_RESIZED ||
586 event->event == SDL_WINDOWEVENT_EXPOSED)
590 if (event->event == SDL_WINDOWEVENT_RESIZED)
592 if (!video.fullscreen_enabled)
594 int new_window_width = event->data1;
595 int new_window_height = event->data2;
597 // if window size has changed after resizing, calculate new scaling factor
598 if (new_window_width != video.window_width ||
599 new_window_height != video.window_height)
601 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
602 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
604 // (extreme window scaling allowed, but cannot be saved permanently)
605 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
606 setup.window_scaling_percent =
607 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
608 MAX_WINDOW_SCALING_PERCENT);
610 video.window_width = new_window_width;
611 video.window_height = new_window_height;
613 if (game_status == GAME_MODE_SETUP)
614 RedrawSetupScreenAfterFullscreenToggle();
619 #if defined(PLATFORM_ANDROID)
622 int new_display_width = event->data1;
623 int new_display_height = event->data2;
625 // if fullscreen display size has changed, device has been rotated
626 if (new_display_width != video.display_width ||
627 new_display_height != video.display_height)
629 video.display_width = new_display_width;
630 video.display_height = new_display_height;
632 SDLSetScreenProperties();
639 #define NUM_TOUCH_FINGERS 3
644 SDL_FingerID finger_id;
647 } touch_info[NUM_TOUCH_FINGERS];
649 void HandleFingerEvent(FingerEvent *event)
651 static Key motion_key_x = KSYM_UNDEFINED;
652 static Key motion_key_y = KSYM_UNDEFINED;
653 static Key button_key = KSYM_UNDEFINED;
654 static float motion_x1, motion_y1;
655 static float button_x1, button_y1;
656 static SDL_FingerID motion_id = -1;
657 static SDL_FingerID button_id = -1;
658 int move_trigger_distance_percent = setup.touch.move_distance;
659 int drop_trigger_distance_percent = setup.touch.drop_distance;
660 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
661 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
662 float event_x = event->x;
663 float event_y = event->y;
665 #if DEBUG_EVENTS_FINGER
666 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
667 event->type == EVENT_FINGERPRESS ? "pressed" :
668 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
672 event->dx, event->dy,
676 if (game_status != GAME_MODE_PLAYING)
679 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
682 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
685 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
687 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
689 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
691 event_y = (event_y - ypos) / (1 - ypos);
693 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
694 event_y > 2.0 / 3.0 && event_y < 1 ?
695 setup.input[0].key.snap :
696 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
697 event_y > 2.0 / 3.0 && event_y < 1 ?
698 setup.input[0].key.drop :
699 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
700 event_y > 0 && event_y < 1.0 / 3.0 ?
701 setup.input[0].key.up :
702 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
703 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
704 setup.input[0].key.left :
705 event_x > 8.0 / 9.0 && event_x < 1 &&
706 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
707 setup.input[0].key.right :
708 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
709 event_y > 2.0 / 3.0 && event_y < 1 ?
710 setup.input[0].key.down :
713 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
717 // for any touch input event, enable overlay buttons (if activated)
718 SetOverlayEnabled(TRUE);
720 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
721 getKeyNameFromKey(key), key_status_name, event->fingerId);
723 // check if we already know this touch event's finger id
724 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
726 if (touch_info[i].touched &&
727 touch_info[i].finger_id == event->fingerId)
729 // Error(ERR_DEBUG, "MARK 1: %d", i);
735 if (i >= NUM_TOUCH_FINGERS)
737 if (key_status == KEY_PRESSED)
739 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
741 // unknown finger id -- get new, empty slot, if available
742 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
744 if (touch_info[i].counter < oldest_counter)
747 oldest_counter = touch_info[i].counter;
749 // Error(ERR_DEBUG, "MARK 2: %d", i);
752 if (!touch_info[i].touched)
754 // Error(ERR_DEBUG, "MARK 3: %d", i);
760 if (i >= NUM_TOUCH_FINGERS)
762 // all slots allocated -- use oldest slot
765 // Error(ERR_DEBUG, "MARK 4: %d", i);
770 // release of previously unknown key (should not happen)
772 if (key != KSYM_UNDEFINED)
774 HandleKey(key, KEY_RELEASED);
776 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
777 getKeyNameFromKey(key), "KEY_RELEASED", i);
782 if (i < NUM_TOUCH_FINGERS)
784 if (key_status == KEY_PRESSED)
786 if (touch_info[i].key != key)
788 if (touch_info[i].key != KSYM_UNDEFINED)
790 HandleKey(touch_info[i].key, KEY_RELEASED);
792 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
793 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
796 if (key != KSYM_UNDEFINED)
798 HandleKey(key, KEY_PRESSED);
800 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
801 getKeyNameFromKey(key), "KEY_PRESSED", i);
805 touch_info[i].touched = TRUE;
806 touch_info[i].finger_id = event->fingerId;
807 touch_info[i].counter = Counter();
808 touch_info[i].key = key;
812 if (touch_info[i].key != KSYM_UNDEFINED)
814 HandleKey(touch_info[i].key, KEY_RELEASED);
816 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
817 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
820 touch_info[i].touched = FALSE;
821 touch_info[i].finger_id = 0;
822 touch_info[i].counter = 0;
823 touch_info[i].key = 0;
830 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
833 // use touch direction control
835 if (event->type == EVENT_FINGERPRESS)
837 if (event_x > 1.0 / 3.0)
841 motion_id = event->fingerId;
846 motion_key_x = KSYM_UNDEFINED;
847 motion_key_y = KSYM_UNDEFINED;
849 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
855 button_id = event->fingerId;
860 button_key = setup.input[0].key.snap;
862 HandleKey(button_key, KEY_PRESSED);
864 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
867 else if (event->type == EVENT_FINGERRELEASE)
869 if (event->fingerId == motion_id)
873 if (motion_key_x != KSYM_UNDEFINED)
874 HandleKey(motion_key_x, KEY_RELEASED);
875 if (motion_key_y != KSYM_UNDEFINED)
876 HandleKey(motion_key_y, KEY_RELEASED);
878 motion_key_x = KSYM_UNDEFINED;
879 motion_key_y = KSYM_UNDEFINED;
881 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
883 else if (event->fingerId == button_id)
887 if (button_key != KSYM_UNDEFINED)
888 HandleKey(button_key, KEY_RELEASED);
890 button_key = KSYM_UNDEFINED;
892 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
895 else if (event->type == EVENT_FINGERMOTION)
897 if (event->fingerId == motion_id)
899 float distance_x = ABS(event_x - motion_x1);
900 float distance_y = ABS(event_y - motion_y1);
901 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
902 event_x > motion_x1 ? setup.input[0].key.right :
904 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
905 event_y > motion_y1 ? setup.input[0].key.down :
908 if (distance_x < move_trigger_distance / 2 ||
909 distance_x < distance_y)
910 new_motion_key_x = KSYM_UNDEFINED;
912 if (distance_y < move_trigger_distance / 2 ||
913 distance_y < distance_x)
914 new_motion_key_y = KSYM_UNDEFINED;
916 if (distance_x > move_trigger_distance ||
917 distance_y > move_trigger_distance)
919 if (new_motion_key_x != motion_key_x)
921 if (motion_key_x != KSYM_UNDEFINED)
922 HandleKey(motion_key_x, KEY_RELEASED);
923 if (new_motion_key_x != KSYM_UNDEFINED)
924 HandleKey(new_motion_key_x, KEY_PRESSED);
927 if (new_motion_key_y != motion_key_y)
929 if (motion_key_y != KSYM_UNDEFINED)
930 HandleKey(motion_key_y, KEY_RELEASED);
931 if (new_motion_key_y != KSYM_UNDEFINED)
932 HandleKey(new_motion_key_y, KEY_PRESSED);
938 motion_key_x = new_motion_key_x;
939 motion_key_y = new_motion_key_y;
941 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
944 else if (event->fingerId == button_id)
946 float distance_x = ABS(event_x - button_x1);
947 float distance_y = ABS(event_y - button_y1);
949 if (distance_x < drop_trigger_distance / 2 &&
950 distance_y > drop_trigger_distance)
952 if (button_key == setup.input[0].key.snap)
953 HandleKey(button_key, KEY_RELEASED);
958 button_key = setup.input[0].key.drop;
960 HandleKey(button_key, KEY_PRESSED);
962 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
970 static void HandleButtonOrFinger_MM(int mx, int my, int button)
972 static int old_mx = 0, old_my = 0;
973 static int last_button = MB_LEFTBUTTON;
974 static boolean touched = FALSE;
975 static boolean tapped = FALSE;
977 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
979 // screen tile was tapped (but finger not touching the screen anymore)
980 // (this point will also be reached without receiving a touch event)
981 if (tapped && !touched)
983 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
988 // stop here if this function was not triggered by a touch event
992 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
994 // finger started touching the screen
1004 ClearPlayerMouseAction();
1006 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1009 else if (button == MB_RELEASED && touched)
1011 // finger stopped touching the screen
1016 SetPlayerMouseAction(old_mx, old_my, last_button);
1018 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1020 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1025 // finger moved while touching the screen
1027 int old_x = getLevelFromScreenX(old_mx);
1028 int old_y = getLevelFromScreenY(old_my);
1029 int new_x = getLevelFromScreenX(mx);
1030 int new_y = getLevelFromScreenY(my);
1032 if (new_x != old_x || new_y != old_y)
1037 // finger moved left or right from (horizontal) starting position
1039 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1041 SetPlayerMouseAction(old_mx, old_my, button_nr);
1043 last_button = button_nr;
1045 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1049 // finger stays at or returned to (horizontal) starting position
1051 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1053 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1057 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1062 static void HandleButtonOrFinger(int mx, int my, int button)
1064 static int old_mx = 0, old_my = 0;
1065 static Key motion_key_x = KSYM_UNDEFINED;
1066 static Key motion_key_y = KSYM_UNDEFINED;
1067 static boolean touched = FALSE;
1068 static boolean started_on_player = FALSE;
1069 static boolean player_is_dropping = FALSE;
1070 static int player_drop_count = 0;
1071 static int last_player_x = -1;
1072 static int last_player_y = -1;
1074 if (game_status != GAME_MODE_PLAYING)
1077 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1080 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1082 HandleButtonOrFinger_MM(mx, my, button);
1087 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1090 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1099 started_on_player = FALSE;
1100 player_is_dropping = FALSE;
1101 player_drop_count = 0;
1105 motion_key_x = KSYM_UNDEFINED;
1106 motion_key_y = KSYM_UNDEFINED;
1108 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1111 else if (button == MB_RELEASED && touched)
1118 if (motion_key_x != KSYM_UNDEFINED)
1119 HandleKey(motion_key_x, KEY_RELEASED);
1120 if (motion_key_y != KSYM_UNDEFINED)
1121 HandleKey(motion_key_y, KEY_RELEASED);
1123 if (started_on_player)
1125 if (player_is_dropping)
1127 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1129 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1133 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1135 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1139 motion_key_x = KSYM_UNDEFINED;
1140 motion_key_y = KSYM_UNDEFINED;
1142 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1147 int src_x = local_player->jx;
1148 int src_y = local_player->jy;
1149 int dst_x = getLevelFromScreenX(old_mx);
1150 int dst_y = getLevelFromScreenY(old_my);
1151 int dx = dst_x - src_x;
1152 int dy = dst_y - src_y;
1153 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1154 dx > 0 ? setup.input[0].key.right :
1156 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1157 dy > 0 ? setup.input[0].key.down :
1160 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1161 (last_player_x != local_player->jx ||
1162 last_player_y != local_player->jy))
1164 // in case of asymmetric diagonal movement, use "preferred" direction
1166 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1168 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1169 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1171 local_player->last_move_dir = last_move_dir;
1173 // (required to prevent accidentally forcing direction for next movement)
1174 last_player_x = local_player->jx;
1175 last_player_y = local_player->jy;
1178 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1180 started_on_player = TRUE;
1181 player_drop_count = getPlayerInventorySize(0);
1182 player_is_dropping = (player_drop_count > 0);
1184 if (player_is_dropping)
1186 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1188 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1192 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1194 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1197 else if (dx != 0 || dy != 0)
1199 if (player_is_dropping &&
1200 player_drop_count == getPlayerInventorySize(0))
1202 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1204 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1205 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1207 player_is_dropping = FALSE;
1211 if (new_motion_key_x != motion_key_x)
1213 Error(ERR_DEBUG, "---------- %s %s ----------",
1214 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1215 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1217 if (motion_key_x != KSYM_UNDEFINED)
1218 HandleKey(motion_key_x, KEY_RELEASED);
1219 if (new_motion_key_x != KSYM_UNDEFINED)
1220 HandleKey(new_motion_key_x, KEY_PRESSED);
1223 if (new_motion_key_y != motion_key_y)
1225 Error(ERR_DEBUG, "---------- %s %s ----------",
1226 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1227 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1229 if (motion_key_y != KSYM_UNDEFINED)
1230 HandleKey(motion_key_y, KEY_RELEASED);
1231 if (new_motion_key_y != KSYM_UNDEFINED)
1232 HandleKey(new_motion_key_y, KEY_PRESSED);
1235 motion_key_x = new_motion_key_x;
1236 motion_key_y = new_motion_key_y;
1240 #if defined(TARGET_SDL2)
1242 static boolean checkTextInputKeyModState()
1244 // when playing, only handle raw key events and ignore text input
1245 if (game_status == GAME_MODE_PLAYING)
1248 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1251 void HandleTextEvent(TextEvent *event)
1253 char *text = event->text;
1254 Key key = getKeyFromKeyName(text);
1256 #if DEBUG_EVENTS_TEXT
1257 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1260 text[0], (int)(text[0]),
1262 getKeyNameFromKey(key),
1266 #if !defined(HAS_SCREEN_KEYBOARD)
1267 // non-mobile devices: only handle key input with modifier keys pressed here
1268 // (every other key input is handled directly as physical key input event)
1269 if (!checkTextInputKeyModState())
1273 // process text input as "classic" (with uppercase etc.) key input event
1274 HandleKey(key, KEY_PRESSED);
1275 HandleKey(key, KEY_RELEASED);
1278 void HandlePauseResumeEvent(PauseResumeEvent *event)
1280 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1284 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1292 void HandleKeyEvent(KeyEvent *event)
1294 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1295 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1296 Key key = GetEventKey(event, with_modifiers);
1297 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1299 #if DEBUG_EVENTS_KEY
1300 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1301 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1302 event->keysym.scancode,
1307 getKeyNameFromKey(key));
1310 #if defined(PLATFORM_ANDROID)
1311 if (key == KSYM_Back)
1313 // always map the "back" button to the "escape" key on Android devices
1318 // for any key event other than "back" button, disable overlay buttons
1319 SetOverlayEnabled(FALSE);
1323 HandleKeyModState(keymod, key_status);
1325 #if defined(TARGET_SDL2)
1326 // only handle raw key input without text modifier keys pressed
1327 if (!checkTextInputKeyModState())
1328 HandleKey(key, key_status);
1330 HandleKey(key, key_status);
1334 void HandleFocusEvent(FocusChangeEvent *event)
1336 static int old_joystick_status = -1;
1338 if (event->type == EVENT_FOCUSOUT)
1340 KeyboardAutoRepeatOn();
1341 old_joystick_status = joystick.status;
1342 joystick.status = JOYSTICK_NOT_AVAILABLE;
1344 ClearPlayerAction();
1346 else if (event->type == EVENT_FOCUSIN)
1348 /* When there are two Rocks'n'Diamonds windows which overlap and
1349 the player moves the pointer from one game window to the other,
1350 a 'FocusOut' event is generated for the window the pointer is
1351 leaving and a 'FocusIn' event is generated for the window the
1352 pointer is entering. In some cases, it can happen that the
1353 'FocusIn' event is handled by the one game process before the
1354 'FocusOut' event by the other game process. In this case the
1355 X11 environment would end up with activated keyboard auto repeat,
1356 because unfortunately this is a global setting and not (which
1357 would be far better) set for each X11 window individually.
1358 The effect would be keyboard auto repeat while playing the game
1359 (game_status == GAME_MODE_PLAYING), which is not desired.
1360 To avoid this special case, we just wait 1/10 second before
1361 processing the 'FocusIn' event.
1364 if (game_status == GAME_MODE_PLAYING)
1367 KeyboardAutoRepeatOffUnlessAutoplay();
1370 if (old_joystick_status != -1)
1371 joystick.status = old_joystick_status;
1375 void HandleClientMessageEvent(ClientMessageEvent *event)
1377 if (CheckCloseWindowEvent(event))
1381 void HandleWindowManagerEvent(Event *event)
1383 #if defined(TARGET_SDL)
1384 SDLHandleWindowManagerEvent(event);
1388 void HandleButton(int mx, int my, int button, int button_nr)
1390 static int old_mx = 0, old_my = 0;
1391 boolean button_hold = FALSE;
1392 boolean handle_gadgets = TRUE;
1398 button_nr = -button_nr;
1407 #if defined(PLATFORM_ANDROID)
1408 // when playing, only handle gadgets when using "follow finger" controls
1409 // or when using touch controls in combination with the MM game engine
1411 (game_status != GAME_MODE_PLAYING ||
1412 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1413 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1416 if (handle_gadgets && HandleGadgets(mx, my, button))
1418 /* do not handle this button event anymore */
1419 mx = my = -32; /* force mouse event to be outside screen tiles */
1422 if (HandleGlobalAnimClicks(mx, my, button))
1424 /* do not handle this button event anymore */
1425 return; /* force mouse event not to be handled at all */
1428 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1431 /* do not use scroll wheel button events for anything other than gadgets */
1432 if (IS_WHEEL_BUTTON(button_nr))
1435 switch (game_status)
1437 case GAME_MODE_TITLE:
1438 HandleTitleScreen(mx, my, 0, 0, button);
1441 case GAME_MODE_MAIN:
1442 HandleMainMenu(mx, my, 0, 0, button);
1445 case GAME_MODE_PSEUDO_TYPENAME:
1446 HandleTypeName(0, KSYM_Return);
1449 case GAME_MODE_LEVELS:
1450 HandleChooseLevelSet(mx, my, 0, 0, button);
1453 case GAME_MODE_LEVELNR:
1454 HandleChooseLevelNr(mx, my, 0, 0, button);
1457 case GAME_MODE_SCORES:
1458 HandleHallOfFame(0, 0, 0, 0, button);
1461 case GAME_MODE_EDITOR:
1462 HandleLevelEditorIdle();
1465 case GAME_MODE_INFO:
1466 HandleInfoScreen(mx, my, 0, 0, button);
1469 case GAME_MODE_SETUP:
1470 HandleSetupScreen(mx, my, 0, 0, button);
1473 case GAME_MODE_PLAYING:
1474 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1475 HandleButtonOrFinger(mx, my, button);
1477 SetPlayerMouseAction(mx, my, button);
1480 if (button == MB_PRESSED && !motion_status && !button_hold &&
1481 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1482 DumpTileFromScreen(mx, my);
1492 static boolean is_string_suffix(char *string, char *suffix)
1494 int string_len = strlen(string);
1495 int suffix_len = strlen(suffix);
1497 if (suffix_len > string_len)
1500 return (strEqual(&string[string_len - suffix_len], suffix));
1503 #define MAX_CHEAT_INPUT_LEN 32
1505 static void HandleKeysSpecial(Key key)
1507 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1508 char letter = getCharFromKey(key);
1509 int cheat_input_len = strlen(cheat_input);
1515 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1517 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1518 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1520 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1523 cheat_input[cheat_input_len++] = letter;
1524 cheat_input[cheat_input_len] = '\0';
1526 #if DEBUG_EVENTS_KEY
1527 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1530 if (game_status == GAME_MODE_MAIN)
1532 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1533 is_string_suffix(cheat_input, ":ist"))
1535 InsertSolutionTape();
1537 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1538 is_string_suffix(cheat_input, ":rg"))
1540 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1543 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1544 is_string_suffix(cheat_input, ":rs"))
1546 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1549 else if (is_string_suffix(cheat_input, ":reload-music") ||
1550 is_string_suffix(cheat_input, ":rm"))
1552 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1555 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1556 is_string_suffix(cheat_input, ":ra"))
1558 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1559 1 << ARTWORK_TYPE_SOUNDS |
1560 1 << ARTWORK_TYPE_MUSIC);
1563 else if (is_string_suffix(cheat_input, ":dump-level") ||
1564 is_string_suffix(cheat_input, ":dl"))
1568 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1569 is_string_suffix(cheat_input, ":dt"))
1573 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1574 is_string_suffix(cheat_input, ":ft"))
1576 /* fix single-player tapes that contain player input for more than one
1577 player (due to a bug in 3.3.1.2 and earlier versions), which results
1578 in playing levels with more than one player in multi-player mode,
1579 even though the tape was originally recorded in single-player mode */
1581 /* remove player input actions for all players but the first one */
1582 for (i = 1; i < MAX_PLAYERS; i++)
1583 tape.player_participates[i] = FALSE;
1585 tape.changed = TRUE;
1587 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1588 is_string_suffix(cheat_input, ":snl"))
1590 SaveNativeLevel(&level);
1592 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1593 is_string_suffix(cheat_input, ":fps"))
1595 global.show_frames_per_second = !global.show_frames_per_second;
1598 else if (game_status == GAME_MODE_PLAYING)
1601 if (is_string_suffix(cheat_input, ".q"))
1602 DEBUG_SetMaximumDynamite();
1605 else if (game_status == GAME_MODE_EDITOR)
1607 if (is_string_suffix(cheat_input, ":dump-brush") ||
1608 is_string_suffix(cheat_input, ":DB"))
1612 else if (is_string_suffix(cheat_input, ":DDB"))
1619 void HandleKeysDebug(Key key)
1624 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1626 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1628 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1630 if (key == setup.debug.frame_delay_key[i] &&
1631 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1633 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1634 setup.debug.frame_delay[i] : setup.game_frame_delay);
1636 if (!setup.debug.frame_delay_game_only)
1637 MenuFrameDelay = GameFrameDelay;
1639 SetVideoFrameDelay(GameFrameDelay);
1641 if (GameFrameDelay > ONE_SECOND_DELAY)
1642 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1643 else if (GameFrameDelay != 0)
1644 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1645 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1646 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1648 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1655 if (game_status == GAME_MODE_PLAYING)
1659 options.debug = !options.debug;
1661 Error(ERR_DEBUG, "debug mode %s",
1662 (options.debug ? "enabled" : "disabled"));
1664 else if (key == KSYM_v)
1666 Error(ERR_DEBUG, "currently using game engine version %d",
1667 game.engine_version);
1673 void HandleKey(Key key, int key_status)
1675 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1676 static boolean ignore_repeated_key = FALSE;
1677 static struct SetupKeyboardInfo ski;
1678 static struct SetupShortcutInfo ssi;
1687 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1688 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1689 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1690 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1691 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1692 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1697 #if defined(TARGET_SDL2)
1698 /* map special keys (media keys / remote control buttons) to default keys */
1699 if (key == KSYM_PlayPause)
1701 else if (key == KSYM_Select)
1705 HandleSpecialGameControllerKeys(key, key_status);
1707 if (game_status == GAME_MODE_PLAYING)
1709 /* only needed for single-step tape recording mode */
1710 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1713 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1715 byte key_action = 0;
1717 if (setup.input[pnr].use_joystick)
1720 ski = setup.input[pnr].key;
1722 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1723 if (key == *key_info[i].key_custom)
1724 key_action |= key_info[i].action;
1726 /* use combined snap+direction keys for the first player only */
1729 ssi = setup.shortcut;
1731 for (i = 0; i < NUM_DIRECTIONS; i++)
1732 if (key == *key_info[i].key_snap)
1733 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1736 if (key_status == KEY_PRESSED)
1737 stored_player[pnr].action |= key_action;
1739 stored_player[pnr].action &= ~key_action;
1741 if (tape.single_step && tape.recording && tape.pausing)
1743 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1745 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1747 /* if snap key already pressed, keep pause mode when releasing */
1748 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1749 has_snapped[pnr] = TRUE;
1751 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1753 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1755 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1756 getRedDiskReleaseFlag_SP() == 0)
1758 /* add a single inactive frame before dropping starts */
1759 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1760 stored_player[pnr].force_dropping = TRUE;
1763 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1765 /* if snap key was pressed without direction, leave pause mode */
1766 if (!has_snapped[pnr])
1767 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1769 has_snapped[pnr] = FALSE;
1772 else if (tape.recording && tape.pausing && !tape.use_mouse)
1774 /* prevent key release events from un-pausing a paused game */
1775 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1776 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1782 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1783 if (key == key_info[i].key_default)
1784 joy |= key_info[i].action;
1789 if (key_status == KEY_PRESSED)
1790 key_joystick_mapping |= joy;
1792 key_joystick_mapping &= ~joy;
1797 if (game_status != GAME_MODE_PLAYING)
1798 key_joystick_mapping = 0;
1800 if (key_status == KEY_RELEASED)
1802 // reset flag to ignore repeated "key pressed" events after key release
1803 ignore_repeated_key = FALSE;
1808 if ((key == KSYM_F11 ||
1809 ((key == KSYM_Return ||
1810 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1811 video.fullscreen_available &&
1812 !ignore_repeated_key)
1814 setup.fullscreen = !setup.fullscreen;
1816 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1818 if (game_status == GAME_MODE_SETUP)
1819 RedrawSetupScreenAfterFullscreenToggle();
1821 // set flag to ignore repeated "key pressed" events
1822 ignore_repeated_key = TRUE;
1827 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1828 key == KSYM_minus || key == KSYM_KP_Subtract ||
1829 key == KSYM_plus || key == KSYM_KP_Add ||
1830 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1831 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1832 video.window_scaling_available &&
1833 !video.fullscreen_enabled)
1835 if (key == KSYM_0 || key == KSYM_KP_0)
1836 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1837 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1838 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1840 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1842 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1843 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1844 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1845 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1847 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1849 if (game_status == GAME_MODE_SETUP)
1850 RedrawSetupScreenAfterFullscreenToggle();
1855 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1856 key == KSYM_Return ||
1857 key == KSYM_Escape)))
1859 /* do not handle this key event anymore */
1860 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1864 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1865 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1872 if (game_status == GAME_MODE_MAIN &&
1873 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1875 StartGameActions(options.network, setup.autorecord, level.random_seed);
1880 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1882 if (key == setup.shortcut.save_game)
1884 else if (key == setup.shortcut.load_game)
1886 else if (key == setup.shortcut.toggle_pause)
1887 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1889 HandleTapeButtonKeys(key);
1890 HandleSoundButtonKeys(key);
1893 if (game_status == GAME_MODE_PLAYING && !network_playing)
1895 int centered_player_nr_next = -999;
1897 if (key == setup.shortcut.focus_player_all)
1898 centered_player_nr_next = -1;
1900 for (i = 0; i < MAX_PLAYERS; i++)
1901 if (key == setup.shortcut.focus_player[i])
1902 centered_player_nr_next = i;
1904 if (centered_player_nr_next != -999)
1906 game.centered_player_nr_next = centered_player_nr_next;
1907 game.set_centered_player = TRUE;
1911 tape.centered_player_nr_next = game.centered_player_nr_next;
1912 tape.set_centered_player = TRUE;
1917 HandleKeysSpecial(key);
1919 if (HandleGadgetsKeyInput(key))
1921 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1922 key = KSYM_UNDEFINED;
1925 switch (game_status)
1927 case GAME_MODE_PSEUDO_TYPENAME:
1928 HandleTypeName(0, key);
1931 case GAME_MODE_TITLE:
1932 case GAME_MODE_MAIN:
1933 case GAME_MODE_LEVELS:
1934 case GAME_MODE_LEVELNR:
1935 case GAME_MODE_SETUP:
1936 case GAME_MODE_INFO:
1937 case GAME_MODE_SCORES:
1942 if (game_status == GAME_MODE_TITLE)
1943 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1944 else if (game_status == GAME_MODE_MAIN)
1945 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1946 else if (game_status == GAME_MODE_LEVELS)
1947 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1948 else if (game_status == GAME_MODE_LEVELNR)
1949 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1950 else if (game_status == GAME_MODE_SETUP)
1951 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1952 else if (game_status == GAME_MODE_INFO)
1953 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1954 else if (game_status == GAME_MODE_SCORES)
1955 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1959 if (game_status != GAME_MODE_MAIN)
1960 FadeSkipNextFadeIn();
1962 if (game_status == GAME_MODE_TITLE)
1963 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1964 else if (game_status == GAME_MODE_LEVELS)
1965 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1966 else if (game_status == GAME_MODE_LEVELNR)
1967 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1968 else if (game_status == GAME_MODE_SETUP)
1969 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1970 else if (game_status == GAME_MODE_INFO)
1971 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1972 else if (game_status == GAME_MODE_SCORES)
1973 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1977 if (game_status == GAME_MODE_LEVELS)
1978 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1979 else if (game_status == GAME_MODE_LEVELNR)
1980 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1981 else if (game_status == GAME_MODE_SETUP)
1982 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1983 else if (game_status == GAME_MODE_INFO)
1984 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1985 else if (game_status == GAME_MODE_SCORES)
1986 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1989 case KSYM_Page_Down:
1990 if (game_status == GAME_MODE_LEVELS)
1991 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1992 else if (game_status == GAME_MODE_LEVELNR)
1993 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1994 else if (game_status == GAME_MODE_SETUP)
1995 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1996 else if (game_status == GAME_MODE_INFO)
1997 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1998 else if (game_status == GAME_MODE_SCORES)
1999 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2007 case GAME_MODE_EDITOR:
2008 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2009 HandleLevelEditorKeyInput(key);
2012 case GAME_MODE_PLAYING:
2017 RequestQuitGame(setup.ask_on_escape);
2027 if (key == KSYM_Escape)
2029 SetGameStatus(GAME_MODE_MAIN);
2037 HandleKeysDebug(key);
2040 void HandleNoEvent()
2042 HandleMouseCursor();
2044 switch (game_status)
2046 case GAME_MODE_PLAYING:
2047 HandleButtonOrFinger(-1, -1, -1);
2052 void HandleEventActions()
2054 // if (button_status && game_status != GAME_MODE_PLAYING)
2055 if (button_status && (game_status != GAME_MODE_PLAYING ||
2057 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2059 HandleButton(0, 0, button_status, -button_status);
2066 #if defined(NETWORK_AVALIABLE)
2067 if (options.network)
2071 switch (game_status)
2073 case GAME_MODE_MAIN:
2074 DrawPreviewLevelAnimation();
2077 case GAME_MODE_EDITOR:
2078 HandleLevelEditorIdle();
2086 static int HandleJoystickForAllPlayers()
2090 boolean no_joysticks_configured = TRUE;
2091 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2092 static byte joy_action_last[MAX_PLAYERS];
2094 for (i = 0; i < MAX_PLAYERS; i++)
2095 if (setup.input[i].use_joystick)
2096 no_joysticks_configured = FALSE;
2098 /* if no joysticks configured, map connected joysticks to players */
2099 if (no_joysticks_configured)
2100 use_as_joystick_nr = TRUE;
2102 for (i = 0; i < MAX_PLAYERS; i++)
2104 byte joy_action = 0;
2106 joy_action = JoystickExt(i, use_as_joystick_nr);
2107 result |= joy_action;
2109 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2110 joy_action != joy_action_last[i])
2111 stored_player[i].action = joy_action;
2113 joy_action_last[i] = joy_action;
2119 void HandleJoystick()
2121 int joystick = HandleJoystickForAllPlayers();
2122 int keyboard = key_joystick_mapping;
2123 int joy = (joystick | keyboard);
2124 int left = joy & JOY_LEFT;
2125 int right = joy & JOY_RIGHT;
2126 int up = joy & JOY_UP;
2127 int down = joy & JOY_DOWN;
2128 int button = joy & JOY_BUTTON;
2129 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2130 int dx = (left ? -1 : right ? 1 : 0);
2131 int dy = (up ? -1 : down ? 1 : 0);
2133 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2135 /* do not handle this button event anymore */
2139 switch (game_status)
2141 case GAME_MODE_TITLE:
2142 case GAME_MODE_MAIN:
2143 case GAME_MODE_LEVELS:
2144 case GAME_MODE_LEVELNR:
2145 case GAME_MODE_SETUP:
2146 case GAME_MODE_INFO:
2148 static unsigned int joystickmove_delay = 0;
2149 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2150 static int joystick_last = 0;
2152 if (joystick && !button &&
2153 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2155 /* delay joystick actions if buttons/axes continually pressed */
2156 newbutton = dx = dy = 0;
2160 /* start with longer delay, then continue with shorter delay */
2161 if (joystick != joystick_last)
2162 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2164 joystickmove_delay_value = GADGET_FRAME_DELAY;
2167 if (game_status == GAME_MODE_TITLE)
2168 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2169 else if (game_status == GAME_MODE_MAIN)
2170 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2171 else if (game_status == GAME_MODE_LEVELS)
2172 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2173 else if (game_status == GAME_MODE_LEVELNR)
2174 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2175 else if (game_status == GAME_MODE_SETUP)
2176 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2177 else if (game_status == GAME_MODE_INFO)
2178 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2180 joystick_last = joystick;
2185 case GAME_MODE_SCORES:
2186 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2189 case GAME_MODE_PLAYING:
2190 if (tape.playing || keyboard)
2191 newbutton = ((joy & JOY_BUTTON) != 0);
2193 if (newbutton && AllPlayersGone)
2200 if (tape.recording && tape.pausing)
2202 if (joystick & JOY_ACTION)
2203 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2213 void HandleSpecialGameControllerButtons(Event *event)
2215 #if defined(TARGET_SDL2)
2216 switch (event->type)
2218 case SDL_CONTROLLERBUTTONDOWN:
2219 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2220 HandleKey(KSYM_space, KEY_PRESSED);
2221 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2222 HandleKey(KSYM_Escape, KEY_PRESSED);
2226 case SDL_CONTROLLERBUTTONUP:
2227 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2228 HandleKey(KSYM_space, KEY_RELEASED);
2229 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2230 HandleKey(KSYM_Escape, KEY_RELEASED);
2237 void HandleSpecialGameControllerKeys(Key key, int key_status)
2239 #if defined(TARGET_SDL2)
2240 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2241 int button = SDL_CONTROLLER_BUTTON_INVALID;
2243 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2244 if (key == KSYM_Rewind)
2245 button = SDL_CONTROLLER_BUTTON_A;
2246 else if (key == KSYM_FastForward || key == KSYM_Menu)
2247 button = SDL_CONTROLLER_BUTTON_B;
2249 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2253 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2254 SDL_CONTROLLERBUTTONUP);
2256 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2257 event.cbutton.button = button;
2258 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2261 HandleJoystickEvent(&event);