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;
42 /* event filter especially needed for SDL event filtering due to
43 delay problems with lots of mouse motion events when mouse button
44 not pressed (X11 can handle this with 'PointerMotionHintMask') */
46 /* event filter addition for SDL2: as SDL2 does not have a function to enable
47 or disable keyboard auto-repeat, filter repeated keyboard events instead */
49 static int FilterEvents(const Event *event)
53 #if defined(TARGET_SDL2)
54 /* skip repeated key press events if keyboard auto-repeat is disabled */
55 if (event->type == EVENT_KEYPRESS &&
61 if (event->type == EVENT_BUTTONPRESS ||
62 event->type == EVENT_BUTTONRELEASE)
64 ((ButtonEvent *)event)->x -= video.screen_xoffset;
65 ((ButtonEvent *)event)->y -= video.screen_yoffset;
67 else if (event->type == EVENT_MOTIONNOTIFY)
69 ((MotionEvent *)event)->x -= video.screen_xoffset;
70 ((MotionEvent *)event)->y -= video.screen_yoffset;
73 /* non-motion events are directly passed to event handler functions */
74 if (event->type != EVENT_MOTIONNOTIFY)
77 motion = (MotionEvent *)event;
78 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
79 motion->y >= SY && motion->y < SY + SYSIZE);
81 /* do no reset mouse cursor before all pending events have been processed */
82 if (gfx.cursor_mode == cursor_mode_last &&
83 ((game_status == GAME_MODE_TITLE &&
84 gfx.cursor_mode == CURSOR_NONE) ||
85 (game_status == GAME_MODE_PLAYING &&
86 gfx.cursor_mode == CURSOR_PLAYFIELD)))
88 SetMouseCursor(CURSOR_DEFAULT);
90 DelayReached(&special_cursor_delay, 0);
92 cursor_mode_last = CURSOR_DEFAULT;
95 /* skip mouse motion events without pressed button outside level editor */
96 if (button_status == MB_RELEASED &&
97 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
103 /* to prevent delay problems, skip mouse motion events if the very next
104 event is also a mouse motion event (and therefore effectively only
105 handling the last of a row of mouse motion events in the event queue) */
107 static boolean SkipPressedMouseMotionEvent(const Event *event)
109 /* nothing to do if the current event is not a mouse motion event */
110 if (event->type != EVENT_MOTIONNOTIFY)
113 /* only skip motion events with pressed button outside the game */
114 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
121 PeekEvent(&next_event);
123 /* if next event is also a mouse motion event, skip the current one */
124 if (next_event.type == EVENT_MOTIONNOTIFY)
131 /* this is especially needed for event modifications for the Android target:
132 if mouse coordinates should be modified in the event filter function,
133 using a properly installed SDL event filter does not work, because in
134 the event filter, mouse coordinates in the event structure are still
135 physical pixel positions, not logical (scaled) screen positions, so this
136 has to be handled at a later stage in the event processing functions
137 (when device pixel positions are already converted to screen positions) */
139 boolean NextValidEvent(Event *event)
141 while (PendingEvent())
143 boolean handle_this_event = FALSE;
147 if (FilterEvents(event))
148 handle_this_event = TRUE;
150 if (SkipPressedMouseMotionEvent(event))
151 handle_this_event = FALSE;
153 if (handle_this_event)
163 unsigned int event_frame_delay = 0;
164 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
166 ResetDelayCounter(&event_frame_delay);
168 while (NextValidEvent(&event))
172 case EVENT_BUTTONPRESS:
173 case EVENT_BUTTONRELEASE:
174 HandleButtonEvent((ButtonEvent *) &event);
177 case EVENT_MOTIONNOTIFY:
178 HandleMotionEvent((MotionEvent *) &event);
181 #if defined(TARGET_SDL2)
182 case EVENT_WHEELMOTION:
183 HandleWheelEvent((WheelEvent *) &event);
186 case SDL_WINDOWEVENT:
187 HandleWindowEvent((WindowEvent *) &event);
190 case EVENT_FINGERPRESS:
191 case EVENT_FINGERRELEASE:
192 case EVENT_FINGERMOTION:
193 HandleFingerEvent((FingerEvent *) &event);
196 case EVENT_TEXTINPUT:
197 HandleTextEvent((TextEvent *) &event);
200 case SDL_APP_WILLENTERBACKGROUND:
201 case SDL_APP_DIDENTERBACKGROUND:
202 case SDL_APP_WILLENTERFOREGROUND:
203 case SDL_APP_DIDENTERFOREGROUND:
204 HandlePauseResumeEvent((PauseResumeEvent *) &event);
209 case EVENT_KEYRELEASE:
210 HandleKeyEvent((KeyEvent *) &event);
214 HandleOtherEvents(&event);
218 // do not handle events for longer than standard frame delay period
219 if (DelayReached(&event_frame_delay, event_frame_delay_value))
224 void HandleOtherEvents(Event *event)
229 HandleExposeEvent((ExposeEvent *) event);
232 case EVENT_UNMAPNOTIFY:
234 /* This causes the game to stop not only when iconified, but also
235 when on another virtual desktop, which might be not desired. */
236 SleepWhileUnmapped();
242 HandleFocusEvent((FocusChangeEvent *) event);
245 case EVENT_CLIENTMESSAGE:
246 HandleClientMessageEvent((ClientMessageEvent *) event);
249 #if defined(TARGET_SDL)
250 case SDL_JOYAXISMOTION:
251 case SDL_JOYBUTTONDOWN:
252 case SDL_JOYBUTTONUP:
253 HandleJoystickEvent(event);
257 HandleWindowManagerEvent(event);
266 void HandleMouseCursor()
268 if (game_status == GAME_MODE_TITLE)
270 /* when showing title screens, hide mouse pointer (if not moved) */
272 if (gfx.cursor_mode != CURSOR_NONE &&
273 DelayReached(&special_cursor_delay, special_cursor_delay_value))
275 SetMouseCursor(CURSOR_NONE);
278 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
281 /* when playing, display a special mouse pointer inside the playfield */
283 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
284 cursor_inside_playfield &&
285 DelayReached(&special_cursor_delay, special_cursor_delay_value))
287 SetMouseCursor(CURSOR_PLAYFIELD);
290 else if (gfx.cursor_mode != CURSOR_DEFAULT)
292 SetMouseCursor(CURSOR_DEFAULT);
295 /* this is set after all pending events have been processed */
296 cursor_mode_last = gfx.cursor_mode;
308 /* also execute after pending events have been processed before */
311 /* don't use all CPU time when idle; the main loop while playing
312 has its own synchronization and is CPU friendly, too */
314 if (game_status == GAME_MODE_PLAYING)
317 /* always copy backbuffer to visible screen for every video frame */
320 /* reset video frame delay to default (may change again while playing) */
321 SetVideoFrameDelay(MenuFrameDelay);
323 if (game_status == GAME_MODE_QUIT)
328 void ClearEventQueue()
330 while (PendingEvent())
338 case EVENT_BUTTONRELEASE:
339 button_status = MB_RELEASED;
342 case EVENT_KEYRELEASE:
347 HandleOtherEvents(&event);
353 void ClearPlayerAction()
357 /* simulate key release events for still pressed keys */
358 key_joystick_mapping = 0;
359 for (i = 0; i < MAX_PLAYERS; i++)
360 stored_player[i].action = 0;
363 void SleepWhileUnmapped()
365 boolean window_unmapped = TRUE;
367 KeyboardAutoRepeatOn();
369 while (window_unmapped)
377 case EVENT_BUTTONRELEASE:
378 button_status = MB_RELEASED;
381 case EVENT_KEYRELEASE:
382 key_joystick_mapping = 0;
385 case EVENT_MAPNOTIFY:
386 window_unmapped = FALSE;
389 case EVENT_UNMAPNOTIFY:
390 /* this is only to surely prevent the 'should not happen' case
391 * of recursively looping between 'SleepWhileUnmapped()' and
392 * 'HandleOtherEvents()' which usually calls this funtion.
397 HandleOtherEvents(&event);
402 if (game_status == GAME_MODE_PLAYING)
403 KeyboardAutoRepeatOffUnlessAutoplay();
406 void HandleExposeEvent(ExposeEvent *event)
410 void HandleButtonEvent(ButtonEvent *event)
412 #if DEBUG_EVENTS_BUTTON
413 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
415 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
419 #if defined(HAS_SCREEN_KEYBOARD)
420 if (video.shifted_up)
421 event->y += video.shifted_up_pos;
424 motion_status = FALSE;
426 if (event->type == EVENT_BUTTONPRESS)
427 button_status = event->button;
429 button_status = MB_RELEASED;
431 HandleButton(event->x, event->y, button_status, event->button);
434 void HandleMotionEvent(MotionEvent *event)
436 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
439 motion_status = TRUE;
441 #if DEBUG_EVENTS_MOTION
442 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
443 button_status, event->x, event->y);
446 HandleButton(event->x, event->y, button_status, button_status);
449 #if defined(TARGET_SDL2)
451 void HandleWheelEvent(WheelEvent *event)
455 #if DEBUG_EVENTS_WHEEL
457 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
458 event->which, event->x, event->y);
460 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
461 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
462 event->which, event->x, event->y,
463 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
464 "SDL_MOUSEWHEEL_FLIPPED"));
468 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
469 event->x > 0 ? MB_WHEEL_RIGHT :
470 event->y < 0 ? MB_WHEEL_DOWN :
471 event->y > 0 ? MB_WHEEL_UP : 0);
473 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
474 // accelerated mouse wheel available on Mac and Windows
475 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
477 // no accelerated mouse wheel available on Unix/Linux
478 wheel_steps = DEFAULT_WHEEL_STEPS;
481 motion_status = FALSE;
483 button_status = button_nr;
484 HandleButton(0, 0, button_status, -button_nr);
486 button_status = MB_RELEASED;
487 HandleButton(0, 0, button_status, -button_nr);
490 void HandleWindowEvent(WindowEvent *event)
492 #if DEBUG_EVENTS_WINDOW
493 int subtype = event->event;
496 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
497 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
498 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
499 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
500 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
501 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
502 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
503 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
504 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
505 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
506 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
507 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
508 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
509 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
512 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
513 event_name, event->data1, event->data2);
517 // (not needed, as the screen gets redrawn every 20 ms anyway)
518 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
519 event->event == SDL_WINDOWEVENT_RESIZED ||
520 event->event == SDL_WINDOWEVENT_EXPOSED)
524 if (event->event == SDL_WINDOWEVENT_RESIZED)
526 if (!video.fullscreen_enabled)
528 int new_window_width = event->data1;
529 int new_window_height = event->data2;
531 // if window size has changed after resizing, calculate new scaling factor
532 if (new_window_width != video.window_width ||
533 new_window_height != video.window_height)
535 int new_xpercent = (100 * new_window_width / video.screen_width);
536 int new_ypercent = (100 * new_window_height / video.screen_height);
538 // (extreme window scaling allowed, but cannot be saved permanently)
539 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
540 setup.window_scaling_percent =
541 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
542 MAX_WINDOW_SCALING_PERCENT);
544 video.window_width = new_window_width;
545 video.window_height = new_window_height;
547 if (game_status == GAME_MODE_SETUP)
548 RedrawSetupScreenAfterFullscreenToggle();
553 #if defined(PLATFORM_ANDROID)
556 int new_display_width = event->data1;
557 int new_display_height = event->data2;
559 // if fullscreen display size has changed, device has been rotated
560 if (new_display_width != video.display_width ||
561 new_display_height != video.display_height)
563 video.display_width = new_display_width;
564 video.display_height = new_display_height;
566 SDLSetScreenProperties();
573 #define NUM_TOUCH_FINGERS 3
578 SDL_FingerID finger_id;
581 } touch_info[NUM_TOUCH_FINGERS];
583 void HandleFingerEvent(FingerEvent *event)
585 static Key motion_key_x = KSYM_UNDEFINED;
586 static Key motion_key_y = KSYM_UNDEFINED;
587 static Key button_key = KSYM_UNDEFINED;
588 static float motion_x1, motion_y1;
589 static float button_x1, button_y1;
590 static SDL_FingerID motion_id = -1;
591 static SDL_FingerID button_id = -1;
592 int move_trigger_distance_percent = setup.touch.move_distance;
593 int drop_trigger_distance_percent = setup.touch.drop_distance;
594 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
595 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
596 float event_x = event->x;
597 float event_y = event->y;
599 #if DEBUG_EVENTS_FINGER
600 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
601 event->type == EVENT_FINGERPRESS ? "pressed" :
602 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
606 event->dx, event->dy,
610 if (game_status != GAME_MODE_PLAYING)
613 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
616 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
618 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
620 Key key = (event->x < 1.0 / 3.0 ?
621 (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
622 setup.input[0].key.drop) :
623 event->x > 2.0 / 3.0 ?
624 (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
625 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
626 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
627 setup.input[0].key.right) :
629 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
633 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
634 getKeyNameFromKey(key), key_status_name, event->fingerId);
636 // check if we already know this touch event's finger id
637 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
639 if (touch_info[i].touched &&
640 touch_info[i].finger_id == event->fingerId)
642 // Error(ERR_DEBUG, "MARK 1: %d", i);
648 if (i >= NUM_TOUCH_FINGERS)
650 if (key_status == KEY_PRESSED)
652 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
654 // unknown finger id -- get new, empty slot, if available
655 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
657 if (touch_info[i].counter < oldest_counter)
660 oldest_counter = touch_info[i].counter;
662 // Error(ERR_DEBUG, "MARK 2: %d", i);
665 if (!touch_info[i].touched)
667 // Error(ERR_DEBUG, "MARK 3: %d", i);
673 if (i >= NUM_TOUCH_FINGERS)
675 // all slots allocated -- use oldest slot
678 // Error(ERR_DEBUG, "MARK 4: %d", i);
683 // release of previously unknown key (should not happen)
685 if (key != KSYM_UNDEFINED)
687 HandleKey(key, KEY_RELEASED);
689 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
690 getKeyNameFromKey(key), "KEY_RELEASED", i);
695 if (i < NUM_TOUCH_FINGERS)
697 if (key_status == KEY_PRESSED)
699 if (touch_info[i].key != key)
701 if (touch_info[i].key != KSYM_UNDEFINED)
703 HandleKey(touch_info[i].key, KEY_RELEASED);
705 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
706 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
709 if (key != KSYM_UNDEFINED)
711 HandleKey(key, KEY_PRESSED);
713 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
714 getKeyNameFromKey(key), "KEY_PRESSED", i);
718 touch_info[i].touched = TRUE;
719 touch_info[i].finger_id = event->fingerId;
720 touch_info[i].counter = Counter();
721 touch_info[i].key = key;
725 if (touch_info[i].key != KSYM_UNDEFINED)
727 HandleKey(touch_info[i].key, KEY_RELEASED);
729 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
730 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
733 touch_info[i].touched = FALSE;
734 touch_info[i].finger_id = 0;
735 touch_info[i].counter = 0;
736 touch_info[i].key = 0;
743 // use touch direction control
745 if (event->type == EVENT_FINGERPRESS)
747 if (event_x > 1.0 / 3.0)
751 motion_id = event->fingerId;
756 motion_key_x = KSYM_UNDEFINED;
757 motion_key_y = KSYM_UNDEFINED;
759 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
765 button_id = event->fingerId;
770 button_key = setup.input[0].key.snap;
772 HandleKey(button_key, KEY_PRESSED);
774 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
777 else if (event->type == EVENT_FINGERRELEASE)
779 if (event->fingerId == motion_id)
783 if (motion_key_x != KSYM_UNDEFINED)
784 HandleKey(motion_key_x, KEY_RELEASED);
785 if (motion_key_y != KSYM_UNDEFINED)
786 HandleKey(motion_key_y, KEY_RELEASED);
788 motion_key_x = KSYM_UNDEFINED;
789 motion_key_y = KSYM_UNDEFINED;
791 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
793 else if (event->fingerId == button_id)
797 if (button_key != KSYM_UNDEFINED)
798 HandleKey(button_key, KEY_RELEASED);
800 button_key = KSYM_UNDEFINED;
802 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
805 else if (event->type == EVENT_FINGERMOTION)
807 if (event->fingerId == motion_id)
809 float distance_x = ABS(event_x - motion_x1);
810 float distance_y = ABS(event_y - motion_y1);
811 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
812 event_x > motion_x1 ? setup.input[0].key.right :
814 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
815 event_y > motion_y1 ? setup.input[0].key.down :
818 if (distance_x < move_trigger_distance / 2 ||
819 distance_x < distance_y)
820 new_motion_key_x = KSYM_UNDEFINED;
822 if (distance_y < move_trigger_distance / 2 ||
823 distance_y < distance_x)
824 new_motion_key_y = KSYM_UNDEFINED;
826 if (distance_x > move_trigger_distance ||
827 distance_y > move_trigger_distance)
829 if (new_motion_key_x != motion_key_x)
831 if (motion_key_x != KSYM_UNDEFINED)
832 HandleKey(motion_key_x, KEY_RELEASED);
833 if (new_motion_key_x != KSYM_UNDEFINED)
834 HandleKey(new_motion_key_x, KEY_PRESSED);
837 if (new_motion_key_y != motion_key_y)
839 if (motion_key_y != KSYM_UNDEFINED)
840 HandleKey(motion_key_y, KEY_RELEASED);
841 if (new_motion_key_y != KSYM_UNDEFINED)
842 HandleKey(new_motion_key_y, KEY_PRESSED);
848 motion_key_x = new_motion_key_x;
849 motion_key_y = new_motion_key_y;
851 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
854 else if (event->fingerId == button_id)
856 float distance_x = ABS(event_x - button_x1);
857 float distance_y = ABS(event_y - button_y1);
859 if (distance_x < drop_trigger_distance / 2 &&
860 distance_y > drop_trigger_distance)
862 if (button_key == setup.input[0].key.snap)
863 HandleKey(button_key, KEY_RELEASED);
868 button_key = setup.input[0].key.drop;
870 HandleKey(button_key, KEY_PRESSED);
872 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
878 static void HandleFollowFinger(int mx, int my, int button)
880 static int old_mx = 0, old_my = 0;
881 static Key motion_key_x = KSYM_UNDEFINED;
882 static Key motion_key_y = KSYM_UNDEFINED;
883 static boolean started_on_player = FALSE;
884 static boolean player_is_dropping = FALSE;
885 static int player_drop_count = 0;
886 static int last_player_x = -1;
887 static int last_player_y = -1;
889 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
892 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
894 touch_info[0].touched = TRUE;
895 touch_info[0].key = 0;
902 started_on_player = FALSE;
903 player_is_dropping = FALSE;
904 player_drop_count = 0;
908 motion_key_x = KSYM_UNDEFINED;
909 motion_key_y = KSYM_UNDEFINED;
911 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
914 else if (button == MB_RELEASED && touch_info[0].touched)
916 touch_info[0].touched = FALSE;
917 touch_info[0].key = 0;
922 if (motion_key_x != KSYM_UNDEFINED)
923 HandleKey(motion_key_x, KEY_RELEASED);
924 if (motion_key_y != KSYM_UNDEFINED)
925 HandleKey(motion_key_y, KEY_RELEASED);
927 if (started_on_player)
929 if (player_is_dropping)
931 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
933 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
937 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
939 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
943 motion_key_x = KSYM_UNDEFINED;
944 motion_key_y = KSYM_UNDEFINED;
946 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
949 if (touch_info[0].touched)
951 int src_x = local_player->jx;
952 int src_y = local_player->jy;
953 int dst_x = getLevelFromScreenX(old_mx);
954 int dst_y = getLevelFromScreenY(old_my);
955 int dx = dst_x - src_x;
956 int dy = dst_y - src_y;
957 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
958 dx > 0 ? setup.input[0].key.right :
960 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
961 dy > 0 ? setup.input[0].key.down :
964 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
965 (last_player_x != local_player->jx ||
966 last_player_y != local_player->jy))
968 // in case of asymmetric diagonal movement, use "preferred" direction
970 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
972 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
973 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
975 local_player->last_move_dir = last_move_dir;
977 // (required to prevent accidentally forcing direction for next movement)
978 last_player_x = local_player->jx;
979 last_player_y = local_player->jy;
982 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
984 started_on_player = TRUE;
985 player_drop_count = getPlayerInventorySize(0);
986 player_is_dropping = (player_drop_count > 0);
988 if (player_is_dropping)
990 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
992 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
996 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
998 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1001 else if (dx != 0 || dy != 0)
1003 if (player_is_dropping &&
1004 player_drop_count == getPlayerInventorySize(0))
1006 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1008 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1009 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1011 player_is_dropping = FALSE;
1015 if (new_motion_key_x != motion_key_x)
1017 Error(ERR_DEBUG, "---------- %s %s ----------",
1018 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1019 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1021 if (motion_key_x != KSYM_UNDEFINED)
1022 HandleKey(motion_key_x, KEY_RELEASED);
1023 if (new_motion_key_x != KSYM_UNDEFINED)
1024 HandleKey(new_motion_key_x, KEY_PRESSED);
1027 if (new_motion_key_y != motion_key_y)
1029 Error(ERR_DEBUG, "---------- %s %s ----------",
1030 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1031 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1033 if (motion_key_y != KSYM_UNDEFINED)
1034 HandleKey(motion_key_y, KEY_RELEASED);
1035 if (new_motion_key_y != KSYM_UNDEFINED)
1036 HandleKey(new_motion_key_y, KEY_PRESSED);
1039 motion_key_x = new_motion_key_x;
1040 motion_key_y = new_motion_key_y;
1044 static boolean checkTextInputKeyModState()
1046 // when playing, only handle raw key events and ignore text input
1047 if (game_status == GAME_MODE_PLAYING)
1050 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1053 void HandleTextEvent(TextEvent *event)
1055 char *text = event->text;
1056 Key key = getKeyFromKeyName(text);
1058 #if DEBUG_EVENTS_TEXT
1059 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1062 text[0], (int)(text[0]),
1064 getKeyNameFromKey(key),
1068 #if !defined(HAS_SCREEN_KEYBOARD)
1069 // non-mobile devices: only handle key input with modifier keys pressed here
1070 // (every other key input is handled directly as physical key input event)
1071 if (!checkTextInputKeyModState())
1075 // process text input as "classic" (with uppercase etc.) key input event
1076 HandleKey(key, KEY_PRESSED);
1077 HandleKey(key, KEY_RELEASED);
1080 void HandlePauseResumeEvent(PauseResumeEvent *event)
1082 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1086 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1094 void HandleKeyEvent(KeyEvent *event)
1096 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1097 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1098 Key key = GetEventKey(event, with_modifiers);
1099 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1101 #if DEBUG_EVENTS_KEY
1102 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1103 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1104 event->keysym.scancode,
1109 getKeyNameFromKey(key));
1112 #if defined(PLATFORM_ANDROID)
1113 // always map the "back" button to the "escape" key on Android devices
1114 if (key == KSYM_Back)
1118 HandleKeyModState(keymod, key_status);
1120 #if defined(TARGET_SDL2)
1121 // only handle raw key input without text modifier keys pressed
1122 if (!checkTextInputKeyModState())
1123 HandleKey(key, key_status);
1125 HandleKey(key, key_status);
1129 void HandleFocusEvent(FocusChangeEvent *event)
1131 static int old_joystick_status = -1;
1133 if (event->type == EVENT_FOCUSOUT)
1135 KeyboardAutoRepeatOn();
1136 old_joystick_status = joystick.status;
1137 joystick.status = JOYSTICK_NOT_AVAILABLE;
1139 ClearPlayerAction();
1141 else if (event->type == EVENT_FOCUSIN)
1143 /* When there are two Rocks'n'Diamonds windows which overlap and
1144 the player moves the pointer from one game window to the other,
1145 a 'FocusOut' event is generated for the window the pointer is
1146 leaving and a 'FocusIn' event is generated for the window the
1147 pointer is entering. In some cases, it can happen that the
1148 'FocusIn' event is handled by the one game process before the
1149 'FocusOut' event by the other game process. In this case the
1150 X11 environment would end up with activated keyboard auto repeat,
1151 because unfortunately this is a global setting and not (which
1152 would be far better) set for each X11 window individually.
1153 The effect would be keyboard auto repeat while playing the game
1154 (game_status == GAME_MODE_PLAYING), which is not desired.
1155 To avoid this special case, we just wait 1/10 second before
1156 processing the 'FocusIn' event.
1159 if (game_status == GAME_MODE_PLAYING)
1162 KeyboardAutoRepeatOffUnlessAutoplay();
1165 if (old_joystick_status != -1)
1166 joystick.status = old_joystick_status;
1170 void HandleClientMessageEvent(ClientMessageEvent *event)
1172 if (CheckCloseWindowEvent(event))
1176 void HandleWindowManagerEvent(Event *event)
1178 #if defined(TARGET_SDL)
1179 SDLHandleWindowManagerEvent(event);
1183 void HandleButton(int mx, int my, int button, int button_nr)
1185 static int old_mx = 0, old_my = 0;
1186 boolean button_hold = FALSE;
1192 button_nr = -button_nr;
1201 #if defined(PLATFORM_ANDROID)
1202 // when playing, only handle gadgets when using "follow finger" controls
1203 boolean handle_gadgets =
1204 (game_status != GAME_MODE_PLAYING ||
1205 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1207 if (handle_gadgets &&
1208 HandleGadgets(mx, my, button))
1210 /* do not handle this button event anymore */
1211 mx = my = -32; /* force mouse event to be outside screen tiles */
1214 if (HandleGadgets(mx, my, button))
1216 /* do not handle this button event anymore */
1217 mx = my = -32; /* force mouse event to be outside screen tiles */
1221 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1224 /* do not use scroll wheel button events for anything other than gadgets */
1225 if (IS_WHEEL_BUTTON(button_nr))
1228 switch (game_status)
1230 case GAME_MODE_TITLE:
1231 HandleTitleScreen(mx, my, 0, 0, button);
1234 case GAME_MODE_MAIN:
1235 HandleMainMenu(mx, my, 0, 0, button);
1238 case GAME_MODE_PSEUDO_TYPENAME:
1239 HandleTypeName(0, KSYM_Return);
1242 case GAME_MODE_LEVELS:
1243 HandleChooseLevelSet(mx, my, 0, 0, button);
1246 case GAME_MODE_LEVELNR:
1247 HandleChooseLevelNr(mx, my, 0, 0, button);
1250 case GAME_MODE_SCORES:
1251 HandleHallOfFame(0, 0, 0, 0, button);
1254 case GAME_MODE_EDITOR:
1255 HandleLevelEditorIdle();
1258 case GAME_MODE_INFO:
1259 HandleInfoScreen(mx, my, 0, 0, button);
1262 case GAME_MODE_SETUP:
1263 HandleSetupScreen(mx, my, 0, 0, button);
1266 #if defined(TARGET_SDL2)
1267 case GAME_MODE_PLAYING:
1268 HandleFollowFinger(mx, my, button);
1272 if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my) &&
1273 GetKeyModState() & KMOD_Control)
1274 DumpTileFromScreen(mx, my);
1284 static boolean is_string_suffix(char *string, char *suffix)
1286 int string_len = strlen(string);
1287 int suffix_len = strlen(suffix);
1289 if (suffix_len > string_len)
1292 return (strEqual(&string[string_len - suffix_len], suffix));
1295 #define MAX_CHEAT_INPUT_LEN 32
1297 static void HandleKeysSpecial(Key key)
1299 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1300 char letter = getCharFromKey(key);
1301 int cheat_input_len = strlen(cheat_input);
1307 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1309 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1310 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1312 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1315 cheat_input[cheat_input_len++] = letter;
1316 cheat_input[cheat_input_len] = '\0';
1318 #if DEBUG_EVENTS_KEY
1319 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1322 if (game_status == GAME_MODE_MAIN)
1324 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1325 is_string_suffix(cheat_input, ":ist"))
1327 InsertSolutionTape();
1329 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1330 is_string_suffix(cheat_input, ":rg"))
1332 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1335 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1336 is_string_suffix(cheat_input, ":rs"))
1338 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1341 else if (is_string_suffix(cheat_input, ":reload-music") ||
1342 is_string_suffix(cheat_input, ":rm"))
1344 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1347 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1348 is_string_suffix(cheat_input, ":ra"))
1350 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1351 1 << ARTWORK_TYPE_SOUNDS |
1352 1 << ARTWORK_TYPE_MUSIC);
1355 else if (is_string_suffix(cheat_input, ":dump-level") ||
1356 is_string_suffix(cheat_input, ":dl"))
1360 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1361 is_string_suffix(cheat_input, ":dt"))
1365 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1366 is_string_suffix(cheat_input, ":ft"))
1368 /* fix single-player tapes that contain player input for more than one
1369 player (due to a bug in 3.3.1.2 and earlier versions), which results
1370 in playing levels with more than one player in multi-player mode,
1371 even though the tape was originally recorded in single-player mode */
1373 /* remove player input actions for all players but the first one */
1374 for (i = 1; i < MAX_PLAYERS; i++)
1375 tape.player_participates[i] = FALSE;
1377 tape.changed = TRUE;
1379 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1380 is_string_suffix(cheat_input, ":snl"))
1382 SaveNativeLevel(&level);
1385 else if (game_status == GAME_MODE_PLAYING)
1388 if (is_string_suffix(cheat_input, ".q"))
1389 DEBUG_SetMaximumDynamite();
1392 else if (game_status == GAME_MODE_EDITOR)
1394 if (is_string_suffix(cheat_input, ":dump-brush") ||
1395 is_string_suffix(cheat_input, ":DB"))
1399 else if (is_string_suffix(cheat_input, ":DDB"))
1406 void HandleKeysDebug(Key key)
1411 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1413 boolean mod_key_pressed = (GetKeyModState() != KMOD_None);
1415 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1417 if (key == setup.debug.frame_delay_key[i] &&
1418 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1420 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1421 setup.debug.frame_delay[i] : GAME_FRAME_DELAY);
1423 if (!setup.debug.frame_delay_game_only)
1424 MenuFrameDelay = GameFrameDelay;
1426 SetVideoFrameDelay(GameFrameDelay);
1428 if (GameFrameDelay > ONE_SECOND_DELAY)
1429 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1430 else if (GameFrameDelay != 0)
1431 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1432 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1433 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1435 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1442 if (game_status == GAME_MODE_PLAYING)
1446 options.debug = !options.debug;
1448 Error(ERR_DEBUG, "debug mode %s",
1449 (options.debug ? "enabled" : "disabled"));
1451 else if (key == KSYM_v)
1453 Error(ERR_DEBUG, "currently using game engine version %d",
1454 game.engine_version);
1460 void HandleKey(Key key, int key_status)
1462 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1463 static boolean ignore_repeated_key = FALSE;
1464 static struct SetupKeyboardInfo ski;
1465 static struct SetupShortcutInfo ssi;
1474 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1475 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1476 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1477 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1478 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1479 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1484 if (game_status == GAME_MODE_PLAYING)
1486 /* only needed for single-step tape recording mode */
1487 static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1488 static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1489 static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1490 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1493 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1495 byte key_action = 0;
1497 if (setup.input[pnr].use_joystick)
1500 ski = setup.input[pnr].key;
1502 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1503 if (key == *key_info[i].key_custom)
1504 key_action |= key_info[i].action;
1506 /* use combined snap+direction keys for the first player only */
1509 ssi = setup.shortcut;
1511 for (i = 0; i < NUM_DIRECTIONS; i++)
1512 if (key == *key_info[i].key_snap)
1513 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1516 /* clear delayed snap and drop actions in single step mode (see below) */
1517 if (tape.single_step)
1519 if (clear_snap_button[pnr])
1521 stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1522 clear_snap_button[pnr] = FALSE;
1525 if (clear_drop_button[pnr])
1527 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1528 clear_drop_button[pnr] = FALSE;
1532 if (key_status == KEY_PRESSED)
1533 stored_player[pnr].action |= key_action;
1535 stored_player[pnr].action &= ~key_action;
1537 if (tape.single_step && tape.recording && tape.pausing)
1539 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1541 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1543 /* if snap key already pressed, don't snap when releasing (below) */
1544 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1545 element_snapped[pnr] = TRUE;
1547 /* if drop key already pressed, don't drop when releasing (below) */
1548 if (stored_player[pnr].action & KEY_BUTTON_DROP)
1549 element_dropped[pnr] = TRUE;
1551 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1553 if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1554 level.game_engine_type == GAME_ENGINE_TYPE_SP)
1557 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1558 getRedDiskReleaseFlag_SP() == 0)
1559 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1561 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1564 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1566 if (key_action & KEY_BUTTON_SNAP)
1568 /* if snap key was released without moving (see above), snap now */
1569 if (!element_snapped[pnr])
1571 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1573 stored_player[pnr].action |= KEY_BUTTON_SNAP;
1575 /* clear delayed snap button on next event */
1576 clear_snap_button[pnr] = TRUE;
1579 element_snapped[pnr] = FALSE;
1582 if (key_action & KEY_BUTTON_DROP &&
1583 level.game_engine_type == GAME_ENGINE_TYPE_RND)
1585 /* if drop key was released without moving (see above), drop now */
1586 if (!element_dropped[pnr])
1588 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1590 if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1591 getRedDiskReleaseFlag_SP() != 0)
1592 stored_player[pnr].action |= KEY_BUTTON_DROP;
1594 /* clear delayed drop button on next event */
1595 clear_drop_button[pnr] = TRUE;
1598 element_dropped[pnr] = FALSE;
1602 else if (tape.recording && tape.pausing)
1604 /* prevent key release events from un-pausing a paused game */
1605 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1606 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1612 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1613 if (key == key_info[i].key_default)
1614 joy |= key_info[i].action;
1619 if (key_status == KEY_PRESSED)
1620 key_joystick_mapping |= joy;
1622 key_joystick_mapping &= ~joy;
1627 if (game_status != GAME_MODE_PLAYING)
1628 key_joystick_mapping = 0;
1630 if (key_status == KEY_RELEASED)
1632 // reset flag to ignore repeated "key pressed" events after key release
1633 ignore_repeated_key = FALSE;
1638 if ((key == KSYM_F11 ||
1639 ((key == KSYM_Return ||
1640 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1641 video.fullscreen_available &&
1642 !ignore_repeated_key)
1644 setup.fullscreen = !setup.fullscreen;
1646 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1648 if (game_status == GAME_MODE_SETUP)
1649 RedrawSetupScreenAfterFullscreenToggle();
1651 // set flag to ignore repeated "key pressed" events
1652 ignore_repeated_key = TRUE;
1657 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1658 key == KSYM_minus || key == KSYM_KP_Subtract ||
1659 key == KSYM_plus || key == KSYM_KP_Add ||
1660 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1661 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1662 video.window_scaling_available &&
1663 !video.fullscreen_enabled)
1665 if (key == KSYM_0 || key == KSYM_KP_0)
1666 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1667 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1668 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1670 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1672 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1673 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1674 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1675 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1677 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1679 if (game_status == GAME_MODE_SETUP)
1680 RedrawSetupScreenAfterFullscreenToggle();
1685 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1686 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1693 if (game_status == GAME_MODE_MAIN &&
1694 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1696 StartGameActions(options.network, setup.autorecord, level.random_seed);
1701 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1703 if (key == setup.shortcut.save_game)
1705 else if (key == setup.shortcut.load_game)
1707 else if (key == setup.shortcut.toggle_pause)
1708 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1710 HandleTapeButtonKeys(key);
1711 HandleSoundButtonKeys(key);
1714 if (game_status == GAME_MODE_PLAYING && !network_playing)
1716 int centered_player_nr_next = -999;
1718 if (key == setup.shortcut.focus_player_all)
1719 centered_player_nr_next = -1;
1721 for (i = 0; i < MAX_PLAYERS; i++)
1722 if (key == setup.shortcut.focus_player[i])
1723 centered_player_nr_next = i;
1725 if (centered_player_nr_next != -999)
1727 game.centered_player_nr_next = centered_player_nr_next;
1728 game.set_centered_player = TRUE;
1732 tape.centered_player_nr_next = game.centered_player_nr_next;
1733 tape.set_centered_player = TRUE;
1738 HandleKeysSpecial(key);
1740 if (HandleGadgetsKeyInput(key))
1742 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1743 key = KSYM_UNDEFINED;
1746 switch (game_status)
1748 case GAME_MODE_PSEUDO_TYPENAME:
1749 HandleTypeName(0, key);
1752 case GAME_MODE_TITLE:
1753 case GAME_MODE_MAIN:
1754 case GAME_MODE_LEVELS:
1755 case GAME_MODE_LEVELNR:
1756 case GAME_MODE_SETUP:
1757 case GAME_MODE_INFO:
1758 case GAME_MODE_SCORES:
1763 if (game_status == GAME_MODE_TITLE)
1764 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1765 else if (game_status == GAME_MODE_MAIN)
1766 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1767 else if (game_status == GAME_MODE_LEVELS)
1768 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1769 else if (game_status == GAME_MODE_LEVELNR)
1770 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1771 else if (game_status == GAME_MODE_SETUP)
1772 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1773 else if (game_status == GAME_MODE_INFO)
1774 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1775 else if (game_status == GAME_MODE_SCORES)
1776 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1780 if (game_status != GAME_MODE_MAIN)
1781 FadeSkipNextFadeIn();
1783 if (game_status == GAME_MODE_TITLE)
1784 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1785 else if (game_status == GAME_MODE_LEVELS)
1786 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1787 else if (game_status == GAME_MODE_LEVELNR)
1788 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1789 else if (game_status == GAME_MODE_SETUP)
1790 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1791 else if (game_status == GAME_MODE_INFO)
1792 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1793 else if (game_status == GAME_MODE_SCORES)
1794 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1798 if (game_status == GAME_MODE_LEVELS)
1799 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1800 else if (game_status == GAME_MODE_LEVELNR)
1801 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1802 else if (game_status == GAME_MODE_SETUP)
1803 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1804 else if (game_status == GAME_MODE_INFO)
1805 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1806 else if (game_status == GAME_MODE_SCORES)
1807 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1810 case KSYM_Page_Down:
1811 if (game_status == GAME_MODE_LEVELS)
1812 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1813 else if (game_status == GAME_MODE_LEVELNR)
1814 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1815 else if (game_status == GAME_MODE_SETUP)
1816 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1817 else if (game_status == GAME_MODE_INFO)
1818 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1819 else if (game_status == GAME_MODE_SCORES)
1820 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1828 case GAME_MODE_EDITOR:
1829 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1830 HandleLevelEditorKeyInput(key);
1833 case GAME_MODE_PLAYING:
1838 RequestQuitGame(setup.ask_on_escape);
1848 if (key == KSYM_Escape)
1850 SetGameStatus(GAME_MODE_MAIN);
1858 HandleKeysDebug(key);
1861 void HandleNoEvent()
1863 // if (button_status && game_status != GAME_MODE_PLAYING)
1864 if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1866 HandleButton(0, 0, button_status, -button_status);
1873 #if defined(NETWORK_AVALIABLE)
1874 if (options.network)
1878 switch (game_status)
1880 case GAME_MODE_MAIN:
1881 DrawPreviewLevelAnimation();
1884 case GAME_MODE_EDITOR:
1885 HandleLevelEditorIdle();
1888 #if defined(TARGET_SDL2)
1889 case GAME_MODE_PLAYING:
1890 HandleFollowFinger(-1, -1, -1);
1899 static int HandleJoystickForAllPlayers()
1904 for (i = 0; i < MAX_PLAYERS; i++)
1906 byte joy_action = 0;
1909 if (!setup.input[i].use_joystick)
1913 joy_action = Joystick(i);
1914 result |= joy_action;
1916 if (!setup.input[i].use_joystick)
1919 stored_player[i].action = joy_action;
1925 void HandleJoystick()
1927 int joystick = HandleJoystickForAllPlayers();
1928 int keyboard = key_joystick_mapping;
1929 int joy = (joystick | keyboard);
1930 int left = joy & JOY_LEFT;
1931 int right = joy & JOY_RIGHT;
1932 int up = joy & JOY_UP;
1933 int down = joy & JOY_DOWN;
1934 int button = joy & JOY_BUTTON;
1935 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1936 int dx = (left ? -1 : right ? 1 : 0);
1937 int dy = (up ? -1 : down ? 1 : 0);
1939 switch (game_status)
1941 case GAME_MODE_TITLE:
1942 case GAME_MODE_MAIN:
1943 case GAME_MODE_LEVELS:
1944 case GAME_MODE_LEVELNR:
1945 case GAME_MODE_SETUP:
1946 case GAME_MODE_INFO:
1948 static unsigned int joystickmove_delay = 0;
1950 if (joystick && !button &&
1951 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1952 newbutton = dx = dy = 0;
1954 if (game_status == GAME_MODE_TITLE)
1955 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1956 else if (game_status == GAME_MODE_MAIN)
1957 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1958 else if (game_status == GAME_MODE_LEVELS)
1959 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1960 else if (game_status == GAME_MODE_LEVELNR)
1961 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1962 else if (game_status == GAME_MODE_SETUP)
1963 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1964 else if (game_status == GAME_MODE_INFO)
1965 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1969 case GAME_MODE_SCORES:
1970 HandleHallOfFame(0, 0, dx, dy, !newbutton);
1973 case GAME_MODE_PLAYING:
1974 if (tape.playing || keyboard)
1975 newbutton = ((joy & JOY_BUTTON) != 0);
1977 if (newbutton && AllPlayersGone)