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_VirtualButtons(FingerEvent *event)
651 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
652 float event_x = (event->x);
653 float event_y = (event->y - ypos) / (1 - ypos);
654 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
655 event_y > 2.0 / 3.0 && event_y < 1 ?
656 setup.input[0].key.snap :
657 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
658 event_y > 2.0 / 3.0 && event_y < 1 ?
659 setup.input[0].key.drop :
660 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
661 event_y > 0 && event_y < 1.0 / 3.0 ?
662 setup.input[0].key.up :
663 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
664 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
665 setup.input[0].key.left :
666 event_x > 8.0 / 9.0 && event_x < 1 &&
667 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
668 setup.input[0].key.right :
669 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
670 event_y > 2.0 / 3.0 && event_y < 1 ?
671 setup.input[0].key.down :
673 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
675 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
679 // for any touch input event, enable overlay buttons (if activated)
680 SetOverlayEnabled(TRUE);
682 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
683 getKeyNameFromKey(key), key_status_name, event->fingerId);
685 // check if we already know this touch event's finger id
686 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
688 if (touch_info[i].touched &&
689 touch_info[i].finger_id == event->fingerId)
691 // Error(ERR_DEBUG, "MARK 1: %d", i);
697 if (i >= NUM_TOUCH_FINGERS)
699 if (key_status == KEY_PRESSED)
701 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
703 // unknown finger id -- get new, empty slot, if available
704 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
706 if (touch_info[i].counter < oldest_counter)
709 oldest_counter = touch_info[i].counter;
711 // Error(ERR_DEBUG, "MARK 2: %d", i);
714 if (!touch_info[i].touched)
716 // Error(ERR_DEBUG, "MARK 3: %d", i);
722 if (i >= NUM_TOUCH_FINGERS)
724 // all slots allocated -- use oldest slot
727 // Error(ERR_DEBUG, "MARK 4: %d", i);
732 // release of previously unknown key (should not happen)
734 if (key != KSYM_UNDEFINED)
736 HandleKey(key, KEY_RELEASED);
738 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
739 getKeyNameFromKey(key), "KEY_RELEASED", i);
744 if (i < NUM_TOUCH_FINGERS)
746 if (key_status == KEY_PRESSED)
748 if (touch_info[i].key != key)
750 if (touch_info[i].key != KSYM_UNDEFINED)
752 HandleKey(touch_info[i].key, KEY_RELEASED);
754 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
755 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
758 if (key != KSYM_UNDEFINED)
760 HandleKey(key, KEY_PRESSED);
762 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
763 getKeyNameFromKey(key), "KEY_PRESSED", i);
767 touch_info[i].touched = TRUE;
768 touch_info[i].finger_id = event->fingerId;
769 touch_info[i].counter = Counter();
770 touch_info[i].key = key;
774 if (touch_info[i].key != KSYM_UNDEFINED)
776 HandleKey(touch_info[i].key, KEY_RELEASED);
778 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
779 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
782 touch_info[i].touched = FALSE;
783 touch_info[i].finger_id = 0;
784 touch_info[i].counter = 0;
785 touch_info[i].key = 0;
790 void HandleFingerEvent_WipeGestures(FingerEvent *event)
792 static Key motion_key_x = KSYM_UNDEFINED;
793 static Key motion_key_y = KSYM_UNDEFINED;
794 static Key button_key = KSYM_UNDEFINED;
795 static float motion_x1, motion_y1;
796 static float button_x1, button_y1;
797 static SDL_FingerID motion_id = -1;
798 static SDL_FingerID button_id = -1;
799 int move_trigger_distance_percent = setup.touch.move_distance;
800 int drop_trigger_distance_percent = setup.touch.drop_distance;
801 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
802 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
803 float event_x = event->x;
804 float event_y = event->y;
806 if (event->type == EVENT_FINGERPRESS)
808 if (event_x > 1.0 / 3.0)
812 motion_id = event->fingerId;
817 motion_key_x = KSYM_UNDEFINED;
818 motion_key_y = KSYM_UNDEFINED;
820 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
826 button_id = event->fingerId;
831 button_key = setup.input[0].key.snap;
833 HandleKey(button_key, KEY_PRESSED);
835 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
838 else if (event->type == EVENT_FINGERRELEASE)
840 if (event->fingerId == motion_id)
844 if (motion_key_x != KSYM_UNDEFINED)
845 HandleKey(motion_key_x, KEY_RELEASED);
846 if (motion_key_y != KSYM_UNDEFINED)
847 HandleKey(motion_key_y, KEY_RELEASED);
849 motion_key_x = KSYM_UNDEFINED;
850 motion_key_y = KSYM_UNDEFINED;
852 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
854 else if (event->fingerId == button_id)
858 if (button_key != KSYM_UNDEFINED)
859 HandleKey(button_key, KEY_RELEASED);
861 button_key = KSYM_UNDEFINED;
863 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
866 else if (event->type == EVENT_FINGERMOTION)
868 if (event->fingerId == motion_id)
870 float distance_x = ABS(event_x - motion_x1);
871 float distance_y = ABS(event_y - motion_y1);
872 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
873 event_x > motion_x1 ? setup.input[0].key.right :
875 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
876 event_y > motion_y1 ? setup.input[0].key.down :
879 if (distance_x < move_trigger_distance / 2 ||
880 distance_x < distance_y)
881 new_motion_key_x = KSYM_UNDEFINED;
883 if (distance_y < move_trigger_distance / 2 ||
884 distance_y < distance_x)
885 new_motion_key_y = KSYM_UNDEFINED;
887 if (distance_x > move_trigger_distance ||
888 distance_y > move_trigger_distance)
890 if (new_motion_key_x != motion_key_x)
892 if (motion_key_x != KSYM_UNDEFINED)
893 HandleKey(motion_key_x, KEY_RELEASED);
894 if (new_motion_key_x != KSYM_UNDEFINED)
895 HandleKey(new_motion_key_x, KEY_PRESSED);
898 if (new_motion_key_y != motion_key_y)
900 if (motion_key_y != KSYM_UNDEFINED)
901 HandleKey(motion_key_y, KEY_RELEASED);
902 if (new_motion_key_y != KSYM_UNDEFINED)
903 HandleKey(new_motion_key_y, KEY_PRESSED);
909 motion_key_x = new_motion_key_x;
910 motion_key_y = new_motion_key_y;
912 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
915 else if (event->fingerId == button_id)
917 float distance_x = ABS(event_x - button_x1);
918 float distance_y = ABS(event_y - button_y1);
920 if (distance_x < drop_trigger_distance / 2 &&
921 distance_y > drop_trigger_distance)
923 if (button_key == setup.input[0].key.snap)
924 HandleKey(button_key, KEY_RELEASED);
929 button_key = setup.input[0].key.drop;
931 HandleKey(button_key, KEY_PRESSED);
933 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
939 void HandleFingerEvent(FingerEvent *event)
941 #if DEBUG_EVENTS_FINGER
942 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
943 event->type == EVENT_FINGERPRESS ? "pressed" :
944 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
948 event->dx, event->dy,
952 if (game_status != GAME_MODE_PLAYING)
955 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
958 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
959 HandleFingerEvent_VirtualButtons(event);
960 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
961 HandleFingerEvent_WipeGestures(event);
966 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
968 static int old_mx = 0, old_my = 0;
969 static int last_button = MB_LEFTBUTTON;
970 static boolean touched = FALSE;
971 static boolean tapped = FALSE;
973 // screen tile was tapped (but finger not touching the screen anymore)
974 // (this point will also be reached without receiving a touch event)
975 if (tapped && !touched)
977 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
982 // stop here if this function was not triggered by a touch event
986 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
988 // finger started touching the screen
998 ClearPlayerMouseAction();
1000 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1003 else if (button == MB_RELEASED && touched)
1005 // finger stopped touching the screen
1010 SetPlayerMouseAction(old_mx, old_my, last_button);
1012 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1014 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1019 // finger moved while touching the screen
1021 int old_x = getLevelFromScreenX(old_mx);
1022 int old_y = getLevelFromScreenY(old_my);
1023 int new_x = getLevelFromScreenX(mx);
1024 int new_y = getLevelFromScreenY(my);
1026 if (new_x != old_x || new_y != old_y)
1031 // finger moved left or right from (horizontal) starting position
1033 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1035 SetPlayerMouseAction(old_mx, old_my, button_nr);
1037 last_button = button_nr;
1039 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1043 // finger stays at or returned to (horizontal) starting position
1045 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1047 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1052 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1054 // (not implemented yet)
1057 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1059 static int old_mx = 0, old_my = 0;
1060 static Key motion_key_x = KSYM_UNDEFINED;
1061 static Key motion_key_y = KSYM_UNDEFINED;
1062 static boolean touched = FALSE;
1063 static boolean started_on_player = FALSE;
1064 static boolean player_is_dropping = FALSE;
1065 static int player_drop_count = 0;
1066 static int last_player_x = -1;
1067 static int last_player_y = -1;
1069 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1078 started_on_player = FALSE;
1079 player_is_dropping = FALSE;
1080 player_drop_count = 0;
1084 motion_key_x = KSYM_UNDEFINED;
1085 motion_key_y = KSYM_UNDEFINED;
1087 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1090 else if (button == MB_RELEASED && touched)
1097 if (motion_key_x != KSYM_UNDEFINED)
1098 HandleKey(motion_key_x, KEY_RELEASED);
1099 if (motion_key_y != KSYM_UNDEFINED)
1100 HandleKey(motion_key_y, KEY_RELEASED);
1102 if (started_on_player)
1104 if (player_is_dropping)
1106 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1108 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1112 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1114 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1118 motion_key_x = KSYM_UNDEFINED;
1119 motion_key_y = KSYM_UNDEFINED;
1121 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1126 int src_x = local_player->jx;
1127 int src_y = local_player->jy;
1128 int dst_x = getLevelFromScreenX(old_mx);
1129 int dst_y = getLevelFromScreenY(old_my);
1130 int dx = dst_x - src_x;
1131 int dy = dst_y - src_y;
1132 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1133 dx > 0 ? setup.input[0].key.right :
1135 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1136 dy > 0 ? setup.input[0].key.down :
1139 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1140 (last_player_x != local_player->jx ||
1141 last_player_y != local_player->jy))
1143 // in case of asymmetric diagonal movement, use "preferred" direction
1145 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1147 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1148 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1150 local_player->last_move_dir = last_move_dir;
1152 // (required to prevent accidentally forcing direction for next movement)
1153 last_player_x = local_player->jx;
1154 last_player_y = local_player->jy;
1157 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1159 started_on_player = TRUE;
1160 player_drop_count = getPlayerInventorySize(0);
1161 player_is_dropping = (player_drop_count > 0);
1163 if (player_is_dropping)
1165 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1167 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1171 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1173 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1176 else if (dx != 0 || dy != 0)
1178 if (player_is_dropping &&
1179 player_drop_count == getPlayerInventorySize(0))
1181 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1183 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1184 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1186 player_is_dropping = FALSE;
1190 if (new_motion_key_x != motion_key_x)
1192 Error(ERR_DEBUG, "---------- %s %s ----------",
1193 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1194 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1196 if (motion_key_x != KSYM_UNDEFINED)
1197 HandleKey(motion_key_x, KEY_RELEASED);
1198 if (new_motion_key_x != KSYM_UNDEFINED)
1199 HandleKey(new_motion_key_x, KEY_PRESSED);
1202 if (new_motion_key_y != motion_key_y)
1204 Error(ERR_DEBUG, "---------- %s %s ----------",
1205 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1206 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1208 if (motion_key_y != KSYM_UNDEFINED)
1209 HandleKey(motion_key_y, KEY_RELEASED);
1210 if (new_motion_key_y != KSYM_UNDEFINED)
1211 HandleKey(new_motion_key_y, KEY_PRESSED);
1214 motion_key_x = new_motion_key_x;
1215 motion_key_y = new_motion_key_y;
1219 static void HandleButtonOrFinger(int mx, int my, int button)
1221 if (game_status != GAME_MODE_PLAYING)
1224 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1226 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1227 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1228 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1229 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1233 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1234 HandleButtonOrFinger_FollowFinger(mx, my, button);
1238 #if defined(TARGET_SDL2)
1240 static boolean checkTextInputKeyModState()
1242 // when playing, only handle raw key events and ignore text input
1243 if (game_status == GAME_MODE_PLAYING)
1246 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1249 void HandleTextEvent(TextEvent *event)
1251 char *text = event->text;
1252 Key key = getKeyFromKeyName(text);
1254 #if DEBUG_EVENTS_TEXT
1255 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1258 text[0], (int)(text[0]),
1260 getKeyNameFromKey(key),
1264 #if !defined(HAS_SCREEN_KEYBOARD)
1265 // non-mobile devices: only handle key input with modifier keys pressed here
1266 // (every other key input is handled directly as physical key input event)
1267 if (!checkTextInputKeyModState())
1271 // process text input as "classic" (with uppercase etc.) key input event
1272 HandleKey(key, KEY_PRESSED);
1273 HandleKey(key, KEY_RELEASED);
1276 void HandlePauseResumeEvent(PauseResumeEvent *event)
1278 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1282 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1290 void HandleKeyEvent(KeyEvent *event)
1292 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1293 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1294 Key key = GetEventKey(event, with_modifiers);
1295 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1297 #if DEBUG_EVENTS_KEY
1298 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1299 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1300 event->keysym.scancode,
1305 getKeyNameFromKey(key));
1308 #if defined(PLATFORM_ANDROID)
1309 if (key == KSYM_Back)
1311 // always map the "back" button to the "escape" key on Android devices
1316 // for any key event other than "back" button, disable overlay buttons
1317 SetOverlayEnabled(FALSE);
1321 HandleKeyModState(keymod, key_status);
1323 #if defined(TARGET_SDL2)
1324 // only handle raw key input without text modifier keys pressed
1325 if (!checkTextInputKeyModState())
1326 HandleKey(key, key_status);
1328 HandleKey(key, key_status);
1332 void HandleFocusEvent(FocusChangeEvent *event)
1334 static int old_joystick_status = -1;
1336 if (event->type == EVENT_FOCUSOUT)
1338 KeyboardAutoRepeatOn();
1339 old_joystick_status = joystick.status;
1340 joystick.status = JOYSTICK_NOT_AVAILABLE;
1342 ClearPlayerAction();
1344 else if (event->type == EVENT_FOCUSIN)
1346 /* When there are two Rocks'n'Diamonds windows which overlap and
1347 the player moves the pointer from one game window to the other,
1348 a 'FocusOut' event is generated for the window the pointer is
1349 leaving and a 'FocusIn' event is generated for the window the
1350 pointer is entering. In some cases, it can happen that the
1351 'FocusIn' event is handled by the one game process before the
1352 'FocusOut' event by the other game process. In this case the
1353 X11 environment would end up with activated keyboard auto repeat,
1354 because unfortunately this is a global setting and not (which
1355 would be far better) set for each X11 window individually.
1356 The effect would be keyboard auto repeat while playing the game
1357 (game_status == GAME_MODE_PLAYING), which is not desired.
1358 To avoid this special case, we just wait 1/10 second before
1359 processing the 'FocusIn' event.
1362 if (game_status == GAME_MODE_PLAYING)
1365 KeyboardAutoRepeatOffUnlessAutoplay();
1368 if (old_joystick_status != -1)
1369 joystick.status = old_joystick_status;
1373 void HandleClientMessageEvent(ClientMessageEvent *event)
1375 if (CheckCloseWindowEvent(event))
1379 void HandleWindowManagerEvent(Event *event)
1381 #if defined(TARGET_SDL)
1382 SDLHandleWindowManagerEvent(event);
1386 void HandleButton(int mx, int my, int button, int button_nr)
1388 static int old_mx = 0, old_my = 0;
1389 boolean button_hold = FALSE;
1390 boolean handle_gadgets = TRUE;
1396 button_nr = -button_nr;
1405 #if defined(PLATFORM_ANDROID)
1406 // when playing, only handle gadgets when using "follow finger" controls
1407 // or when using touch controls in combination with the MM game engine
1409 (game_status != GAME_MODE_PLAYING ||
1410 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1411 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1414 if (handle_gadgets && HandleGadgets(mx, my, button))
1416 /* do not handle this button event anymore */
1417 mx = my = -32; /* force mouse event to be outside screen tiles */
1420 if (HandleGlobalAnimClicks(mx, my, button))
1422 /* do not handle this button event anymore */
1423 return; /* force mouse event not to be handled at all */
1426 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1429 /* do not use scroll wheel button events for anything other than gadgets */
1430 if (IS_WHEEL_BUTTON(button_nr))
1433 switch (game_status)
1435 case GAME_MODE_TITLE:
1436 HandleTitleScreen(mx, my, 0, 0, button);
1439 case GAME_MODE_MAIN:
1440 HandleMainMenu(mx, my, 0, 0, button);
1443 case GAME_MODE_PSEUDO_TYPENAME:
1444 HandleTypeName(0, KSYM_Return);
1447 case GAME_MODE_LEVELS:
1448 HandleChooseLevelSet(mx, my, 0, 0, button);
1451 case GAME_MODE_LEVELNR:
1452 HandleChooseLevelNr(mx, my, 0, 0, button);
1455 case GAME_MODE_SCORES:
1456 HandleHallOfFame(0, 0, 0, 0, button);
1459 case GAME_MODE_EDITOR:
1460 HandleLevelEditorIdle();
1463 case GAME_MODE_INFO:
1464 HandleInfoScreen(mx, my, 0, 0, button);
1467 case GAME_MODE_SETUP:
1468 HandleSetupScreen(mx, my, 0, 0, button);
1471 case GAME_MODE_PLAYING:
1472 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1473 HandleButtonOrFinger(mx, my, button);
1475 SetPlayerMouseAction(mx, my, button);
1478 if (button == MB_PRESSED && !motion_status && !button_hold &&
1479 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1480 DumpTileFromScreen(mx, my);
1490 static boolean is_string_suffix(char *string, char *suffix)
1492 int string_len = strlen(string);
1493 int suffix_len = strlen(suffix);
1495 if (suffix_len > string_len)
1498 return (strEqual(&string[string_len - suffix_len], suffix));
1501 #define MAX_CHEAT_INPUT_LEN 32
1503 static void HandleKeysSpecial(Key key)
1505 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1506 char letter = getCharFromKey(key);
1507 int cheat_input_len = strlen(cheat_input);
1513 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1515 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1516 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1518 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1521 cheat_input[cheat_input_len++] = letter;
1522 cheat_input[cheat_input_len] = '\0';
1524 #if DEBUG_EVENTS_KEY
1525 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1528 if (game_status == GAME_MODE_MAIN)
1530 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1531 is_string_suffix(cheat_input, ":ist"))
1533 InsertSolutionTape();
1535 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1536 is_string_suffix(cheat_input, ":rg"))
1538 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1541 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1542 is_string_suffix(cheat_input, ":rs"))
1544 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1547 else if (is_string_suffix(cheat_input, ":reload-music") ||
1548 is_string_suffix(cheat_input, ":rm"))
1550 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1553 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1554 is_string_suffix(cheat_input, ":ra"))
1556 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1557 1 << ARTWORK_TYPE_SOUNDS |
1558 1 << ARTWORK_TYPE_MUSIC);
1561 else if (is_string_suffix(cheat_input, ":dump-level") ||
1562 is_string_suffix(cheat_input, ":dl"))
1566 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1567 is_string_suffix(cheat_input, ":dt"))
1571 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1572 is_string_suffix(cheat_input, ":ft"))
1574 /* fix single-player tapes that contain player input for more than one
1575 player (due to a bug in 3.3.1.2 and earlier versions), which results
1576 in playing levels with more than one player in multi-player mode,
1577 even though the tape was originally recorded in single-player mode */
1579 /* remove player input actions for all players but the first one */
1580 for (i = 1; i < MAX_PLAYERS; i++)
1581 tape.player_participates[i] = FALSE;
1583 tape.changed = TRUE;
1585 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1586 is_string_suffix(cheat_input, ":snl"))
1588 SaveNativeLevel(&level);
1590 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1591 is_string_suffix(cheat_input, ":fps"))
1593 global.show_frames_per_second = !global.show_frames_per_second;
1596 else if (game_status == GAME_MODE_PLAYING)
1599 if (is_string_suffix(cheat_input, ".q"))
1600 DEBUG_SetMaximumDynamite();
1603 else if (game_status == GAME_MODE_EDITOR)
1605 if (is_string_suffix(cheat_input, ":dump-brush") ||
1606 is_string_suffix(cheat_input, ":DB"))
1610 else if (is_string_suffix(cheat_input, ":DDB"))
1617 void HandleKeysDebug(Key key)
1622 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1624 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1626 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1628 if (key == setup.debug.frame_delay_key[i] &&
1629 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1631 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1632 setup.debug.frame_delay[i] : setup.game_frame_delay);
1634 if (!setup.debug.frame_delay_game_only)
1635 MenuFrameDelay = GameFrameDelay;
1637 SetVideoFrameDelay(GameFrameDelay);
1639 if (GameFrameDelay > ONE_SECOND_DELAY)
1640 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1641 else if (GameFrameDelay != 0)
1642 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1643 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1644 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1646 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1653 if (game_status == GAME_MODE_PLAYING)
1657 options.debug = !options.debug;
1659 Error(ERR_DEBUG, "debug mode %s",
1660 (options.debug ? "enabled" : "disabled"));
1662 else if (key == KSYM_v)
1664 Error(ERR_DEBUG, "currently using game engine version %d",
1665 game.engine_version);
1671 void HandleKey(Key key, int key_status)
1673 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1674 static boolean ignore_repeated_key = FALSE;
1675 static struct SetupKeyboardInfo ski;
1676 static struct SetupShortcutInfo ssi;
1685 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1686 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1687 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1688 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1689 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1690 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1695 #if defined(TARGET_SDL2)
1696 /* map special keys (media keys / remote control buttons) to default keys */
1697 if (key == KSYM_PlayPause)
1699 else if (key == KSYM_Select)
1703 HandleSpecialGameControllerKeys(key, key_status);
1705 if (game_status == GAME_MODE_PLAYING)
1707 /* only needed for single-step tape recording mode */
1708 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1711 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1713 byte key_action = 0;
1715 if (setup.input[pnr].use_joystick)
1718 ski = setup.input[pnr].key;
1720 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1721 if (key == *key_info[i].key_custom)
1722 key_action |= key_info[i].action;
1724 /* use combined snap+direction keys for the first player only */
1727 ssi = setup.shortcut;
1729 for (i = 0; i < NUM_DIRECTIONS; i++)
1730 if (key == *key_info[i].key_snap)
1731 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1734 if (key_status == KEY_PRESSED)
1735 stored_player[pnr].action |= key_action;
1737 stored_player[pnr].action &= ~key_action;
1739 if (tape.single_step && tape.recording && tape.pausing)
1741 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1743 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1745 /* if snap key already pressed, keep pause mode when releasing */
1746 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1747 has_snapped[pnr] = TRUE;
1749 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1751 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1753 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1754 getRedDiskReleaseFlag_SP() == 0)
1756 /* add a single inactive frame before dropping starts */
1757 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1758 stored_player[pnr].force_dropping = TRUE;
1761 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1763 /* if snap key was pressed without direction, leave pause mode */
1764 if (!has_snapped[pnr])
1765 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1767 has_snapped[pnr] = FALSE;
1770 else if (tape.recording && tape.pausing && !tape.use_mouse)
1772 /* prevent key release events from un-pausing a paused game */
1773 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1774 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1780 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1781 if (key == key_info[i].key_default)
1782 joy |= key_info[i].action;
1787 if (key_status == KEY_PRESSED)
1788 key_joystick_mapping |= joy;
1790 key_joystick_mapping &= ~joy;
1795 if (game_status != GAME_MODE_PLAYING)
1796 key_joystick_mapping = 0;
1798 if (key_status == KEY_RELEASED)
1800 // reset flag to ignore repeated "key pressed" events after key release
1801 ignore_repeated_key = FALSE;
1806 if ((key == KSYM_F11 ||
1807 ((key == KSYM_Return ||
1808 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1809 video.fullscreen_available &&
1810 !ignore_repeated_key)
1812 setup.fullscreen = !setup.fullscreen;
1814 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1816 if (game_status == GAME_MODE_SETUP)
1817 RedrawSetupScreenAfterFullscreenToggle();
1819 // set flag to ignore repeated "key pressed" events
1820 ignore_repeated_key = TRUE;
1825 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1826 key == KSYM_minus || key == KSYM_KP_Subtract ||
1827 key == KSYM_plus || key == KSYM_KP_Add ||
1828 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1829 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1830 video.window_scaling_available &&
1831 !video.fullscreen_enabled)
1833 if (key == KSYM_0 || key == KSYM_KP_0)
1834 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1835 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1836 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1838 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1840 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1841 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1842 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1843 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1845 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1847 if (game_status == GAME_MODE_SETUP)
1848 RedrawSetupScreenAfterFullscreenToggle();
1853 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1854 key == KSYM_Return ||
1855 key == KSYM_Escape)))
1857 /* do not handle this key event anymore */
1858 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1862 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1863 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1870 if (game_status == GAME_MODE_MAIN &&
1871 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1873 StartGameActions(options.network, setup.autorecord, level.random_seed);
1878 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1880 if (key == setup.shortcut.save_game)
1882 else if (key == setup.shortcut.load_game)
1884 else if (key == setup.shortcut.toggle_pause)
1885 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1887 HandleTapeButtonKeys(key);
1888 HandleSoundButtonKeys(key);
1891 if (game_status == GAME_MODE_PLAYING && !network_playing)
1893 int centered_player_nr_next = -999;
1895 if (key == setup.shortcut.focus_player_all)
1896 centered_player_nr_next = -1;
1898 for (i = 0; i < MAX_PLAYERS; i++)
1899 if (key == setup.shortcut.focus_player[i])
1900 centered_player_nr_next = i;
1902 if (centered_player_nr_next != -999)
1904 game.centered_player_nr_next = centered_player_nr_next;
1905 game.set_centered_player = TRUE;
1909 tape.centered_player_nr_next = game.centered_player_nr_next;
1910 tape.set_centered_player = TRUE;
1915 HandleKeysSpecial(key);
1917 if (HandleGadgetsKeyInput(key))
1919 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1920 key = KSYM_UNDEFINED;
1923 switch (game_status)
1925 case GAME_MODE_PSEUDO_TYPENAME:
1926 HandleTypeName(0, key);
1929 case GAME_MODE_TITLE:
1930 case GAME_MODE_MAIN:
1931 case GAME_MODE_LEVELS:
1932 case GAME_MODE_LEVELNR:
1933 case GAME_MODE_SETUP:
1934 case GAME_MODE_INFO:
1935 case GAME_MODE_SCORES:
1940 if (game_status == GAME_MODE_TITLE)
1941 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1942 else if (game_status == GAME_MODE_MAIN)
1943 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1944 else if (game_status == GAME_MODE_LEVELS)
1945 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1946 else if (game_status == GAME_MODE_LEVELNR)
1947 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1948 else if (game_status == GAME_MODE_SETUP)
1949 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1950 else if (game_status == GAME_MODE_INFO)
1951 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1952 else if (game_status == GAME_MODE_SCORES)
1953 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1957 if (game_status != GAME_MODE_MAIN)
1958 FadeSkipNextFadeIn();
1960 if (game_status == GAME_MODE_TITLE)
1961 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1962 else if (game_status == GAME_MODE_LEVELS)
1963 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1964 else if (game_status == GAME_MODE_LEVELNR)
1965 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1966 else if (game_status == GAME_MODE_SETUP)
1967 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1968 else if (game_status == GAME_MODE_INFO)
1969 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1970 else if (game_status == GAME_MODE_SCORES)
1971 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1975 if (game_status == GAME_MODE_LEVELS)
1976 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1977 else if (game_status == GAME_MODE_LEVELNR)
1978 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1979 else if (game_status == GAME_MODE_SETUP)
1980 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1981 else if (game_status == GAME_MODE_INFO)
1982 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1983 else if (game_status == GAME_MODE_SCORES)
1984 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1987 case KSYM_Page_Down:
1988 if (game_status == GAME_MODE_LEVELS)
1989 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1990 else if (game_status == GAME_MODE_LEVELNR)
1991 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1992 else if (game_status == GAME_MODE_SETUP)
1993 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1994 else if (game_status == GAME_MODE_INFO)
1995 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1996 else if (game_status == GAME_MODE_SCORES)
1997 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2005 case GAME_MODE_EDITOR:
2006 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2007 HandleLevelEditorKeyInput(key);
2010 case GAME_MODE_PLAYING:
2015 RequestQuitGame(setup.ask_on_escape);
2025 if (key == KSYM_Escape)
2027 SetGameStatus(GAME_MODE_MAIN);
2035 HandleKeysDebug(key);
2038 void HandleNoEvent()
2040 HandleMouseCursor();
2042 switch (game_status)
2044 case GAME_MODE_PLAYING:
2045 HandleButtonOrFinger(-1, -1, -1);
2050 void HandleEventActions()
2052 // if (button_status && game_status != GAME_MODE_PLAYING)
2053 if (button_status && (game_status != GAME_MODE_PLAYING ||
2055 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2057 HandleButton(0, 0, button_status, -button_status);
2064 #if defined(NETWORK_AVALIABLE)
2065 if (options.network)
2069 switch (game_status)
2071 case GAME_MODE_MAIN:
2072 DrawPreviewLevelAnimation();
2075 case GAME_MODE_EDITOR:
2076 HandleLevelEditorIdle();
2084 static int HandleJoystickForAllPlayers()
2088 boolean no_joysticks_configured = TRUE;
2089 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2090 static byte joy_action_last[MAX_PLAYERS];
2092 for (i = 0; i < MAX_PLAYERS; i++)
2093 if (setup.input[i].use_joystick)
2094 no_joysticks_configured = FALSE;
2096 /* if no joysticks configured, map connected joysticks to players */
2097 if (no_joysticks_configured)
2098 use_as_joystick_nr = TRUE;
2100 for (i = 0; i < MAX_PLAYERS; i++)
2102 byte joy_action = 0;
2104 joy_action = JoystickExt(i, use_as_joystick_nr);
2105 result |= joy_action;
2107 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2108 joy_action != joy_action_last[i])
2109 stored_player[i].action = joy_action;
2111 joy_action_last[i] = joy_action;
2117 void HandleJoystick()
2119 int joystick = HandleJoystickForAllPlayers();
2120 int keyboard = key_joystick_mapping;
2121 int joy = (joystick | keyboard);
2122 int left = joy & JOY_LEFT;
2123 int right = joy & JOY_RIGHT;
2124 int up = joy & JOY_UP;
2125 int down = joy & JOY_DOWN;
2126 int button = joy & JOY_BUTTON;
2127 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2128 int dx = (left ? -1 : right ? 1 : 0);
2129 int dy = (up ? -1 : down ? 1 : 0);
2131 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2133 /* do not handle this button event anymore */
2137 switch (game_status)
2139 case GAME_MODE_TITLE:
2140 case GAME_MODE_MAIN:
2141 case GAME_MODE_LEVELS:
2142 case GAME_MODE_LEVELNR:
2143 case GAME_MODE_SETUP:
2144 case GAME_MODE_INFO:
2146 static unsigned int joystickmove_delay = 0;
2147 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2148 static int joystick_last = 0;
2150 if (joystick && !button &&
2151 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2153 /* delay joystick actions if buttons/axes continually pressed */
2154 newbutton = dx = dy = 0;
2158 /* start with longer delay, then continue with shorter delay */
2159 if (joystick != joystick_last)
2160 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2162 joystickmove_delay_value = GADGET_FRAME_DELAY;
2165 if (game_status == GAME_MODE_TITLE)
2166 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2167 else if (game_status == GAME_MODE_MAIN)
2168 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2169 else if (game_status == GAME_MODE_LEVELS)
2170 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2171 else if (game_status == GAME_MODE_LEVELNR)
2172 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2173 else if (game_status == GAME_MODE_SETUP)
2174 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2175 else if (game_status == GAME_MODE_INFO)
2176 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2178 joystick_last = joystick;
2183 case GAME_MODE_SCORES:
2184 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2187 case GAME_MODE_PLAYING:
2188 if (tape.playing || keyboard)
2189 newbutton = ((joy & JOY_BUTTON) != 0);
2191 if (newbutton && AllPlayersGone)
2198 if (tape.recording && tape.pausing)
2200 if (joystick & JOY_ACTION)
2201 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2211 void HandleSpecialGameControllerButtons(Event *event)
2213 #if defined(TARGET_SDL2)
2214 switch (event->type)
2216 case SDL_CONTROLLERBUTTONDOWN:
2217 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2218 HandleKey(KSYM_space, KEY_PRESSED);
2219 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2220 HandleKey(KSYM_Escape, KEY_PRESSED);
2224 case SDL_CONTROLLERBUTTONUP:
2225 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2226 HandleKey(KSYM_space, KEY_RELEASED);
2227 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2228 HandleKey(KSYM_Escape, KEY_RELEASED);
2235 void HandleSpecialGameControllerKeys(Key key, int key_status)
2237 #if defined(TARGET_SDL2)
2238 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2239 int button = SDL_CONTROLLER_BUTTON_INVALID;
2241 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2242 if (key == KSYM_Rewind)
2243 button = SDL_CONTROLLER_BUTTON_A;
2244 else if (key == KSYM_FastForward || key == KSYM_Menu)
2245 button = SDL_CONTROLLER_BUTTON_B;
2247 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2251 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2252 SDL_CONTROLLERBUTTONUP);
2254 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2255 event.cbutton.button = button;
2256 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2259 HandleJoystickEvent(&event);