1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://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;
41 static boolean special_cursor_enabled = FALSE;
43 static boolean stop_processing_events = FALSE;
46 // forward declarations for internal use
47 static void ClearTouchInfo(void);
48 static void HandleNoEvent(void);
49 static void HandleEventActions(void);
52 void SetPlayfieldMouseCursorEnabled(boolean enabled)
54 special_cursor_enabled = enabled;
57 // event filter to set mouse x/y position (for pointer class global animations)
58 // (this is especially required to ensure smooth global animation mouse pointer
59 // movement when the screen is updated without handling events; this can happen
60 // when drawing door/envelope request animations, for example)
62 int FilterMouseMotionEvents(void *userdata, Event *event)
64 if (event->type == EVENT_MOTIONNOTIFY)
66 int mouse_x = ((MotionEvent *)event)->x;
67 int mouse_y = ((MotionEvent *)event)->y;
69 UpdateRawMousePosition(mouse_x, mouse_y);
75 // event filter especially needed for SDL event filtering due to
76 // delay problems with lots of mouse motion events when mouse button
77 // not pressed (X11 can handle this with 'PointerMotionHintMask')
79 // event filter addition for SDL2: as SDL2 does not have a function to enable
80 // or disable keyboard auto-repeat, filter repeated keyboard events instead
82 static int FilterEvents(const Event *event)
86 // skip repeated key press events if keyboard auto-repeat is disabled
87 if (event->type == EVENT_KEYPRESS &&
92 if (event->type == EVENT_BUTTONPRESS ||
93 event->type == EVENT_BUTTONRELEASE)
95 ((ButtonEvent *)event)->x -= video.screen_xoffset;
96 ((ButtonEvent *)event)->y -= video.screen_yoffset;
98 else if (event->type == EVENT_MOTIONNOTIFY)
100 ((MotionEvent *)event)->x -= video.screen_xoffset;
101 ((MotionEvent *)event)->y -= video.screen_yoffset;
104 if (event->type == EVENT_BUTTONPRESS ||
105 event->type == EVENT_BUTTONRELEASE ||
106 event->type == EVENT_MOTIONNOTIFY)
108 // do not reset mouse cursor before all pending events have been processed
109 if (gfx.cursor_mode == cursor_mode_last &&
110 ((game_status == GAME_MODE_TITLE &&
111 gfx.cursor_mode == CURSOR_NONE) ||
112 (game_status == GAME_MODE_PLAYING &&
113 gfx.cursor_mode == CURSOR_PLAYFIELD)))
115 SetMouseCursor(CURSOR_DEFAULT);
117 ResetDelayCounter(&special_cursor_delay);
119 cursor_mode_last = CURSOR_DEFAULT;
123 // non-motion events are directly passed to event handler functions
124 if (event->type != EVENT_MOTIONNOTIFY)
127 motion = (MotionEvent *)event;
128 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
129 motion->y >= SY && motion->y < SY + SYSIZE);
131 // set correct mouse x/y position (for pointer class global animations)
132 // (this is required in rare cases where the mouse x/y position calculated
133 // from raw values (to apply logical screen size scaling corrections) does
134 // not match the final mouse event x/y position -- this may happen because
135 // the SDL renderer's viewport position is internally represented as float,
136 // but only accessible as integer, which may lead to rounding errors)
137 gfx.mouse_x = motion->x;
138 gfx.mouse_y = motion->y;
140 // skip mouse motion events without pressed button outside level editor
141 if (button_status == MB_RELEASED &&
142 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
148 // to prevent delay problems, skip mouse motion events if the very next
149 // event is also a mouse motion event (and therefore effectively only
150 // handling the last of a row of mouse motion events in the event queue)
152 static boolean SkipPressedMouseMotionEvent(const Event *event)
154 // nothing to do if the current event is not a mouse motion event
155 if (event->type != EVENT_MOTIONNOTIFY)
158 // only skip motion events with pressed button outside the game
159 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
166 PeekEvent(&next_event);
168 // if next event is also a mouse motion event, skip the current one
169 if (next_event.type == EVENT_MOTIONNOTIFY)
176 static boolean WaitValidEvent(Event *event)
180 if (!FilterEvents(event))
183 if (SkipPressedMouseMotionEvent(event))
189 /* this is especially needed for event modifications for the Android target:
190 if mouse coordinates should be modified in the event filter function,
191 using a properly installed SDL event filter does not work, because in
192 the event filter, mouse coordinates in the event structure are still
193 physical pixel positions, not logical (scaled) screen positions, so this
194 has to be handled at a later stage in the event processing functions
195 (when device pixel positions are already converted to screen positions) */
197 boolean NextValidEvent(Event *event)
199 while (PendingEvent())
200 if (WaitValidEvent(event))
206 void StopProcessingEvents(void)
208 stop_processing_events = TRUE;
211 static void HandleEvents(void)
214 unsigned int event_frame_delay = 0;
215 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
217 ResetDelayCounter(&event_frame_delay);
219 stop_processing_events = FALSE;
221 while (NextValidEvent(&event))
223 int game_status_last = game_status;
227 case EVENT_BUTTONPRESS:
228 case EVENT_BUTTONRELEASE:
229 HandleButtonEvent((ButtonEvent *) &event);
232 case EVENT_MOTIONNOTIFY:
233 HandleMotionEvent((MotionEvent *) &event);
236 case EVENT_WHEELMOTION:
237 HandleWheelEvent((WheelEvent *) &event);
240 case SDL_WINDOWEVENT:
241 HandleWindowEvent((WindowEvent *) &event);
244 case EVENT_FINGERPRESS:
245 case EVENT_FINGERRELEASE:
246 case EVENT_FINGERMOTION:
247 HandleFingerEvent((FingerEvent *) &event);
250 case EVENT_TEXTINPUT:
251 HandleTextEvent((TextEvent *) &event);
254 case SDL_APP_WILLENTERBACKGROUND:
255 case SDL_APP_DIDENTERBACKGROUND:
256 case SDL_APP_WILLENTERFOREGROUND:
257 case SDL_APP_DIDENTERFOREGROUND:
258 HandlePauseResumeEvent((PauseResumeEvent *) &event);
262 case EVENT_KEYRELEASE:
263 HandleKeyEvent((KeyEvent *) &event);
267 HandleUserEvent((UserEvent *) &event);
271 HandleOtherEvents(&event);
275 // always handle events within delay period if game status has changed
276 if (game_status != game_status_last)
277 ResetDelayCounter(&event_frame_delay);
279 // do not handle events for longer than standard frame delay period
280 if (DelayReached(&event_frame_delay, event_frame_delay_value))
283 // do not handle any further events if triggered by a special flag
284 if (stop_processing_events)
289 void HandleOtherEvents(Event *event)
293 case SDL_CONTROLLERBUTTONDOWN:
294 case SDL_CONTROLLERBUTTONUP:
295 // for any game controller button event, disable overlay buttons
296 SetOverlayEnabled(FALSE);
298 HandleSpecialGameControllerButtons(event);
301 case SDL_CONTROLLERDEVICEADDED:
302 case SDL_CONTROLLERDEVICEREMOVED:
303 case SDL_CONTROLLERAXISMOTION:
304 case SDL_JOYAXISMOTION:
305 case SDL_JOYBUTTONDOWN:
306 case SDL_JOYBUTTONUP:
307 HandleJoystickEvent(event);
311 case SDL_DROPCOMPLETE:
314 HandleDropEvent(event);
326 static void HandleMouseCursor(void)
328 if (game_status == GAME_MODE_TITLE)
330 // when showing title screens, hide mouse pointer (if not moved)
332 if (gfx.cursor_mode != CURSOR_NONE &&
333 DelayReached(&special_cursor_delay, special_cursor_delay_value))
335 SetMouseCursor(CURSOR_NONE);
338 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
341 // when playing, display a special mouse pointer inside the playfield
343 // display normal pointer if mouse pressed
344 if (button_status != MB_RELEASED)
345 ResetDelayCounter(&special_cursor_delay);
347 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
348 cursor_inside_playfield &&
349 special_cursor_enabled &&
350 DelayReached(&special_cursor_delay, special_cursor_delay_value))
352 SetMouseCursor(CURSOR_PLAYFIELD);
355 else if (gfx.cursor_mode != CURSOR_DEFAULT)
357 SetMouseCursor(CURSOR_DEFAULT);
360 // this is set after all pending events have been processed
361 cursor_mode_last = gfx.cursor_mode;
373 // execute event related actions after pending events have been processed
374 HandleEventActions();
376 // don't use all CPU time when idle; the main loop while playing
377 // has its own synchronization and is CPU friendly, too
379 if (game_status == GAME_MODE_PLAYING)
382 // always copy backbuffer to visible screen for every video frame
385 // reset video frame delay to default (may change again while playing)
386 SetVideoFrameDelay(MenuFrameDelay);
388 if (game_status == GAME_MODE_QUIT)
393 void ClearAutoRepeatKeyEvents(void)
395 while (PendingEvent())
399 PeekEvent(&next_event);
401 // if event is repeated key press event, remove it from event queue
402 if (next_event.type == EVENT_KEYPRESS &&
403 next_event.key.repeat)
404 WaitEvent(&next_event);
410 void ClearEventQueue(void)
414 while (NextValidEvent(&event))
418 case EVENT_BUTTONRELEASE:
419 button_status = MB_RELEASED;
422 case EVENT_FINGERRELEASE:
423 case EVENT_KEYRELEASE:
427 case SDL_CONTROLLERBUTTONUP:
428 HandleJoystickEvent(&event);
433 HandleOtherEvents(&event);
439 static void ClearPlayerMouseAction(void)
441 local_player->mouse_action.lx = 0;
442 local_player->mouse_action.ly = 0;
443 local_player->mouse_action.button = 0;
446 void ClearPlayerAction(void)
450 // simulate key release events for still pressed keys
451 key_joystick_mapping = 0;
452 for (i = 0; i < MAX_PLAYERS; i++)
454 stored_player[i].action = 0;
455 stored_player[i].snap_action = 0;
458 // simulate finger release events for still pressed virtual buttons
459 overlay.grid_button_action = JOY_NO_ACTION;
462 ClearJoystickState();
463 ClearPlayerMouseAction();
466 static void SetPlayerMouseAction(int mx, int my, int button)
468 int lx = getLevelFromScreenX(mx);
469 int ly = getLevelFromScreenY(my);
470 int new_button = (!local_player->mouse_action.button && button);
472 if (local_player->mouse_action.button_hint)
473 button = local_player->mouse_action.button_hint;
475 ClearPlayerMouseAction();
477 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
480 local_player->mouse_action.lx = lx;
481 local_player->mouse_action.ly = ly;
482 local_player->mouse_action.button = button;
484 if (tape.recording && tape.pausing && tape.use_mouse_actions)
486 // un-pause a paused game only if mouse button was newly pressed down
488 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
491 SetTileCursorXY(lx, ly);
494 static Key GetKeyFromGridButton(int grid_button)
496 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
497 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
498 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
499 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
500 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
501 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
505 #if defined(PLATFORM_ANDROID)
506 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
508 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
509 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
510 int x = touch_x * overlay.grid_xsize;
511 int y = touch_y * overlay.grid_ysize;
512 int grid_button = overlay.grid_button[x][y];
513 Key key = GetKeyFromGridButton(grid_button);
514 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
516 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
520 void HandleButtonEvent(ButtonEvent *event)
522 #if DEBUG_EVENTS_BUTTON
523 Debug("event:button", "button %d %s, x/y %d/%d\n",
525 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
529 // for any mouse button event, disable playfield tile cursor
530 SetTileCursorEnabled(FALSE);
532 // for any mouse button event, disable playfield mouse cursor
533 if (cursor_inside_playfield)
534 SetPlayfieldMouseCursorEnabled(FALSE);
536 #if defined(HAS_SCREEN_KEYBOARD)
537 if (video.shifted_up)
538 event->y += video.shifted_up_pos;
541 motion_status = FALSE;
543 if (event->type == EVENT_BUTTONPRESS)
544 button_status = event->button;
546 button_status = MB_RELEASED;
548 HandleButton(event->x, event->y, button_status, event->button);
551 void HandleMotionEvent(MotionEvent *event)
553 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
556 motion_status = TRUE;
558 #if DEBUG_EVENTS_MOTION
559 Debug("event:motion", "button %d moved, x/y %d/%d\n",
560 button_status, event->x, event->y);
563 HandleButton(event->x, event->y, button_status, button_status);
566 void HandleWheelEvent(WheelEvent *event)
570 #if DEBUG_EVENTS_WHEEL
572 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
573 event->which, event->x, event->y);
575 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
576 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
577 event->which, event->x, event->y,
578 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
579 "SDL_MOUSEWHEEL_FLIPPED"));
583 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
584 event->x > 0 ? MB_WHEEL_RIGHT :
585 event->y < 0 ? MB_WHEEL_DOWN :
586 event->y > 0 ? MB_WHEEL_UP : 0);
588 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
589 // accelerated mouse wheel available on Mac and Windows
590 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
592 // no accelerated mouse wheel available on Unix/Linux
593 wheel_steps = DEFAULT_WHEEL_STEPS;
596 motion_status = FALSE;
598 button_status = button_nr;
599 HandleButton(0, 0, button_status, -button_nr);
601 button_status = MB_RELEASED;
602 HandleButton(0, 0, button_status, -button_nr);
605 void HandleWindowEvent(WindowEvent *event)
607 #if DEBUG_EVENTS_WINDOW
608 int subtype = event->event;
611 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
612 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
613 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
614 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
615 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
616 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
617 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
618 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
619 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
620 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
621 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
622 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
623 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
624 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
627 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
628 event_name, event->data1, event->data2);
632 // (not needed, as the screen gets redrawn every 20 ms anyway)
633 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
634 event->event == SDL_WINDOWEVENT_RESIZED ||
635 event->event == SDL_WINDOWEVENT_EXPOSED)
639 if (event->event == SDL_WINDOWEVENT_RESIZED)
641 if (!video.fullscreen_enabled)
643 int new_window_width = event->data1;
644 int new_window_height = event->data2;
646 // if window size has changed after resizing, calculate new scaling factor
647 if (new_window_width != video.window_width ||
648 new_window_height != video.window_height)
650 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
651 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
653 // (extreme window scaling allowed, but cannot be saved permanently)
654 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
655 setup.window_scaling_percent =
656 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
657 MAX_WINDOW_SCALING_PERCENT);
659 video.window_width = new_window_width;
660 video.window_height = new_window_height;
662 if (game_status == GAME_MODE_SETUP)
663 RedrawSetupScreenAfterFullscreenToggle();
665 UpdateMousePosition();
670 #if defined(PLATFORM_ANDROID)
673 int new_display_width = event->data1;
674 int new_display_height = event->data2;
676 // if fullscreen display size has changed, device has been rotated
677 if (new_display_width != video.display_width ||
678 new_display_height != video.display_height)
680 int nr = GRID_ACTIVE_NR(); // previous screen orientation
682 video.display_width = new_display_width;
683 video.display_height = new_display_height;
685 SDLSetScreenProperties();
686 SetGadgetsPosition_OverlayTouchButtons();
688 // check if screen orientation has changed (should always be true here)
689 if (nr != GRID_ACTIVE_NR())
691 if (game_status == GAME_MODE_SETUP)
692 RedrawSetupScreenAfterScreenRotation(nr);
694 SetOverlayGridSizeAndButtons();
702 #define NUM_TOUCH_FINGERS 3
707 SDL_FingerID finger_id;
711 } touch_info[NUM_TOUCH_FINGERS];
713 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
714 Key key, byte action)
716 touch_info[pos].touched = (action != JOY_NO_ACTION);
717 touch_info[pos].finger_id = finger_id;
718 touch_info[pos].counter = counter;
719 touch_info[pos].key = key;
720 touch_info[pos].action = action;
723 static void ClearTouchInfo(void)
727 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
728 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
731 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
733 int x = event->x * overlay.grid_xsize;
734 int y = event->y * overlay.grid_ysize;
735 int grid_button = overlay.grid_button[x][y];
736 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
737 Key key = GetKeyFromGridButton(grid_button);
738 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
740 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
744 // for any touch input event, enable overlay buttons (if activated)
745 SetOverlayEnabled(TRUE);
747 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
748 getKeyNameFromKey(key), key_status_name, event->fingerId);
750 if (key_status == KEY_PRESSED)
751 overlay.grid_button_action |= grid_button_action;
753 overlay.grid_button_action &= ~grid_button_action;
755 // check if we already know this touch event's finger id
756 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
758 if (touch_info[i].touched &&
759 touch_info[i].finger_id == event->fingerId)
761 // Debug("event:finger", "MARK 1: %d", i);
767 if (i >= NUM_TOUCH_FINGERS)
769 if (key_status == KEY_PRESSED)
771 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
773 // unknown finger id -- get new, empty slot, if available
774 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
776 if (touch_info[i].counter < oldest_counter)
779 oldest_counter = touch_info[i].counter;
781 // Debug("event:finger", "MARK 2: %d", i);
784 if (!touch_info[i].touched)
786 // Debug("event:finger", "MARK 3: %d", i);
792 if (i >= NUM_TOUCH_FINGERS)
794 // all slots allocated -- use oldest slot
797 // Debug("event:finger", "MARK 4: %d", i);
802 // release of previously unknown key (should not happen)
804 if (key != KSYM_UNDEFINED)
806 HandleKey(key, KEY_RELEASED);
808 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
809 getKeyNameFromKey(key), "KEY_RELEASED", i);
814 if (i < NUM_TOUCH_FINGERS)
816 if (key_status == KEY_PRESSED)
818 if (touch_info[i].key != key)
820 if (touch_info[i].key != KSYM_UNDEFINED)
822 HandleKey(touch_info[i].key, KEY_RELEASED);
824 // undraw previous grid button when moving finger away
825 overlay.grid_button_action &= ~touch_info[i].action;
827 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
828 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
831 if (key != KSYM_UNDEFINED)
833 HandleKey(key, KEY_PRESSED);
835 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
836 getKeyNameFromKey(key), "KEY_PRESSED", i);
840 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
844 if (touch_info[i].key != KSYM_UNDEFINED)
846 HandleKey(touch_info[i].key, KEY_RELEASED);
848 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
849 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
852 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
857 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
859 static Key motion_key_x = KSYM_UNDEFINED;
860 static Key motion_key_y = KSYM_UNDEFINED;
861 static Key button_key = KSYM_UNDEFINED;
862 static float motion_x1, motion_y1;
863 static float button_x1, button_y1;
864 static SDL_FingerID motion_id = -1;
865 static SDL_FingerID button_id = -1;
866 int move_trigger_distance_percent = setup.touch.move_distance;
867 int drop_trigger_distance_percent = setup.touch.drop_distance;
868 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
869 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
870 float event_x = event->x;
871 float event_y = event->y;
873 if (event->type == EVENT_FINGERPRESS)
875 if (event_x > 1.0 / 3.0)
879 motion_id = event->fingerId;
884 motion_key_x = KSYM_UNDEFINED;
885 motion_key_y = KSYM_UNDEFINED;
887 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
893 button_id = event->fingerId;
898 button_key = setup.input[0].key.snap;
900 HandleKey(button_key, KEY_PRESSED);
902 Debug("event:finger", "---------- SNAP STARTED ----------");
905 else if (event->type == EVENT_FINGERRELEASE)
907 if (event->fingerId == motion_id)
911 if (motion_key_x != KSYM_UNDEFINED)
912 HandleKey(motion_key_x, KEY_RELEASED);
913 if (motion_key_y != KSYM_UNDEFINED)
914 HandleKey(motion_key_y, KEY_RELEASED);
916 motion_key_x = KSYM_UNDEFINED;
917 motion_key_y = KSYM_UNDEFINED;
919 Debug("event:finger", "---------- MOVE STOPPED ----------");
921 else if (event->fingerId == button_id)
925 if (button_key != KSYM_UNDEFINED)
926 HandleKey(button_key, KEY_RELEASED);
928 button_key = KSYM_UNDEFINED;
930 Debug("event:finger", "---------- SNAP STOPPED ----------");
933 else if (event->type == EVENT_FINGERMOTION)
935 if (event->fingerId == motion_id)
937 float distance_x = ABS(event_x - motion_x1);
938 float distance_y = ABS(event_y - motion_y1);
939 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
940 event_x > motion_x1 ? setup.input[0].key.right :
942 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
943 event_y > motion_y1 ? setup.input[0].key.down :
946 if (distance_x < move_trigger_distance / 2 ||
947 distance_x < distance_y)
948 new_motion_key_x = KSYM_UNDEFINED;
950 if (distance_y < move_trigger_distance / 2 ||
951 distance_y < distance_x)
952 new_motion_key_y = KSYM_UNDEFINED;
954 if (distance_x > move_trigger_distance ||
955 distance_y > move_trigger_distance)
957 if (new_motion_key_x != motion_key_x)
959 if (motion_key_x != KSYM_UNDEFINED)
960 HandleKey(motion_key_x, KEY_RELEASED);
961 if (new_motion_key_x != KSYM_UNDEFINED)
962 HandleKey(new_motion_key_x, KEY_PRESSED);
965 if (new_motion_key_y != motion_key_y)
967 if (motion_key_y != KSYM_UNDEFINED)
968 HandleKey(motion_key_y, KEY_RELEASED);
969 if (new_motion_key_y != KSYM_UNDEFINED)
970 HandleKey(new_motion_key_y, KEY_PRESSED);
976 motion_key_x = new_motion_key_x;
977 motion_key_y = new_motion_key_y;
979 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
982 else if (event->fingerId == button_id)
984 float distance_x = ABS(event_x - button_x1);
985 float distance_y = ABS(event_y - button_y1);
987 if (distance_x < drop_trigger_distance / 2 &&
988 distance_y > drop_trigger_distance)
990 if (button_key == setup.input[0].key.snap)
991 HandleKey(button_key, KEY_RELEASED);
996 button_key = setup.input[0].key.drop;
998 HandleKey(button_key, KEY_PRESSED);
1000 Debug("event:finger", "---------- DROP STARTED ----------");
1006 void HandleFingerEvent(FingerEvent *event)
1008 #if DEBUG_EVENTS_FINGER
1009 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1010 event->type == EVENT_FINGERPRESS ? "pressed" :
1011 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1015 event->dx, event->dy,
1019 runtime.uses_touch_device = TRUE;
1021 if (game_status != GAME_MODE_PLAYING)
1024 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1026 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1027 local_player->mouse_action.button_hint =
1028 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1029 event->x < 0.5 ? MB_LEFTBUTTON :
1030 event->x > 0.5 ? MB_RIGHTBUTTON :
1036 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1037 HandleFingerEvent_VirtualButtons(event);
1038 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1039 HandleFingerEvent_WipeGestures(event);
1042 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1044 static int old_mx = 0, old_my = 0;
1045 static int last_button = MB_LEFTBUTTON;
1046 static boolean touched = FALSE;
1047 static boolean tapped = FALSE;
1049 // screen tile was tapped (but finger not touching the screen anymore)
1050 // (this point will also be reached without receiving a touch event)
1051 if (tapped && !touched)
1053 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1058 // stop here if this function was not triggered by a touch event
1062 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1064 // finger started touching the screen
1074 ClearPlayerMouseAction();
1076 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1079 else if (button == MB_RELEASED && touched)
1081 // finger stopped touching the screen
1086 SetPlayerMouseAction(old_mx, old_my, last_button);
1088 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1095 // finger moved while touching the screen
1097 int old_x = getLevelFromScreenX(old_mx);
1098 int old_y = getLevelFromScreenY(old_my);
1099 int new_x = getLevelFromScreenX(mx);
1100 int new_y = getLevelFromScreenY(my);
1102 if (new_x != old_x || new_y != old_y)
1107 // finger moved left or right from (horizontal) starting position
1109 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1111 SetPlayerMouseAction(old_mx, old_my, button_nr);
1113 last_button = button_nr;
1115 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1119 // finger stays at or returned to (horizontal) starting position
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1123 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1128 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1130 static int old_mx = 0, old_my = 0;
1131 static int last_button = MB_LEFTBUTTON;
1132 static boolean touched = FALSE;
1133 static boolean tapped = FALSE;
1135 // screen tile was tapped (but finger not touching the screen anymore)
1136 // (this point will also be reached without receiving a touch event)
1137 if (tapped && !touched)
1139 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 // stop here if this function was not triggered by a touch event
1148 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1150 // finger started touching the screen
1160 ClearPlayerMouseAction();
1162 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1165 else if (button == MB_RELEASED && touched)
1167 // finger stopped touching the screen
1172 SetPlayerMouseAction(old_mx, old_my, last_button);
1174 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1176 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1181 // finger moved while touching the screen
1183 int old_x = getLevelFromScreenX(old_mx);
1184 int old_y = getLevelFromScreenY(old_my);
1185 int new_x = getLevelFromScreenX(mx);
1186 int new_y = getLevelFromScreenY(my);
1188 if (new_x != old_x || new_y != old_y)
1190 // finger moved away from starting position
1192 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1194 // quickly alternate between clicking and releasing for maximum speed
1195 if (FrameCounter % 2 == 0)
1196 button_nr = MB_RELEASED;
1198 SetPlayerMouseAction(old_mx, old_my, button_nr);
1201 last_button = button_nr;
1205 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1209 // finger stays at or returned to starting position
1211 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1213 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1218 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1220 static int old_mx = 0, old_my = 0;
1221 static Key motion_key_x = KSYM_UNDEFINED;
1222 static Key motion_key_y = KSYM_UNDEFINED;
1223 static boolean touched = FALSE;
1224 static boolean started_on_player = FALSE;
1225 static boolean player_is_dropping = FALSE;
1226 static int player_drop_count = 0;
1227 static int last_player_x = -1;
1228 static int last_player_y = -1;
1230 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1239 started_on_player = FALSE;
1240 player_is_dropping = FALSE;
1241 player_drop_count = 0;
1245 motion_key_x = KSYM_UNDEFINED;
1246 motion_key_y = KSYM_UNDEFINED;
1248 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1251 else if (button == MB_RELEASED && touched)
1258 if (motion_key_x != KSYM_UNDEFINED)
1259 HandleKey(motion_key_x, KEY_RELEASED);
1260 if (motion_key_y != KSYM_UNDEFINED)
1261 HandleKey(motion_key_y, KEY_RELEASED);
1263 if (started_on_player)
1265 if (player_is_dropping)
1267 Debug("event:finger", "---------- DROP STOPPED ----------");
1269 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1273 Debug("event:finger", "---------- SNAP STOPPED ----------");
1275 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1279 motion_key_x = KSYM_UNDEFINED;
1280 motion_key_y = KSYM_UNDEFINED;
1282 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1287 int src_x = local_player->jx;
1288 int src_y = local_player->jy;
1289 int dst_x = getLevelFromScreenX(old_mx);
1290 int dst_y = getLevelFromScreenY(old_my);
1291 int dx = dst_x - src_x;
1292 int dy = dst_y - src_y;
1293 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1294 dx > 0 ? setup.input[0].key.right :
1296 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1297 dy > 0 ? setup.input[0].key.down :
1300 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1301 (last_player_x != local_player->jx ||
1302 last_player_y != local_player->jy))
1304 // in case of asymmetric diagonal movement, use "preferred" direction
1306 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1309 game_em.ply[0]->last_move_dir = last_move_dir;
1311 local_player->last_move_dir = last_move_dir;
1313 // (required to prevent accidentally forcing direction for next movement)
1314 last_player_x = local_player->jx;
1315 last_player_y = local_player->jy;
1318 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1320 started_on_player = TRUE;
1321 player_drop_count = getPlayerInventorySize(0);
1322 player_is_dropping = (player_drop_count > 0);
1324 if (player_is_dropping)
1326 Debug("event:finger", "---------- DROP STARTED ----------");
1328 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1332 Debug("event:finger", "---------- SNAP STARTED ----------");
1334 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1337 else if (dx != 0 || dy != 0)
1339 if (player_is_dropping &&
1340 player_drop_count == getPlayerInventorySize(0))
1342 Debug("event:finger", "---------- DROP -> SNAP ----------");
1344 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1345 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1347 player_is_dropping = FALSE;
1351 if (new_motion_key_x != motion_key_x)
1353 Debug("event:finger", "---------- %s %s ----------",
1354 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1355 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1357 if (motion_key_x != KSYM_UNDEFINED)
1358 HandleKey(motion_key_x, KEY_RELEASED);
1359 if (new_motion_key_x != KSYM_UNDEFINED)
1360 HandleKey(new_motion_key_x, KEY_PRESSED);
1363 if (new_motion_key_y != motion_key_y)
1365 Debug("event:finger", "---------- %s %s ----------",
1366 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1367 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1369 if (motion_key_y != KSYM_UNDEFINED)
1370 HandleKey(motion_key_y, KEY_RELEASED);
1371 if (new_motion_key_y != KSYM_UNDEFINED)
1372 HandleKey(new_motion_key_y, KEY_PRESSED);
1375 motion_key_x = new_motion_key_x;
1376 motion_key_y = new_motion_key_y;
1380 static void HandleButtonOrFinger(int mx, int my, int button)
1382 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1384 if (game_status != GAME_MODE_PLAYING)
1387 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1389 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1390 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1391 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1392 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1393 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1394 SetPlayerMouseAction(mx, my, button); // special case
1398 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1399 HandleButtonOrFinger_FollowFinger(mx, my, button);
1400 else if (game.use_mouse_actions && valid_mouse_event)
1401 SetPlayerMouseAction(mx, my, button);
1405 static boolean checkTextInputKey(Key key)
1407 // when playing, only handle raw key events and ignore text input
1408 if (game_status == GAME_MODE_PLAYING)
1411 // if Shift or right Alt key is pressed, handle key as text input
1412 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1415 // ignore raw keys as text input when not in text input mode
1416 if (KSYM_RAW(key) && !textinput_status)
1419 // else handle all printable keys as text input
1420 return KSYM_PRINTABLE(key);
1423 void HandleTextEvent(TextEvent *event)
1425 char *text = event->text;
1426 Key key = getKeyFromKeyName(text);
1428 #if DEBUG_EVENTS_TEXT
1429 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1432 text[0], (int)(text[0]),
1434 getKeyNameFromKey(key),
1438 if (checkTextInputKey(key))
1440 // process printable keys (with uppercase etc.) in text input mode
1441 HandleKey(key, KEY_PRESSED);
1442 HandleKey(key, KEY_RELEASED);
1446 void HandlePauseResumeEvent(PauseResumeEvent *event)
1448 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1452 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1458 void HandleKeyEvent(KeyEvent *event)
1460 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1461 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1462 Key key = GetEventKey(event, with_modifiers);
1463 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1465 #if DEBUG_EVENTS_KEY
1466 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1467 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1468 event->keysym.scancode,
1473 getKeyNameFromKey(key));
1476 #if defined(PLATFORM_ANDROID)
1477 if (key == KSYM_Back)
1479 // always map the "back" button to the "escape" key on Android devices
1482 else if (key == KSYM_Menu)
1484 // the "menu" button can be used to toggle displaying virtual buttons
1485 if (key_status == KEY_PRESSED)
1486 SetOverlayEnabled(!GetOverlayEnabled());
1488 else if (!textinput_status)
1490 // for any other "real" key event, disable virtual buttons
1491 SetOverlayEnabled(FALSE);
1493 // for any other "real" key event, disable overlay touch buttons
1494 runtime.uses_touch_device = FALSE;
1498 HandleKeyModState(keymod, key_status);
1500 // process all keys if not in text input mode or if non-printable keys
1501 if (!checkTextInputKey(key))
1502 HandleKey(key, key_status);
1505 static int HandleDropFileEvent(char *filename)
1507 Debug("event:dropfile", "filename == '%s'", filename);
1509 // check and extract dropped zip files into correct user data directory
1510 if (!strSuffixLower(filename, ".zip"))
1512 Warn("file '%s' not supported", filename);
1514 return TREE_TYPE_UNDEFINED;
1517 TreeInfo *tree_node = NULL;
1518 int tree_type = GetZipFileTreeType(filename);
1519 char *directory = TREE_USERDIR(tree_type);
1521 if (directory == NULL)
1523 Warn("zip file '%s' has invalid content!", filename);
1525 return TREE_TYPE_UNDEFINED;
1528 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1529 game_status == GAME_MODE_LEVELS &&
1530 leveldir_current->node_parent != NULL)
1532 // extract new level set next to currently selected level set
1533 tree_node = leveldir_current;
1535 // get parent directory of currently selected level set directory
1536 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1538 // use private level directory instead of top-level package level directory
1539 if (strPrefix(directory, options.level_directory) &&
1540 strEqual(leveldir_current->node_parent->fullpath, "."))
1541 directory = getUserLevelDir(NULL);
1544 // extract level or artwork set from zip file to target directory
1545 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1547 if (top_dir == NULL)
1549 // error message already issued by "ExtractZipFileIntoDirectory()"
1551 return TREE_TYPE_UNDEFINED;
1554 // add extracted level or artwork set to tree info structure
1555 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1557 // update menu screen (and possibly change current level set)
1558 DrawScreenAfterAddingSet(top_dir, tree_type);
1563 static void HandleDropTextEvent(char *text)
1565 Debug("event:droptext", "text == '%s'", text);
1568 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1569 int num_artwork_sets_succeeded,
1570 int num_files_failed)
1572 // only show request dialog if no other request dialog already active
1573 if (game.request_active)
1576 // this case can happen with drag-and-drop with older SDL versions
1577 if (num_level_sets_succeeded == 0 &&
1578 num_artwork_sets_succeeded == 0 &&
1579 num_files_failed == 0)
1584 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1586 char message_part1[50];
1588 sprintf(message_part1, "New %s set%s added",
1589 (num_artwork_sets_succeeded == 0 ? "level" :
1590 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1591 (num_level_sets_succeeded +
1592 num_artwork_sets_succeeded > 1 ? "s" : ""));
1594 if (num_files_failed > 0)
1595 sprintf(message, "%s, but %d dropped file%s failed!",
1596 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1598 sprintf(message, "%s!", message_part1);
1600 else if (num_files_failed > 0)
1602 sprintf(message, "Failed to process dropped file%s!",
1603 num_files_failed > 1 ? "s" : "");
1606 Request(message, REQ_CONFIRM);
1609 void HandleDropEvent(Event *event)
1611 static boolean confirm_on_drop_complete = FALSE;
1612 static int num_level_sets_succeeded = 0;
1613 static int num_artwork_sets_succeeded = 0;
1614 static int num_files_failed = 0;
1616 switch (event->type)
1620 confirm_on_drop_complete = TRUE;
1621 num_level_sets_succeeded = 0;
1622 num_artwork_sets_succeeded = 0;
1623 num_files_failed = 0;
1630 int tree_type = HandleDropFileEvent(event->drop.file);
1632 if (tree_type == TREE_TYPE_LEVEL_DIR)
1633 num_level_sets_succeeded++;
1634 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1635 tree_type == TREE_TYPE_SOUNDS_DIR ||
1636 tree_type == TREE_TYPE_MUSIC_DIR)
1637 num_artwork_sets_succeeded++;
1641 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1642 if (!confirm_on_drop_complete)
1644 // process all remaining events, including further SDL_DROPFILE events
1647 HandleDropCompleteEvent(num_level_sets_succeeded,
1648 num_artwork_sets_succeeded,
1651 num_level_sets_succeeded = 0;
1652 num_artwork_sets_succeeded = 0;
1653 num_files_failed = 0;
1661 HandleDropTextEvent(event->drop.file);
1666 case SDL_DROPCOMPLETE:
1668 HandleDropCompleteEvent(num_level_sets_succeeded,
1669 num_artwork_sets_succeeded,
1676 if (event->drop.file != NULL)
1677 SDL_free(event->drop.file);
1680 void HandleUserEvent(UserEvent *event)
1682 switch (event->code)
1684 case USEREVENT_ANIM_DELAY_ACTION:
1685 case USEREVENT_ANIM_EVENT_ACTION:
1686 // execute action functions until matching action was found
1687 if (DoKeysymAction(event->value1) ||
1688 DoGadgetAction(event->value1) ||
1689 DoScreenAction(event->value1))
1698 void HandleButton(int mx, int my, int button, int button_nr)
1700 static int old_mx = 0, old_my = 0;
1701 boolean button_hold = FALSE;
1702 boolean handle_gadgets = TRUE;
1708 button_nr = -button_nr;
1717 #if defined(PLATFORM_ANDROID)
1718 // when playing, only handle gadgets when using "follow finger" controls
1719 // or when using touch controls in combination with the MM game engine
1720 // or when using gadgets that do not overlap with virtual buttons
1721 // or when touch controls are disabled (e.g., with mouse-only levels)
1723 (game_status != GAME_MODE_PLAYING ||
1724 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1725 strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1726 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1727 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1728 !CheckVirtualButtonPressed(mx, my, button)));
1730 // always recognize potentially releasing already pressed gadgets
1731 if (button == MB_RELEASED)
1732 handle_gadgets = TRUE;
1734 // always recognize pressing or releasing overlay touch buttons
1735 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1736 handle_gadgets = TRUE;
1739 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1741 // do not handle this button event anymore
1742 return; // force mouse event not to be handled at all
1745 if (handle_gadgets && HandleGadgets(mx, my, button))
1747 // do not handle this button event anymore
1748 mx = my = -32; // force mouse event to be outside screen tiles
1751 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1754 // do not use scroll wheel button events for anything other than gadgets
1755 if (IS_WHEEL_BUTTON(button_nr))
1758 switch (game_status)
1760 case GAME_MODE_TITLE:
1761 HandleTitleScreen(mx, my, 0, 0, button);
1764 case GAME_MODE_MAIN:
1765 HandleMainMenu(mx, my, 0, 0, button);
1768 case GAME_MODE_PSEUDO_TYPENAME:
1769 case GAME_MODE_PSEUDO_TYPENAMES:
1770 HandleTypeName(KSYM_Return);
1773 case GAME_MODE_NAMES:
1774 HandleChoosePlayerName(mx, my, 0, 0, button);
1777 case GAME_MODE_LEVELS:
1778 HandleChooseLevelSet(mx, my, 0, 0, button);
1781 case GAME_MODE_LEVELNR:
1782 HandleChooseLevelNr(mx, my, 0, 0, button);
1785 case GAME_MODE_SCORES:
1786 HandleHallOfFame(mx, my, 0, 0, button);
1789 case GAME_MODE_SCOREINFO:
1790 HandleScoreInfo(mx, my, 0, 0, button);
1793 case GAME_MODE_EDITOR:
1794 HandleLevelEditorIdle();
1797 case GAME_MODE_INFO:
1798 HandleInfoScreen(mx, my, 0, 0, button);
1801 case GAME_MODE_SETUP:
1802 HandleSetupScreen(mx, my, 0, 0, button);
1805 case GAME_MODE_PLAYING:
1806 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1807 HandleButtonOrFinger(mx, my, button);
1809 SetPlayerMouseAction(mx, my, button);
1812 if (button == MB_PRESSED && !motion_status && !button_hold &&
1813 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1814 DumpTileFromScreen(mx, my);
1824 #define MAX_CHEAT_INPUT_LEN 32
1826 static void HandleKeysSpecial(Key key)
1828 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1829 char letter = getCharFromKey(key);
1830 int cheat_input_len = strlen(cheat_input);
1836 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1838 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1839 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1841 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1844 cheat_input[cheat_input_len++] = letter;
1845 cheat_input[cheat_input_len] = '\0';
1847 #if DEBUG_EVENTS_KEY
1848 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1851 if (game_status == GAME_MODE_MAIN)
1853 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1854 strSuffix(cheat_input, ":ist"))
1856 InsertSolutionTape();
1858 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1859 strSuffix(cheat_input, ":pst"))
1863 else if (strSuffix(cheat_input, ":reload-graphics") ||
1864 strSuffix(cheat_input, ":rg"))
1866 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1869 else if (strSuffix(cheat_input, ":reload-sounds") ||
1870 strSuffix(cheat_input, ":rs"))
1872 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1875 else if (strSuffix(cheat_input, ":reload-music") ||
1876 strSuffix(cheat_input, ":rm"))
1878 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1881 else if (strSuffix(cheat_input, ":reload-artwork") ||
1882 strSuffix(cheat_input, ":ra"))
1884 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1885 1 << ARTWORK_TYPE_SOUNDS |
1886 1 << ARTWORK_TYPE_MUSIC);
1889 else if (strSuffix(cheat_input, ":dump-level") ||
1890 strSuffix(cheat_input, ":dl"))
1894 else if (strSuffix(cheat_input, ":dump-tape") ||
1895 strSuffix(cheat_input, ":dt"))
1899 else if (strSuffix(cheat_input, ":undo-tape") ||
1900 strSuffix(cheat_input, ":ut"))
1904 else if (strSuffix(cheat_input, ":fix-tape") ||
1905 strSuffix(cheat_input, ":ft"))
1907 FixTape_ForceSinglePlayer();
1909 else if (strSuffix(cheat_input, ":save-native-level") ||
1910 strSuffix(cheat_input, ":snl"))
1912 SaveNativeLevel(&level);
1914 else if (strSuffix(cheat_input, ":frames-per-second") ||
1915 strSuffix(cheat_input, ":fps"))
1917 global.show_frames_per_second = !global.show_frames_per_second;
1919 else if (strSuffix(cheat_input, ":xsn"))
1921 tile_cursor.xsn_debug = TRUE;
1924 else if (game_status == GAME_MODE_PLAYING)
1927 if (strSuffix(cheat_input, ".q"))
1928 DEBUG_SetMaximumDynamite();
1931 else if (game_status == GAME_MODE_EDITOR)
1933 if (strSuffix(cheat_input, ":dump-brush") ||
1934 strSuffix(cheat_input, ":DB"))
1938 else if (strSuffix(cheat_input, ":DDB"))
1943 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1945 if (letter == 'x') // copy brush to clipboard (small size)
1947 CopyBrushToClipboard_Small();
1949 else if (letter == 'c') // copy brush to clipboard (normal size)
1951 CopyBrushToClipboard();
1953 else if (letter == 'v') // paste brush from Clipboard
1955 CopyClipboardToBrush();
1957 else if (letter == 'z') // undo or redo last operation
1959 if (GetKeyModState() & KMOD_Shift)
1960 RedoLevelEditorOperation();
1962 UndoLevelEditorOperation();
1967 // special key shortcuts for all game modes
1968 if (strSuffix(cheat_input, ":dump-event-actions") ||
1969 strSuffix(cheat_input, ":dea") ||
1970 strSuffix(cheat_input, ":DEA"))
1972 DumpGadgetIdentifiers();
1973 DumpScreenIdentifiers();
1977 boolean HandleKeysDebug(Key key, int key_status)
1982 if (key_status != KEY_PRESSED)
1985 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1987 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1989 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1991 if (key == setup.debug.frame_delay_key[i] &&
1992 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1994 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1995 setup.debug.frame_delay[i] : setup.game_frame_delay);
1997 if (!setup.debug.frame_delay_game_only)
1998 MenuFrameDelay = GameFrameDelay;
2000 SetVideoFrameDelay(GameFrameDelay);
2002 if (GameFrameDelay > ONE_SECOND_DELAY)
2003 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2004 else if (GameFrameDelay != 0)
2005 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2006 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2007 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2009 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2016 if (game_status == GAME_MODE_PLAYING)
2020 options.debug = !options.debug;
2022 Debug("event:key:debug", "debug mode %s",
2023 (options.debug ? "enabled" : "disabled"));
2027 else if (key == KSYM_v)
2029 Debug("event:key:debug", "currently using game engine version %d",
2030 game.engine_version);
2040 void HandleKey(Key key, int key_status)
2042 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2043 static boolean ignore_repeated_key = FALSE;
2044 static struct SetupKeyboardInfo ski;
2045 static struct SetupShortcutInfo ssi;
2054 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2055 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2056 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2057 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2058 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2059 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2064 if (HandleKeysDebug(key, key_status))
2065 return; // do not handle already processed keys again
2067 // map special keys (media keys / remote control buttons) to default keys
2068 if (key == KSYM_PlayPause)
2070 else if (key == KSYM_Select)
2073 HandleSpecialGameControllerKeys(key, key_status);
2075 if (game_status == GAME_MODE_PLAYING)
2077 // only needed for single-step tape recording mode
2078 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2081 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2083 byte key_action = 0;
2084 byte key_snap_action = 0;
2086 if (setup.input[pnr].use_joystick)
2089 ski = setup.input[pnr].key;
2091 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2092 if (key == *key_info[i].key_custom)
2093 key_action |= key_info[i].action;
2095 // use combined snap+direction keys for the first player only
2098 ssi = setup.shortcut;
2100 // also remember normal snap key when handling snap+direction keys
2101 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2103 for (i = 0; i < NUM_DIRECTIONS; i++)
2105 if (key == *key_info[i].key_snap)
2107 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2108 key_snap_action |= key_info[i].action;
2110 tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2115 if (key_status == KEY_PRESSED)
2117 stored_player[pnr].action |= key_action;
2118 stored_player[pnr].snap_action |= key_snap_action;
2122 stored_player[pnr].action &= ~key_action;
2123 stored_player[pnr].snap_action &= ~key_snap_action;
2126 // restore snap action if one of several pressed snap keys was released
2127 if (stored_player[pnr].snap_action)
2128 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2130 if (tape.recording && tape.pausing && tape.use_key_actions)
2132 if (tape.single_step)
2134 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2136 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2138 // if snap key already pressed, keep pause mode when releasing
2139 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2140 has_snapped[pnr] = TRUE;
2142 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2144 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2146 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2147 getRedDiskReleaseFlag_SP() == 0)
2149 // add a single inactive frame before dropping starts
2150 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2151 stored_player[pnr].force_dropping = TRUE;
2154 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2156 // if snap key was pressed without direction, leave pause mode
2157 if (!has_snapped[pnr])
2158 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2160 has_snapped[pnr] = FALSE;
2165 // prevent key release events from un-pausing a paused game
2166 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2167 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2171 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2172 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2175 // for any keyboard event, enable playfield mouse cursor
2176 if (key_action && key_status == KEY_PRESSED)
2177 SetPlayfieldMouseCursorEnabled(TRUE);
2182 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2183 if (key == key_info[i].key_default)
2184 joy |= key_info[i].action;
2189 if (key_status == KEY_PRESSED)
2190 key_joystick_mapping |= joy;
2192 key_joystick_mapping &= ~joy;
2197 if (game_status != GAME_MODE_PLAYING)
2198 key_joystick_mapping = 0;
2200 if (key_status == KEY_RELEASED)
2202 // reset flag to ignore repeated "key pressed" events after key release
2203 ignore_repeated_key = FALSE;
2208 if ((key == KSYM_F11 ||
2209 ((key == KSYM_Return ||
2210 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2211 video.fullscreen_available &&
2212 !ignore_repeated_key)
2214 setup.fullscreen = !setup.fullscreen;
2216 ToggleFullscreenIfNeeded();
2218 if (game_status == GAME_MODE_SETUP)
2219 RedrawSetupScreenAfterFullscreenToggle();
2221 UpdateMousePosition();
2223 // set flag to ignore repeated "key pressed" events
2224 ignore_repeated_key = TRUE;
2229 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2230 key == KSYM_minus || key == KSYM_KP_Subtract ||
2231 key == KSYM_plus || key == KSYM_KP_Add ||
2232 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2233 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2234 video.window_scaling_available &&
2235 !video.fullscreen_enabled)
2237 if (key == KSYM_0 || key == KSYM_KP_0)
2238 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2239 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2240 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2242 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2244 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2245 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2246 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2247 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2249 ChangeWindowScalingIfNeeded();
2251 if (game_status == GAME_MODE_SETUP)
2252 RedrawSetupScreenAfterFullscreenToggle();
2254 UpdateMousePosition();
2259 // some key events are handled like clicks for global animations
2260 boolean click = (key == KSYM_space ||
2261 key == KSYM_Return ||
2262 key == KSYM_Escape);
2264 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2266 // do not handle this key event anymore
2267 if (key != KSYM_Escape) // always allow ESC key to be handled
2271 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2272 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2279 if (game_status == GAME_MODE_MAIN &&
2280 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2282 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2287 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2289 if (key == setup.shortcut.save_game)
2291 else if (key == setup.shortcut.load_game)
2293 else if (key == setup.shortcut.restart_game)
2295 else if (key == setup.shortcut.pause_before_end)
2296 TapeReplayAndPauseBeforeEnd();
2297 else if (key == setup.shortcut.toggle_pause)
2298 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2300 HandleTapeButtonKeys(key);
2301 HandleSoundButtonKeys(key);
2304 if (game_status == GAME_MODE_SCOREINFO)
2306 HandleScreenGadgetKeys(key);
2309 if (game_status == GAME_MODE_PLAYING && !network_playing)
2311 int centered_player_nr_next = -999;
2313 if (key == setup.shortcut.focus_player_all)
2314 centered_player_nr_next = -1;
2316 for (i = 0; i < MAX_PLAYERS; i++)
2317 if (key == setup.shortcut.focus_player[i])
2318 centered_player_nr_next = i;
2320 if (centered_player_nr_next != -999)
2322 game.centered_player_nr_next = centered_player_nr_next;
2323 game.set_centered_player = TRUE;
2327 tape.centered_player_nr_next = game.centered_player_nr_next;
2328 tape.set_centered_player = TRUE;
2333 HandleKeysSpecial(key);
2335 if (HandleGadgetsKeyInput(key))
2336 return; // do not handle already processed keys again
2338 // special case: on "space" key, either continue playing or go to main menu
2339 if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2341 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2346 switch (game_status)
2348 case GAME_MODE_PSEUDO_TYPENAME:
2349 case GAME_MODE_PSEUDO_TYPENAMES:
2350 HandleTypeName(key);
2353 case GAME_MODE_TITLE:
2354 case GAME_MODE_MAIN:
2355 case GAME_MODE_NAMES:
2356 case GAME_MODE_LEVELS:
2357 case GAME_MODE_LEVELNR:
2358 case GAME_MODE_SETUP:
2359 case GAME_MODE_INFO:
2360 case GAME_MODE_SCORES:
2361 case GAME_MODE_SCOREINFO:
2363 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2370 if (game_status == GAME_MODE_TITLE)
2371 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2372 else if (game_status == GAME_MODE_MAIN)
2373 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2374 else if (game_status == GAME_MODE_NAMES)
2375 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2376 else if (game_status == GAME_MODE_LEVELS)
2377 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2378 else if (game_status == GAME_MODE_LEVELNR)
2379 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2380 else if (game_status == GAME_MODE_SETUP)
2381 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2382 else if (game_status == GAME_MODE_INFO)
2383 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2384 else if (game_status == GAME_MODE_SCORES)
2385 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2386 else if (game_status == GAME_MODE_SCOREINFO)
2387 HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2391 if (game_status != GAME_MODE_MAIN)
2392 FadeSkipNextFadeIn();
2394 if (game_status == GAME_MODE_TITLE)
2395 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2396 else if (game_status == GAME_MODE_NAMES)
2397 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2398 else if (game_status == GAME_MODE_LEVELS)
2399 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2400 else if (game_status == GAME_MODE_LEVELNR)
2401 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2402 else if (game_status == GAME_MODE_SETUP)
2403 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2404 else if (game_status == GAME_MODE_INFO)
2405 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2406 else if (game_status == GAME_MODE_SCORES)
2407 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2408 else if (game_status == GAME_MODE_SCOREINFO)
2409 HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2413 if (game_status == GAME_MODE_NAMES)
2414 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2415 else if (game_status == GAME_MODE_LEVELS)
2416 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2417 else if (game_status == GAME_MODE_LEVELNR)
2418 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2419 else if (game_status == GAME_MODE_SETUP)
2420 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2421 else if (game_status == GAME_MODE_INFO)
2422 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2423 else if (game_status == GAME_MODE_SCORES)
2424 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2425 else if (game_status == GAME_MODE_SCOREINFO)
2426 HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2429 case KSYM_Page_Down:
2430 if (game_status == GAME_MODE_NAMES)
2431 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2432 else if (game_status == GAME_MODE_LEVELS)
2433 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2434 else if (game_status == GAME_MODE_LEVELNR)
2435 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2436 else if (game_status == GAME_MODE_SETUP)
2437 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2438 else if (game_status == GAME_MODE_INFO)
2439 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2440 else if (game_status == GAME_MODE_SCORES)
2441 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2442 else if (game_status == GAME_MODE_SCOREINFO)
2443 HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2451 case GAME_MODE_EDITOR:
2452 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2453 HandleLevelEditorKeyInput(key);
2456 case GAME_MODE_PLAYING:
2461 RequestQuitGame(TRUE);
2471 if (key == KSYM_Escape)
2473 SetGameStatus(GAME_MODE_MAIN);
2482 void HandleNoEvent(void)
2484 HandleMouseCursor();
2486 switch (game_status)
2488 case GAME_MODE_PLAYING:
2489 HandleButtonOrFinger(-1, -1, -1);
2494 void HandleEventActions(void)
2496 // if (button_status && game_status != GAME_MODE_PLAYING)
2497 if (button_status && (game_status != GAME_MODE_PLAYING ||
2499 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2501 HandleButton(0, 0, button_status, -button_status);
2508 if (network.enabled)
2511 switch (game_status)
2513 case GAME_MODE_MAIN:
2514 DrawPreviewLevelAnimation();
2517 case GAME_MODE_EDITOR:
2518 HandleLevelEditorIdle();
2526 static void HandleTileCursor(int dx, int dy, int button)
2529 ClearPlayerMouseAction();
2536 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2537 (dx < 0 ? MB_LEFTBUTTON :
2538 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2540 else if (!tile_cursor.moving)
2542 int old_xpos = tile_cursor.xpos;
2543 int old_ypos = tile_cursor.ypos;
2544 int new_xpos = old_xpos;
2545 int new_ypos = old_ypos;
2547 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2548 new_xpos = old_xpos + dx;
2550 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2551 new_ypos = old_ypos + dy;
2553 SetTileCursorTargetXY(new_xpos, new_ypos);
2557 static int HandleJoystickForAllPlayers(void)
2561 boolean no_joysticks_configured = TRUE;
2562 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2563 static byte joy_action_last[MAX_PLAYERS];
2565 for (i = 0; i < MAX_PLAYERS; i++)
2566 if (setup.input[i].use_joystick)
2567 no_joysticks_configured = FALSE;
2569 // if no joysticks configured, map connected joysticks to players
2570 if (no_joysticks_configured)
2571 use_as_joystick_nr = TRUE;
2573 for (i = 0; i < MAX_PLAYERS; i++)
2575 byte joy_action = 0;
2577 joy_action = JoystickExt(i, use_as_joystick_nr);
2578 result |= joy_action;
2580 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2581 joy_action != joy_action_last[i])
2582 stored_player[i].action = joy_action;
2584 joy_action_last[i] = joy_action;
2590 void HandleJoystick(void)
2592 static unsigned int joytest_delay = 0;
2593 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2594 static int joytest_last = 0;
2595 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2596 int delay_value = GADGET_FRAME_DELAY;
2597 int joystick = HandleJoystickForAllPlayers();
2598 int keyboard = key_joystick_mapping;
2599 int joy = (joystick | keyboard);
2600 int joytest = joystick;
2601 int left = joy & JOY_LEFT;
2602 int right = joy & JOY_RIGHT;
2603 int up = joy & JOY_UP;
2604 int down = joy & JOY_DOWN;
2605 int button = joy & JOY_BUTTON;
2606 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2607 int dx = (left ? -1 : right ? 1 : 0);
2608 int dy = (up ? -1 : down ? 1 : 0);
2609 boolean use_delay_value_first = (joytest != joytest_last);
2611 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2613 // do not handle this button event anymore
2617 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2618 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2619 anyTextGadgetActive()))
2621 // leave name input in main menu or text input gadget
2622 HandleKey(KSYM_Escape, KEY_PRESSED);
2623 HandleKey(KSYM_Escape, KEY_RELEASED);
2628 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2630 if (game_status == GAME_MODE_PLAYING)
2632 // when playing MM style levels, also use delay for keyboard events
2633 joytest |= keyboard;
2635 // only use first delay value for new events, but not for changed events
2636 use_delay_value_first = (!joytest != !joytest_last);
2638 // only use delay after the initial keyboard event
2642 // for any joystick or keyboard event, enable playfield tile cursor
2643 if (dx || dy || button)
2644 SetTileCursorEnabled(TRUE);
2647 // for any joystick event, enable playfield mouse cursor
2648 if (dx || dy || button)
2649 SetPlayfieldMouseCursorEnabled(TRUE);
2651 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2653 // delay joystick/keyboard actions if axes/keys continually pressed
2654 newbutton = dx = dy = 0;
2658 // first start with longer delay, then continue with shorter delay
2659 joytest_delay_value =
2660 (use_delay_value_first ? delay_value_first : delay_value);
2663 joytest_last = joytest;
2665 switch (game_status)
2667 case GAME_MODE_TITLE:
2668 case GAME_MODE_MAIN:
2669 case GAME_MODE_NAMES:
2670 case GAME_MODE_LEVELS:
2671 case GAME_MODE_LEVELNR:
2672 case GAME_MODE_SETUP:
2673 case GAME_MODE_INFO:
2674 case GAME_MODE_SCORES:
2675 case GAME_MODE_SCOREINFO:
2677 if (anyTextGadgetActive())
2680 if (game_status == GAME_MODE_TITLE)
2681 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2682 else if (game_status == GAME_MODE_MAIN)
2683 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2684 else if (game_status == GAME_MODE_NAMES)
2685 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2686 else if (game_status == GAME_MODE_LEVELS)
2687 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2688 else if (game_status == GAME_MODE_LEVELNR)
2689 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2690 else if (game_status == GAME_MODE_SETUP)
2691 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2692 else if (game_status == GAME_MODE_INFO)
2693 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2694 else if (game_status == GAME_MODE_SCORES)
2695 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2696 else if (game_status == GAME_MODE_SCOREINFO)
2697 HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2702 case GAME_MODE_PLAYING:
2704 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2705 if (tape.playing || keyboard)
2706 newbutton = ((joy & JOY_BUTTON) != 0);
2709 if (newbutton && game.all_players_gone)
2716 if (tape.recording && tape.pausing && tape.use_key_actions)
2718 if (tape.single_step)
2720 if (joystick & JOY_ACTION)
2721 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2725 if (joystick & JOY_ACTION)
2726 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2730 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2731 HandleTileCursor(dx, dy, button);
2740 void HandleSpecialGameControllerButtons(Event *event)
2745 switch (event->type)
2747 case SDL_CONTROLLERBUTTONDOWN:
2748 key_status = KEY_PRESSED;
2751 case SDL_CONTROLLERBUTTONUP:
2752 key_status = KEY_RELEASED;
2759 switch (event->cbutton.button)
2761 case SDL_CONTROLLER_BUTTON_START:
2765 case SDL_CONTROLLER_BUTTON_BACK:
2773 HandleKey(key, key_status);
2776 void HandleSpecialGameControllerKeys(Key key, int key_status)
2778 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2779 int button = SDL_CONTROLLER_BUTTON_INVALID;
2781 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2782 if (key == KSYM_Rewind)
2783 button = SDL_CONTROLLER_BUTTON_A;
2784 else if (key == KSYM_FastForward || key == KSYM_Menu)
2785 button = SDL_CONTROLLER_BUTTON_B;
2787 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2791 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2792 SDL_CONTROLLERBUTTONUP);
2794 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2795 event.cbutton.button = button;
2796 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2799 HandleJoystickEvent(&event);
2804 boolean DoKeysymAction(int keysym)
2808 Key key = (Key)(-keysym);
2810 HandleKey(key, KEY_PRESSED);
2811 HandleKey(key, KEY_RELEASED);