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 (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
682 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
684 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
686 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
688 event_y = (event_y - ypos) / (1 - ypos);
690 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
691 event_y > 2.0 / 3.0 && event_y < 1 ?
692 setup.input[0].key.snap :
693 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
694 event_y > 2.0 / 3.0 && event_y < 1 ?
695 setup.input[0].key.drop :
696 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
697 event_y > 0 && event_y < 1.0 / 3.0 ?
698 setup.input[0].key.up :
699 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
700 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
701 setup.input[0].key.left :
702 event_x > 8.0 / 9.0 && event_x < 1 &&
703 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
704 setup.input[0].key.right :
705 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
706 event_y > 2.0 / 3.0 && event_y < 1 ?
707 setup.input[0].key.down :
710 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
714 // for any touch input event, enable overlay buttons (if activated)
715 SetOverlayEnabled(TRUE);
717 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
718 getKeyNameFromKey(key), key_status_name, event->fingerId);
720 // check if we already know this touch event's finger id
721 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
723 if (touch_info[i].touched &&
724 touch_info[i].finger_id == event->fingerId)
726 // Error(ERR_DEBUG, "MARK 1: %d", i);
732 if (i >= NUM_TOUCH_FINGERS)
734 if (key_status == KEY_PRESSED)
736 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
738 // unknown finger id -- get new, empty slot, if available
739 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
741 if (touch_info[i].counter < oldest_counter)
744 oldest_counter = touch_info[i].counter;
746 // Error(ERR_DEBUG, "MARK 2: %d", i);
749 if (!touch_info[i].touched)
751 // Error(ERR_DEBUG, "MARK 3: %d", i);
757 if (i >= NUM_TOUCH_FINGERS)
759 // all slots allocated -- use oldest slot
762 // Error(ERR_DEBUG, "MARK 4: %d", i);
767 // release of previously unknown key (should not happen)
769 if (key != KSYM_UNDEFINED)
771 HandleKey(key, KEY_RELEASED);
773 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
774 getKeyNameFromKey(key), "KEY_RELEASED", i);
779 if (i < NUM_TOUCH_FINGERS)
781 if (key_status == KEY_PRESSED)
783 if (touch_info[i].key != key)
785 if (touch_info[i].key != KSYM_UNDEFINED)
787 HandleKey(touch_info[i].key, KEY_RELEASED);
789 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
790 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
793 if (key != KSYM_UNDEFINED)
795 HandleKey(key, KEY_PRESSED);
797 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
798 getKeyNameFromKey(key), "KEY_PRESSED", i);
802 touch_info[i].touched = TRUE;
803 touch_info[i].finger_id = event->fingerId;
804 touch_info[i].counter = Counter();
805 touch_info[i].key = key;
809 if (touch_info[i].key != KSYM_UNDEFINED)
811 HandleKey(touch_info[i].key, KEY_RELEASED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
814 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
817 touch_info[i].touched = FALSE;
818 touch_info[i].finger_id = 0;
819 touch_info[i].counter = 0;
820 touch_info[i].key = 0;
827 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
830 // use touch direction control
832 if (event->type == EVENT_FINGERPRESS)
834 if (event_x > 1.0 / 3.0)
838 motion_id = event->fingerId;
843 motion_key_x = KSYM_UNDEFINED;
844 motion_key_y = KSYM_UNDEFINED;
846 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
852 button_id = event->fingerId;
857 button_key = setup.input[0].key.snap;
859 HandleKey(button_key, KEY_PRESSED);
861 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
864 else if (event->type == EVENT_FINGERRELEASE)
866 if (event->fingerId == motion_id)
870 if (motion_key_x != KSYM_UNDEFINED)
871 HandleKey(motion_key_x, KEY_RELEASED);
872 if (motion_key_y != KSYM_UNDEFINED)
873 HandleKey(motion_key_y, KEY_RELEASED);
875 motion_key_x = KSYM_UNDEFINED;
876 motion_key_y = KSYM_UNDEFINED;
878 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
880 else if (event->fingerId == button_id)
884 if (button_key != KSYM_UNDEFINED)
885 HandleKey(button_key, KEY_RELEASED);
887 button_key = KSYM_UNDEFINED;
889 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
892 else if (event->type == EVENT_FINGERMOTION)
894 if (event->fingerId == motion_id)
896 float distance_x = ABS(event_x - motion_x1);
897 float distance_y = ABS(event_y - motion_y1);
898 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
899 event_x > motion_x1 ? setup.input[0].key.right :
901 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
902 event_y > motion_y1 ? setup.input[0].key.down :
905 if (distance_x < move_trigger_distance / 2 ||
906 distance_x < distance_y)
907 new_motion_key_x = KSYM_UNDEFINED;
909 if (distance_y < move_trigger_distance / 2 ||
910 distance_y < distance_x)
911 new_motion_key_y = KSYM_UNDEFINED;
913 if (distance_x > move_trigger_distance ||
914 distance_y > move_trigger_distance)
916 if (new_motion_key_x != motion_key_x)
918 if (motion_key_x != KSYM_UNDEFINED)
919 HandleKey(motion_key_x, KEY_RELEASED);
920 if (new_motion_key_x != KSYM_UNDEFINED)
921 HandleKey(new_motion_key_x, KEY_PRESSED);
924 if (new_motion_key_y != motion_key_y)
926 if (motion_key_y != KSYM_UNDEFINED)
927 HandleKey(motion_key_y, KEY_RELEASED);
928 if (new_motion_key_y != KSYM_UNDEFINED)
929 HandleKey(new_motion_key_y, KEY_PRESSED);
935 motion_key_x = new_motion_key_x;
936 motion_key_y = new_motion_key_y;
938 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
941 else if (event->fingerId == button_id)
943 float distance_x = ABS(event_x - button_x1);
944 float distance_y = ABS(event_y - button_y1);
946 if (distance_x < drop_trigger_distance / 2 &&
947 distance_y > drop_trigger_distance)
949 if (button_key == setup.input[0].key.snap)
950 HandleKey(button_key, KEY_RELEASED);
955 button_key = setup.input[0].key.drop;
957 HandleKey(button_key, KEY_PRESSED);
959 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
965 static void HandleFollowFinger(int mx, int my, int button)
967 static int old_mx = 0, old_my = 0;
968 static Key motion_key_x = KSYM_UNDEFINED;
969 static Key motion_key_y = KSYM_UNDEFINED;
970 static boolean started_on_player = FALSE;
971 static boolean player_is_dropping = FALSE;
972 static int player_drop_count = 0;
973 static int last_player_x = -1;
974 static int last_player_y = -1;
976 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
979 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
981 touch_info[0].touched = TRUE;
982 touch_info[0].key = 0;
989 started_on_player = FALSE;
990 player_is_dropping = FALSE;
991 player_drop_count = 0;
995 motion_key_x = KSYM_UNDEFINED;
996 motion_key_y = KSYM_UNDEFINED;
998 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1001 else if (button == MB_RELEASED && touch_info[0].touched)
1003 touch_info[0].touched = FALSE;
1004 touch_info[0].key = 0;
1009 if (motion_key_x != KSYM_UNDEFINED)
1010 HandleKey(motion_key_x, KEY_RELEASED);
1011 if (motion_key_y != KSYM_UNDEFINED)
1012 HandleKey(motion_key_y, KEY_RELEASED);
1014 if (started_on_player)
1016 if (player_is_dropping)
1018 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1020 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1024 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1026 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1030 motion_key_x = KSYM_UNDEFINED;
1031 motion_key_y = KSYM_UNDEFINED;
1033 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1036 if (touch_info[0].touched)
1038 int src_x = local_player->jx;
1039 int src_y = local_player->jy;
1040 int dst_x = getLevelFromScreenX(old_mx);
1041 int dst_y = getLevelFromScreenY(old_my);
1042 int dx = dst_x - src_x;
1043 int dy = dst_y - src_y;
1044 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1045 dx > 0 ? setup.input[0].key.right :
1047 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1048 dy > 0 ? setup.input[0].key.down :
1051 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1052 (last_player_x != local_player->jx ||
1053 last_player_y != local_player->jy))
1055 // in case of asymmetric diagonal movement, use "preferred" direction
1057 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1059 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1060 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1062 local_player->last_move_dir = last_move_dir;
1064 // (required to prevent accidentally forcing direction for next movement)
1065 last_player_x = local_player->jx;
1066 last_player_y = local_player->jy;
1069 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1071 started_on_player = TRUE;
1072 player_drop_count = getPlayerInventorySize(0);
1073 player_is_dropping = (player_drop_count > 0);
1075 if (player_is_dropping)
1077 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1079 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1083 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1085 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1088 else if (dx != 0 || dy != 0)
1090 if (player_is_dropping &&
1091 player_drop_count == getPlayerInventorySize(0))
1093 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1095 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1096 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1098 player_is_dropping = FALSE;
1102 if (new_motion_key_x != motion_key_x)
1104 Error(ERR_DEBUG, "---------- %s %s ----------",
1105 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1106 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1108 if (motion_key_x != KSYM_UNDEFINED)
1109 HandleKey(motion_key_x, KEY_RELEASED);
1110 if (new_motion_key_x != KSYM_UNDEFINED)
1111 HandleKey(new_motion_key_x, KEY_PRESSED);
1114 if (new_motion_key_y != motion_key_y)
1116 Error(ERR_DEBUG, "---------- %s %s ----------",
1117 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1118 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1120 if (motion_key_y != KSYM_UNDEFINED)
1121 HandleKey(motion_key_y, KEY_RELEASED);
1122 if (new_motion_key_y != KSYM_UNDEFINED)
1123 HandleKey(new_motion_key_y, KEY_PRESSED);
1126 motion_key_x = new_motion_key_x;
1127 motion_key_y = new_motion_key_y;
1131 static boolean checkTextInputKeyModState()
1133 // when playing, only handle raw key events and ignore text input
1134 if (game_status == GAME_MODE_PLAYING)
1137 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1140 void HandleTextEvent(TextEvent *event)
1142 char *text = event->text;
1143 Key key = getKeyFromKeyName(text);
1145 #if DEBUG_EVENTS_TEXT
1146 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1149 text[0], (int)(text[0]),
1151 getKeyNameFromKey(key),
1155 #if !defined(HAS_SCREEN_KEYBOARD)
1156 // non-mobile devices: only handle key input with modifier keys pressed here
1157 // (every other key input is handled directly as physical key input event)
1158 if (!checkTextInputKeyModState())
1162 // process text input as "classic" (with uppercase etc.) key input event
1163 HandleKey(key, KEY_PRESSED);
1164 HandleKey(key, KEY_RELEASED);
1167 void HandlePauseResumeEvent(PauseResumeEvent *event)
1169 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1173 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1181 void HandleKeyEvent(KeyEvent *event)
1183 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1184 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1185 Key key = GetEventKey(event, with_modifiers);
1186 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1188 #if DEBUG_EVENTS_KEY
1189 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1190 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1191 event->keysym.scancode,
1196 getKeyNameFromKey(key));
1199 #if defined(PLATFORM_ANDROID)
1200 if (key == KSYM_Back)
1202 // always map the "back" button to the "escape" key on Android devices
1207 // for any key event other than "back" button, disable overlay buttons
1208 SetOverlayEnabled(FALSE);
1212 HandleKeyModState(keymod, key_status);
1214 #if defined(TARGET_SDL2)
1215 // only handle raw key input without text modifier keys pressed
1216 if (!checkTextInputKeyModState())
1217 HandleKey(key, key_status);
1219 HandleKey(key, key_status);
1223 void HandleFocusEvent(FocusChangeEvent *event)
1225 static int old_joystick_status = -1;
1227 if (event->type == EVENT_FOCUSOUT)
1229 KeyboardAutoRepeatOn();
1230 old_joystick_status = joystick.status;
1231 joystick.status = JOYSTICK_NOT_AVAILABLE;
1233 ClearPlayerAction();
1235 else if (event->type == EVENT_FOCUSIN)
1237 /* When there are two Rocks'n'Diamonds windows which overlap and
1238 the player moves the pointer from one game window to the other,
1239 a 'FocusOut' event is generated for the window the pointer is
1240 leaving and a 'FocusIn' event is generated for the window the
1241 pointer is entering. In some cases, it can happen that the
1242 'FocusIn' event is handled by the one game process before the
1243 'FocusOut' event by the other game process. In this case the
1244 X11 environment would end up with activated keyboard auto repeat,
1245 because unfortunately this is a global setting and not (which
1246 would be far better) set for each X11 window individually.
1247 The effect would be keyboard auto repeat while playing the game
1248 (game_status == GAME_MODE_PLAYING), which is not desired.
1249 To avoid this special case, we just wait 1/10 second before
1250 processing the 'FocusIn' event.
1253 if (game_status == GAME_MODE_PLAYING)
1256 KeyboardAutoRepeatOffUnlessAutoplay();
1259 if (old_joystick_status != -1)
1260 joystick.status = old_joystick_status;
1264 void HandleClientMessageEvent(ClientMessageEvent *event)
1266 if (CheckCloseWindowEvent(event))
1270 void HandleWindowManagerEvent(Event *event)
1272 #if defined(TARGET_SDL)
1273 SDLHandleWindowManagerEvent(event);
1277 void HandleButton(int mx, int my, int button, int button_nr)
1279 static int old_mx = 0, old_my = 0;
1280 boolean button_hold = FALSE;
1286 button_nr = -button_nr;
1295 #if defined(PLATFORM_ANDROID)
1296 // when playing, only handle gadgets when using "follow finger" controls
1297 boolean handle_gadgets =
1298 (game_status != GAME_MODE_PLAYING ||
1299 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1301 if (handle_gadgets &&
1302 HandleGadgets(mx, my, button))
1304 /* do not handle this button event anymore */
1305 mx = my = -32; /* force mouse event to be outside screen tiles */
1308 if (HandleGadgets(mx, my, button))
1310 /* do not handle this button event anymore */
1311 mx = my = -32; /* force mouse event to be outside screen tiles */
1315 if (HandleGlobalAnimClicks(mx, my, button))
1317 /* do not handle this button event anymore */
1318 return; /* force mouse event not to be handled at all */
1321 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1324 /* do not use scroll wheel button events for anything other than gadgets */
1325 if (IS_WHEEL_BUTTON(button_nr))
1328 switch (game_status)
1330 case GAME_MODE_TITLE:
1331 HandleTitleScreen(mx, my, 0, 0, button);
1334 case GAME_MODE_MAIN:
1335 HandleMainMenu(mx, my, 0, 0, button);
1338 case GAME_MODE_PSEUDO_TYPENAME:
1339 HandleTypeName(0, KSYM_Return);
1342 case GAME_MODE_LEVELS:
1343 HandleChooseLevelSet(mx, my, 0, 0, button);
1346 case GAME_MODE_LEVELNR:
1347 HandleChooseLevelNr(mx, my, 0, 0, button);
1350 case GAME_MODE_SCORES:
1351 HandleHallOfFame(0, 0, 0, 0, button);
1354 case GAME_MODE_EDITOR:
1355 HandleLevelEditorIdle();
1358 case GAME_MODE_INFO:
1359 HandleInfoScreen(mx, my, 0, 0, button);
1362 case GAME_MODE_SETUP:
1363 HandleSetupScreen(mx, my, 0, 0, button);
1366 case GAME_MODE_PLAYING:
1367 SetPlayerMouseAction(mx, my, button);
1369 #if defined(TARGET_SDL2)
1370 HandleFollowFinger(mx, my, button);
1374 if (button == MB_PRESSED && !motion_status && !button_hold &&
1375 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1376 DumpTileFromScreen(mx, my);
1386 static boolean is_string_suffix(char *string, char *suffix)
1388 int string_len = strlen(string);
1389 int suffix_len = strlen(suffix);
1391 if (suffix_len > string_len)
1394 return (strEqual(&string[string_len - suffix_len], suffix));
1397 #define MAX_CHEAT_INPUT_LEN 32
1399 static void HandleKeysSpecial(Key key)
1401 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1402 char letter = getCharFromKey(key);
1403 int cheat_input_len = strlen(cheat_input);
1409 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1411 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1412 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1414 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1417 cheat_input[cheat_input_len++] = letter;
1418 cheat_input[cheat_input_len] = '\0';
1420 #if DEBUG_EVENTS_KEY
1421 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1424 if (game_status == GAME_MODE_MAIN)
1426 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1427 is_string_suffix(cheat_input, ":ist"))
1429 InsertSolutionTape();
1431 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1432 is_string_suffix(cheat_input, ":rg"))
1434 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1437 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1438 is_string_suffix(cheat_input, ":rs"))
1440 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1443 else if (is_string_suffix(cheat_input, ":reload-music") ||
1444 is_string_suffix(cheat_input, ":rm"))
1446 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1449 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1450 is_string_suffix(cheat_input, ":ra"))
1452 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1453 1 << ARTWORK_TYPE_SOUNDS |
1454 1 << ARTWORK_TYPE_MUSIC);
1457 else if (is_string_suffix(cheat_input, ":dump-level") ||
1458 is_string_suffix(cheat_input, ":dl"))
1462 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1463 is_string_suffix(cheat_input, ":dt"))
1467 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1468 is_string_suffix(cheat_input, ":ft"))
1470 /* fix single-player tapes that contain player input for more than one
1471 player (due to a bug in 3.3.1.2 and earlier versions), which results
1472 in playing levels with more than one player in multi-player mode,
1473 even though the tape was originally recorded in single-player mode */
1475 /* remove player input actions for all players but the first one */
1476 for (i = 1; i < MAX_PLAYERS; i++)
1477 tape.player_participates[i] = FALSE;
1479 tape.changed = TRUE;
1481 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1482 is_string_suffix(cheat_input, ":snl"))
1484 SaveNativeLevel(&level);
1486 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1487 is_string_suffix(cheat_input, ":fps"))
1489 global.show_frames_per_second = !global.show_frames_per_second;
1492 else if (game_status == GAME_MODE_PLAYING)
1495 if (is_string_suffix(cheat_input, ".q"))
1496 DEBUG_SetMaximumDynamite();
1499 else if (game_status == GAME_MODE_EDITOR)
1501 if (is_string_suffix(cheat_input, ":dump-brush") ||
1502 is_string_suffix(cheat_input, ":DB"))
1506 else if (is_string_suffix(cheat_input, ":DDB"))
1513 void HandleKeysDebug(Key key)
1518 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1520 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1522 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1524 if (key == setup.debug.frame_delay_key[i] &&
1525 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1527 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1528 setup.debug.frame_delay[i] : setup.game_frame_delay);
1530 if (!setup.debug.frame_delay_game_only)
1531 MenuFrameDelay = GameFrameDelay;
1533 SetVideoFrameDelay(GameFrameDelay);
1535 if (GameFrameDelay > ONE_SECOND_DELAY)
1536 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1537 else if (GameFrameDelay != 0)
1538 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1539 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1540 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1542 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1549 if (game_status == GAME_MODE_PLAYING)
1553 options.debug = !options.debug;
1555 Error(ERR_DEBUG, "debug mode %s",
1556 (options.debug ? "enabled" : "disabled"));
1558 else if (key == KSYM_v)
1560 Error(ERR_DEBUG, "currently using game engine version %d",
1561 game.engine_version);
1567 void HandleKey(Key key, int key_status)
1569 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1570 static boolean ignore_repeated_key = FALSE;
1571 static struct SetupKeyboardInfo ski;
1572 static struct SetupShortcutInfo ssi;
1581 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1582 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1583 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1584 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1585 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1586 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1591 #if defined(TARGET_SDL2)
1592 /* map special keys (media keys / remote control buttons) to default keys */
1593 if (key == KSYM_PlayPause)
1595 else if (key == KSYM_Select)
1599 HandleSpecialGameControllerKeys(key, key_status);
1601 if (game_status == GAME_MODE_PLAYING)
1603 /* only needed for single-step tape recording mode */
1604 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1607 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1609 byte key_action = 0;
1611 if (setup.input[pnr].use_joystick)
1614 ski = setup.input[pnr].key;
1616 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1617 if (key == *key_info[i].key_custom)
1618 key_action |= key_info[i].action;
1620 /* use combined snap+direction keys for the first player only */
1623 ssi = setup.shortcut;
1625 for (i = 0; i < NUM_DIRECTIONS; i++)
1626 if (key == *key_info[i].key_snap)
1627 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1630 if (key_status == KEY_PRESSED)
1631 stored_player[pnr].action |= key_action;
1633 stored_player[pnr].action &= ~key_action;
1635 if (tape.single_step && tape.recording && tape.pausing)
1637 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1639 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1641 /* if snap key already pressed, keep pause mode when releasing */
1642 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1643 has_snapped[pnr] = TRUE;
1645 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1647 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1649 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1650 getRedDiskReleaseFlag_SP() == 0)
1652 /* add a single inactive frame before dropping starts */
1653 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1654 stored_player[pnr].force_dropping = TRUE;
1657 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1659 /* if snap key was pressed without direction, leave pause mode */
1660 if (!has_snapped[pnr])
1661 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1663 has_snapped[pnr] = FALSE;
1666 else if (tape.recording && tape.pausing && !tape.use_mouse)
1668 /* prevent key release events from un-pausing a paused game */
1669 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1670 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1676 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1677 if (key == key_info[i].key_default)
1678 joy |= key_info[i].action;
1683 if (key_status == KEY_PRESSED)
1684 key_joystick_mapping |= joy;
1686 key_joystick_mapping &= ~joy;
1691 if (game_status != GAME_MODE_PLAYING)
1692 key_joystick_mapping = 0;
1694 if (key_status == KEY_RELEASED)
1696 // reset flag to ignore repeated "key pressed" events after key release
1697 ignore_repeated_key = FALSE;
1702 if ((key == KSYM_F11 ||
1703 ((key == KSYM_Return ||
1704 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1705 video.fullscreen_available &&
1706 !ignore_repeated_key)
1708 setup.fullscreen = !setup.fullscreen;
1710 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1712 if (game_status == GAME_MODE_SETUP)
1713 RedrawSetupScreenAfterFullscreenToggle();
1715 // set flag to ignore repeated "key pressed" events
1716 ignore_repeated_key = TRUE;
1721 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1722 key == KSYM_minus || key == KSYM_KP_Subtract ||
1723 key == KSYM_plus || key == KSYM_KP_Add ||
1724 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1725 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1726 video.window_scaling_available &&
1727 !video.fullscreen_enabled)
1729 if (key == KSYM_0 || key == KSYM_KP_0)
1730 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1731 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1732 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1734 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1736 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1737 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1738 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1739 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1741 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1743 if (game_status == GAME_MODE_SETUP)
1744 RedrawSetupScreenAfterFullscreenToggle();
1749 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1750 key == KSYM_Return ||
1751 key == KSYM_Escape)))
1753 /* do not handle this key event anymore */
1754 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1758 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1759 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1766 if (game_status == GAME_MODE_MAIN &&
1767 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1769 StartGameActions(options.network, setup.autorecord, level.random_seed);
1774 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1776 if (key == setup.shortcut.save_game)
1778 else if (key == setup.shortcut.load_game)
1780 else if (key == setup.shortcut.toggle_pause)
1781 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1783 HandleTapeButtonKeys(key);
1784 HandleSoundButtonKeys(key);
1787 if (game_status == GAME_MODE_PLAYING && !network_playing)
1789 int centered_player_nr_next = -999;
1791 if (key == setup.shortcut.focus_player_all)
1792 centered_player_nr_next = -1;
1794 for (i = 0; i < MAX_PLAYERS; i++)
1795 if (key == setup.shortcut.focus_player[i])
1796 centered_player_nr_next = i;
1798 if (centered_player_nr_next != -999)
1800 game.centered_player_nr_next = centered_player_nr_next;
1801 game.set_centered_player = TRUE;
1805 tape.centered_player_nr_next = game.centered_player_nr_next;
1806 tape.set_centered_player = TRUE;
1811 HandleKeysSpecial(key);
1813 if (HandleGadgetsKeyInput(key))
1815 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1816 key = KSYM_UNDEFINED;
1819 switch (game_status)
1821 case GAME_MODE_PSEUDO_TYPENAME:
1822 HandleTypeName(0, key);
1825 case GAME_MODE_TITLE:
1826 case GAME_MODE_MAIN:
1827 case GAME_MODE_LEVELS:
1828 case GAME_MODE_LEVELNR:
1829 case GAME_MODE_SETUP:
1830 case GAME_MODE_INFO:
1831 case GAME_MODE_SCORES:
1836 if (game_status == GAME_MODE_TITLE)
1837 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1838 else if (game_status == GAME_MODE_MAIN)
1839 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1840 else if (game_status == GAME_MODE_LEVELS)
1841 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1842 else if (game_status == GAME_MODE_LEVELNR)
1843 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1844 else if (game_status == GAME_MODE_SETUP)
1845 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1846 else if (game_status == GAME_MODE_INFO)
1847 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1848 else if (game_status == GAME_MODE_SCORES)
1849 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1853 if (game_status != GAME_MODE_MAIN)
1854 FadeSkipNextFadeIn();
1856 if (game_status == GAME_MODE_TITLE)
1857 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1858 else if (game_status == GAME_MODE_LEVELS)
1859 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1860 else if (game_status == GAME_MODE_LEVELNR)
1861 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1862 else if (game_status == GAME_MODE_SETUP)
1863 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1864 else if (game_status == GAME_MODE_INFO)
1865 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1866 else if (game_status == GAME_MODE_SCORES)
1867 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1871 if (game_status == GAME_MODE_LEVELS)
1872 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1873 else if (game_status == GAME_MODE_LEVELNR)
1874 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1875 else if (game_status == GAME_MODE_SETUP)
1876 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1877 else if (game_status == GAME_MODE_INFO)
1878 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1879 else if (game_status == GAME_MODE_SCORES)
1880 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1883 case KSYM_Page_Down:
1884 if (game_status == GAME_MODE_LEVELS)
1885 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1886 else if (game_status == GAME_MODE_LEVELNR)
1887 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1888 else if (game_status == GAME_MODE_SETUP)
1889 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1890 else if (game_status == GAME_MODE_INFO)
1891 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1892 else if (game_status == GAME_MODE_SCORES)
1893 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1901 case GAME_MODE_EDITOR:
1902 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1903 HandleLevelEditorKeyInput(key);
1906 case GAME_MODE_PLAYING:
1911 RequestQuitGame(setup.ask_on_escape);
1921 if (key == KSYM_Escape)
1923 SetGameStatus(GAME_MODE_MAIN);
1931 HandleKeysDebug(key);
1934 void HandleNoEvent()
1936 HandleMouseCursor();
1938 switch (game_status)
1940 #if defined(TARGET_SDL2)
1941 case GAME_MODE_PLAYING:
1942 HandleFollowFinger(-1, -1, -1);
1948 void HandleEventActions()
1950 // if (button_status && game_status != GAME_MODE_PLAYING)
1951 if (button_status && (game_status != GAME_MODE_PLAYING ||
1953 level.game_engine_type == GAME_ENGINE_TYPE_MM))
1955 HandleButton(0, 0, button_status, -button_status);
1962 #if defined(NETWORK_AVALIABLE)
1963 if (options.network)
1967 switch (game_status)
1969 case GAME_MODE_MAIN:
1970 DrawPreviewLevelAnimation();
1973 case GAME_MODE_EDITOR:
1974 HandleLevelEditorIdle();
1982 static int HandleJoystickForAllPlayers()
1986 boolean no_joysticks_configured = TRUE;
1987 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1988 static byte joy_action_last[MAX_PLAYERS];
1990 for (i = 0; i < MAX_PLAYERS; i++)
1991 if (setup.input[i].use_joystick)
1992 no_joysticks_configured = FALSE;
1994 /* if no joysticks configured, map connected joysticks to players */
1995 if (no_joysticks_configured)
1996 use_as_joystick_nr = TRUE;
1998 for (i = 0; i < MAX_PLAYERS; i++)
2000 byte joy_action = 0;
2002 joy_action = JoystickExt(i, use_as_joystick_nr);
2003 result |= joy_action;
2005 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2006 joy_action != joy_action_last[i])
2007 stored_player[i].action = joy_action;
2009 joy_action_last[i] = joy_action;
2015 void HandleJoystick()
2017 int joystick = HandleJoystickForAllPlayers();
2018 int keyboard = key_joystick_mapping;
2019 int joy = (joystick | keyboard);
2020 int left = joy & JOY_LEFT;
2021 int right = joy & JOY_RIGHT;
2022 int up = joy & JOY_UP;
2023 int down = joy & JOY_DOWN;
2024 int button = joy & JOY_BUTTON;
2025 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2026 int dx = (left ? -1 : right ? 1 : 0);
2027 int dy = (up ? -1 : down ? 1 : 0);
2029 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2031 /* do not handle this button event anymore */
2035 switch (game_status)
2037 case GAME_MODE_TITLE:
2038 case GAME_MODE_MAIN:
2039 case GAME_MODE_LEVELS:
2040 case GAME_MODE_LEVELNR:
2041 case GAME_MODE_SETUP:
2042 case GAME_MODE_INFO:
2044 static unsigned int joystickmove_delay = 0;
2045 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2046 static int joystick_last = 0;
2048 if (joystick && !button &&
2049 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2051 /* delay joystick actions if buttons/axes continually pressed */
2052 newbutton = dx = dy = 0;
2056 /* start with longer delay, then continue with shorter delay */
2057 if (joystick != joystick_last)
2058 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2060 joystickmove_delay_value = GADGET_FRAME_DELAY;
2063 if (game_status == GAME_MODE_TITLE)
2064 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2065 else if (game_status == GAME_MODE_MAIN)
2066 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2067 else if (game_status == GAME_MODE_LEVELS)
2068 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2069 else if (game_status == GAME_MODE_LEVELNR)
2070 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2071 else if (game_status == GAME_MODE_SETUP)
2072 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2073 else if (game_status == GAME_MODE_INFO)
2074 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2076 joystick_last = joystick;
2081 case GAME_MODE_SCORES:
2082 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2085 case GAME_MODE_PLAYING:
2086 if (tape.playing || keyboard)
2087 newbutton = ((joy & JOY_BUTTON) != 0);
2089 if (newbutton && AllPlayersGone)
2096 if (tape.recording && tape.pausing)
2098 if (joystick & JOY_ACTION)
2099 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2109 void HandleSpecialGameControllerButtons(Event *event)
2111 #if defined(TARGET_SDL2)
2112 switch (event->type)
2114 case SDL_CONTROLLERBUTTONDOWN:
2115 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2116 HandleKey(KSYM_space, KEY_PRESSED);
2117 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2118 HandleKey(KSYM_Escape, KEY_PRESSED);
2122 case SDL_CONTROLLERBUTTONUP:
2123 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2124 HandleKey(KSYM_space, KEY_RELEASED);
2125 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2126 HandleKey(KSYM_Escape, KEY_RELEASED);
2133 void HandleSpecialGameControllerKeys(Key key, int key_status)
2135 #if defined(TARGET_SDL2)
2136 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2137 int button = SDL_CONTROLLER_BUTTON_INVALID;
2139 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2140 if (key == KSYM_Rewind)
2141 button = SDL_CONTROLLER_BUTTON_A;
2142 else if (key == KSYM_FastForward || key == KSYM_Menu)
2143 button = SDL_CONTROLLER_BUTTON_B;
2145 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2149 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2150 SDL_CONTROLLERBUTTONUP);
2152 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2153 event.cbutton.button = button;
2154 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2157 HandleJoystickEvent(&event);