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 DelayCounter special_cursor_delay = { 1000 };
40 static boolean special_cursor_enabled = FALSE;
42 static boolean stop_processing_events = FALSE;
45 // forward declarations for internal use
46 static void ClearTouchInfo(void);
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
51 void SetPlayfieldMouseCursorEnabled(boolean enabled)
53 special_cursor_enabled = enabled;
56 // event filter to set mouse x/y position (for pointer class global animations)
57 // (this is especially required to ensure smooth global animation mouse pointer
58 // movement when the screen is updated without handling events; this can happen
59 // when drawing door/envelope request animations, for example)
61 int FilterMouseMotionEvents(void *userdata, Event *event)
63 if (event->type == EVENT_MOTIONNOTIFY)
65 int mouse_x = ((MotionEvent *)event)->x;
66 int mouse_y = ((MotionEvent *)event)->y;
68 UpdateRawMousePosition(mouse_x, mouse_y);
74 // event filter especially needed for SDL event filtering due to
75 // delay problems with lots of mouse motion events when mouse button
76 // not pressed (X11 can handle this with 'PointerMotionHintMask')
78 // event filter addition for SDL2: as SDL2 does not have a function to enable
79 // or disable keyboard auto-repeat, filter repeated keyboard events instead
81 static int FilterEvents(const Event *event)
85 // skip repeated key press events if keyboard auto-repeat is disabled
86 if (event->type == EVENT_KEYPRESS &&
91 if (event->type == EVENT_BUTTONPRESS ||
92 event->type == EVENT_BUTTONRELEASE)
94 ((ButtonEvent *)event)->x -= video.screen_xoffset;
95 ((ButtonEvent *)event)->y -= video.screen_yoffset;
97 else if (event->type == EVENT_MOTIONNOTIFY)
99 ((MotionEvent *)event)->x -= video.screen_xoffset;
100 ((MotionEvent *)event)->y -= video.screen_yoffset;
103 if (event->type == EVENT_BUTTONPRESS ||
104 event->type == EVENT_BUTTONRELEASE ||
105 event->type == EVENT_MOTIONNOTIFY)
107 // do not reset mouse cursor before all pending events have been processed
108 if (gfx.cursor_mode == cursor_mode_last &&
109 ((game_status == GAME_MODE_TITLE &&
110 gfx.cursor_mode == CURSOR_NONE) ||
111 (game_status == GAME_MODE_PLAYING &&
112 gfx.cursor_mode == CURSOR_PLAYFIELD)))
114 SetMouseCursor(CURSOR_DEFAULT);
116 ResetDelayCounter(&special_cursor_delay);
118 cursor_mode_last = CURSOR_DEFAULT;
122 // non-motion events are directly passed to event handler functions
123 if (event->type != EVENT_MOTIONNOTIFY)
126 motion = (MotionEvent *)event;
127 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
128 motion->y >= SY && motion->y < SY + SYSIZE);
130 // set correct mouse x/y position (for pointer class global animations)
131 // (this is required in rare cases where the mouse x/y position calculated
132 // from raw values (to apply logical screen size scaling corrections) does
133 // not match the final mouse event x/y position -- this may happen because
134 // the SDL renderer's viewport position is internally represented as float,
135 // but only accessible as integer, which may lead to rounding errors)
136 gfx.mouse_x = motion->x;
137 gfx.mouse_y = motion->y;
139 // skip mouse motion events without pressed button outside level editor
140 if (button_status == MB_RELEASED &&
141 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
147 // to prevent delay problems, skip mouse motion events if the very next
148 // event is also a mouse motion event (and therefore effectively only
149 // handling the last of a row of mouse motion events in the event queue)
151 static boolean SkipPressedMouseMotionEvent(const Event *event)
153 // nothing to do if the current event is not a mouse motion event
154 if (event->type != EVENT_MOTIONNOTIFY)
157 // only skip motion events with pressed button outside the game
158 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
165 PeekEvent(&next_event);
167 // if next event is also a mouse motion event, skip the current one
168 if (next_event.type == EVENT_MOTIONNOTIFY)
175 static boolean WaitValidEvent(Event *event)
179 if (!FilterEvents(event))
182 if (SkipPressedMouseMotionEvent(event))
188 /* this is especially needed for event modifications for the Android target:
189 if mouse coordinates should be modified in the event filter function,
190 using a properly installed SDL event filter does not work, because in
191 the event filter, mouse coordinates in the event structure are still
192 physical pixel positions, not logical (scaled) screen positions, so this
193 has to be handled at a later stage in the event processing functions
194 (when device pixel positions are already converted to screen positions) */
196 boolean NextValidEvent(Event *event)
198 while (PendingEvent())
199 if (WaitValidEvent(event))
205 void StopProcessingEvents(void)
207 stop_processing_events = TRUE;
210 static void HandleEvents(void)
213 DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
215 ResetDelayCounter(&event_frame_delay);
217 stop_processing_events = FALSE;
219 while (NextValidEvent(&event))
221 int game_status_last = game_status;
225 case EVENT_BUTTONPRESS:
226 case EVENT_BUTTONRELEASE:
227 HandleButtonEvent((ButtonEvent *) &event);
230 case EVENT_MOTIONNOTIFY:
231 HandleMotionEvent((MotionEvent *) &event);
234 case EVENT_WHEELMOTION:
235 HandleWheelEvent((WheelEvent *) &event);
238 case SDL_WINDOWEVENT:
239 HandleWindowEvent((WindowEvent *) &event);
242 case EVENT_FINGERPRESS:
243 case EVENT_FINGERRELEASE:
244 case EVENT_FINGERMOTION:
245 HandleFingerEvent((FingerEvent *) &event);
248 case EVENT_TEXTINPUT:
249 HandleTextEvent((TextEvent *) &event);
252 case SDL_APP_WILLENTERBACKGROUND:
253 case SDL_APP_DIDENTERBACKGROUND:
254 case SDL_APP_WILLENTERFOREGROUND:
255 case SDL_APP_DIDENTERFOREGROUND:
256 HandlePauseResumeEvent((PauseResumeEvent *) &event);
260 case EVENT_KEYRELEASE:
261 HandleKeyEvent((KeyEvent *) &event);
265 HandleUserEvent((UserEvent *) &event);
269 HandleOtherEvents(&event);
273 // always handle events within delay period if game status has changed
274 if (game_status != game_status_last)
275 ResetDelayCounter(&event_frame_delay);
277 // do not handle events for longer than standard frame delay period
278 if (DelayReached(&event_frame_delay))
281 // do not handle any further events if triggered by a special flag
282 if (stop_processing_events)
287 void HandleOtherEvents(Event *event)
291 case SDL_CONTROLLERBUTTONDOWN:
292 case SDL_CONTROLLERBUTTONUP:
293 // for any game controller button event, disable overlay buttons
294 SetOverlayEnabled(FALSE);
296 HandleSpecialGameControllerButtons(event);
299 case SDL_CONTROLLERDEVICEADDED:
300 case SDL_CONTROLLERDEVICEREMOVED:
301 case SDL_CONTROLLERAXISMOTION:
302 case SDL_JOYAXISMOTION:
303 case SDL_JOYBUTTONDOWN:
304 case SDL_JOYBUTTONUP:
305 HandleJoystickEvent(event);
309 case SDL_DROPCOMPLETE:
312 HandleDropEvent(event);
324 static void HandleMouseCursor(void)
326 if (game_status == GAME_MODE_TITLE)
328 // when showing title screens, hide mouse pointer (if not moved)
330 if (gfx.cursor_mode != CURSOR_NONE &&
331 DelayReached(&special_cursor_delay))
333 SetMouseCursor(CURSOR_NONE);
336 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
339 // when playing, display a special mouse pointer inside the playfield
341 // display normal pointer if mouse pressed
342 if (button_status != MB_RELEASED)
343 ResetDelayCounter(&special_cursor_delay);
345 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
346 cursor_inside_playfield &&
347 special_cursor_enabled &&
348 DelayReached(&special_cursor_delay))
350 SetMouseCursor(CURSOR_PLAYFIELD);
353 else if (gfx.cursor_mode != CURSOR_DEFAULT)
355 SetMouseCursor(CURSOR_DEFAULT);
358 // this is set after all pending events have been processed
359 cursor_mode_last = gfx.cursor_mode;
371 // execute event related actions after pending events have been processed
372 HandleEventActions();
374 // don't use all CPU time when idle; the main loop while playing
375 // has its own synchronization and is CPU friendly, too
377 if (game_status == GAME_MODE_PLAYING)
380 // always copy backbuffer to visible screen for every video frame
383 // reset video frame delay to default (may change again while playing)
384 SetVideoFrameDelay(MenuFrameDelay);
386 if (game_status == GAME_MODE_QUIT)
391 void ClearAutoRepeatKeyEvents(void)
393 while (PendingEvent())
397 PeekEvent(&next_event);
399 // if event is repeated key press event, remove it from event queue
400 if (next_event.type == EVENT_KEYPRESS &&
401 next_event.key.repeat)
402 WaitEvent(&next_event);
408 void ClearEventQueue(void)
412 while (NextValidEvent(&event))
416 case EVENT_BUTTONRELEASE:
417 button_status = MB_RELEASED;
420 case EVENT_FINGERRELEASE:
421 case EVENT_KEYRELEASE:
425 case SDL_CONTROLLERBUTTONUP:
426 HandleJoystickEvent(&event);
431 HandleOtherEvents(&event);
437 static void ClearPlayerMouseAction(void)
439 local_player->mouse_action.lx = 0;
440 local_player->mouse_action.ly = 0;
441 local_player->mouse_action.button = 0;
444 void ClearPlayerAction(void)
448 // simulate key release events for still pressed keys
449 key_joystick_mapping = 0;
450 for (i = 0; i < MAX_PLAYERS; i++)
452 stored_player[i].action = 0;
453 stored_player[i].snap_action = 0;
456 // simulate finger release events for still pressed virtual buttons
457 overlay.grid_button_action = JOY_NO_ACTION;
460 ClearJoystickState();
461 ClearPlayerMouseAction();
464 static void SetPlayerMouseAction(int mx, int my, int button)
466 int lx = getLevelFromScreenX(mx);
467 int ly = getLevelFromScreenY(my);
468 int new_button = (!local_player->mouse_action.button && button);
470 if (local_player->mouse_action.button_hint)
471 button = local_player->mouse_action.button_hint;
473 ClearPlayerMouseAction();
475 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
478 local_player->mouse_action.lx = lx;
479 local_player->mouse_action.ly = ly;
480 local_player->mouse_action.button = button;
482 if (tape.recording && tape.pausing && tape.use_mouse_actions)
484 // un-pause a paused game only if mouse button was newly pressed down
486 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
489 SetTileCursorXY(lx, ly);
492 static Key GetKeyFromGridButton(int grid_button)
494 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
495 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
496 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
497 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
498 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
499 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
503 #if defined(PLATFORM_ANDROID)
504 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
506 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
507 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
508 int x = touch_x * overlay.grid_xsize;
509 int y = touch_y * overlay.grid_ysize;
510 int grid_button = overlay.grid_button[x][y];
511 Key key = GetKeyFromGridButton(grid_button);
512 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
514 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
518 void HandleButtonEvent(ButtonEvent *event)
520 #if DEBUG_EVENTS_BUTTON
521 Debug("event:button", "button %d %s, x/y %d/%d\n",
523 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
527 // for any mouse button event, disable playfield tile cursor
528 SetTileCursorEnabled(FALSE);
530 // for any mouse button event, disable playfield mouse cursor
531 if (cursor_inside_playfield)
532 SetPlayfieldMouseCursorEnabled(FALSE);
534 #if defined(HAS_SCREEN_KEYBOARD)
535 if (video.shifted_up)
536 event->y += video.shifted_up_pos;
539 motion_status = FALSE;
541 if (event->type == EVENT_BUTTONPRESS)
542 button_status = event->button;
544 button_status = MB_RELEASED;
546 HandleButton(event->x, event->y, button_status, event->button);
549 void HandleMotionEvent(MotionEvent *event)
551 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
554 motion_status = TRUE;
556 #if DEBUG_EVENTS_MOTION
557 Debug("event:motion", "button %d moved, x/y %d/%d\n",
558 button_status, event->x, event->y);
561 HandleButton(event->x, event->y, button_status, button_status);
564 void HandleWheelEvent(WheelEvent *event)
568 #if DEBUG_EVENTS_WHEEL
570 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
571 event->which, event->x, event->y);
573 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
574 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
575 event->which, event->x, event->y,
576 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
577 "SDL_MOUSEWHEEL_FLIPPED"));
581 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
582 event->x > 0 ? MB_WHEEL_RIGHT :
583 event->y < 0 ? MB_WHEEL_DOWN :
584 event->y > 0 ? MB_WHEEL_UP : 0);
586 #if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC)
587 // accelerated mouse wheel available on Mac and Windows
588 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
590 // no accelerated mouse wheel available on Unix/Linux
591 wheel_steps = DEFAULT_WHEEL_STEPS;
594 motion_status = FALSE;
596 button_status = button_nr;
597 HandleButton(0, 0, button_status, -button_nr);
599 button_status = MB_RELEASED;
600 HandleButton(0, 0, button_status, -button_nr);
603 void HandleWindowEvent(WindowEvent *event)
605 #if DEBUG_EVENTS_WINDOW
606 int subtype = event->event;
609 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
610 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
611 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
612 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
613 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
614 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
615 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
616 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
617 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
618 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
619 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
620 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
621 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
622 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
623 subtype == SDL_WINDOWEVENT_TAKE_FOCUS ? "SDL_WINDOWEVENT_TAKE_FOCUS" :
624 subtype == SDL_WINDOWEVENT_HIT_TEST ? "SDL_WINDOWEVENT_HIT_TEST" :
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 Key key = GetEventKey(event);
1463 #if DEBUG_EVENTS_KEY
1464 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1465 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1466 event->keysym.scancode,
1470 getKeyNameFromKey(key));
1473 #if defined(PLATFORM_ANDROID)
1474 if (key == KSYM_Back)
1476 // always map the "back" button to the "escape" key on Android devices
1479 else if (key == KSYM_Menu)
1481 // the "menu" button can be used to toggle displaying virtual buttons
1482 if (key_status == KEY_PRESSED)
1483 SetOverlayEnabled(!GetOverlayEnabled());
1485 else if (!textinput_status)
1487 // for any other "real" key event, disable virtual buttons
1488 SetOverlayEnabled(FALSE);
1490 // for any other "real" key event, disable overlay touch buttons
1491 runtime.uses_touch_device = FALSE;
1495 HandleKeyModState(key, key_status);
1497 // process all keys if not in text input mode or if non-printable keys
1498 if (!checkTextInputKey(key))
1499 HandleKey(key, key_status);
1502 static int HandleDropFileEvent(char *filename)
1504 Debug("event:dropfile", "filename == '%s'", filename);
1506 // check and extract dropped zip files into correct user data directory
1507 if (!strSuffixLower(filename, ".zip"))
1509 Warn("file '%s' not supported", filename);
1511 return TREE_TYPE_UNDEFINED;
1514 TreeInfo *tree_node = NULL;
1515 int tree_type = GetZipFileTreeType(filename);
1516 char *directory = TREE_USERDIR(tree_type);
1518 if (directory == NULL)
1520 Warn("zip file '%s' has invalid content!", filename);
1522 return TREE_TYPE_UNDEFINED;
1525 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1526 game_status == GAME_MODE_LEVELS &&
1527 leveldir_current->node_parent != NULL)
1529 // extract new level set next to currently selected level set
1530 tree_node = leveldir_current;
1532 // get parent directory of currently selected level set directory
1533 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1535 // use private level directory instead of top-level package level directory
1536 if (strPrefix(directory, options.level_directory) &&
1537 strEqual(leveldir_current->node_parent->fullpath, "."))
1538 directory = getUserLevelDir(NULL);
1541 // extract level or artwork set from zip file to target directory
1542 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1544 if (top_dir == NULL)
1546 // error message already issued by "ExtractZipFileIntoDirectory()"
1548 return TREE_TYPE_UNDEFINED;
1551 // add extracted level or artwork set to tree info structure
1552 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1554 // force restart after adding level collection
1555 if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
1557 Request("Program must be restarted after adding a new level collection!",
1563 // update menu screen (and possibly change current level set)
1564 DrawScreenAfterAddingSet(top_dir, tree_type);
1569 static void HandleDropTextEvent(char *text)
1571 Debug("event:droptext", "text == '%s'", text);
1574 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1575 int num_artwork_sets_succeeded,
1576 int num_files_failed)
1578 // only show request dialog if no other request dialog already active
1579 if (game.request_active)
1582 // this case can happen with drag-and-drop with older SDL versions
1583 if (num_level_sets_succeeded == 0 &&
1584 num_artwork_sets_succeeded == 0 &&
1585 num_files_failed == 0)
1590 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1592 char message_part1[50];
1594 sprintf(message_part1, "New %s set%s added",
1595 (num_artwork_sets_succeeded == 0 ? "level" :
1596 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1597 (num_level_sets_succeeded +
1598 num_artwork_sets_succeeded > 1 ? "s" : ""));
1600 if (num_files_failed > 0)
1601 sprintf(message, "%s, but %d dropped file%s failed!",
1602 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1604 sprintf(message, "%s!", message_part1);
1606 else if (num_files_failed > 0)
1608 sprintf(message, "Failed to process dropped file%s!",
1609 num_files_failed > 1 ? "s" : "");
1612 Request(message, REQ_CONFIRM);
1615 void HandleDropEvent(Event *event)
1617 static boolean confirm_on_drop_complete = FALSE;
1618 static int num_level_sets_succeeded = 0;
1619 static int num_artwork_sets_succeeded = 0;
1620 static int num_files_failed = 0;
1622 switch (event->type)
1626 confirm_on_drop_complete = TRUE;
1627 num_level_sets_succeeded = 0;
1628 num_artwork_sets_succeeded = 0;
1629 num_files_failed = 0;
1636 int tree_type = HandleDropFileEvent(event->drop.file);
1638 if (tree_type == TREE_TYPE_LEVEL_DIR)
1639 num_level_sets_succeeded++;
1640 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1641 tree_type == TREE_TYPE_SOUNDS_DIR ||
1642 tree_type == TREE_TYPE_MUSIC_DIR)
1643 num_artwork_sets_succeeded++;
1647 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1648 if (!confirm_on_drop_complete)
1650 // process all remaining events, including further SDL_DROPFILE events
1653 HandleDropCompleteEvent(num_level_sets_succeeded,
1654 num_artwork_sets_succeeded,
1657 num_level_sets_succeeded = 0;
1658 num_artwork_sets_succeeded = 0;
1659 num_files_failed = 0;
1667 HandleDropTextEvent(event->drop.file);
1672 case SDL_DROPCOMPLETE:
1674 HandleDropCompleteEvent(num_level_sets_succeeded,
1675 num_artwork_sets_succeeded,
1682 if (event->drop.file != NULL)
1683 SDL_free(event->drop.file);
1686 void HandleUserEvent(UserEvent *event)
1688 switch (event->code)
1690 case USEREVENT_ANIM_DELAY_ACTION:
1691 case USEREVENT_ANIM_EVENT_ACTION:
1692 // execute action functions until matching action was found
1693 if (DoKeysymAction(event->value1) ||
1694 DoGadgetAction(event->value1) ||
1695 DoScreenAction(event->value1))
1704 void HandleButton(int mx, int my, int button, int button_nr)
1706 static int old_mx = 0, old_my = 0;
1707 boolean button_hold = FALSE;
1708 boolean handle_gadgets = TRUE;
1709 int game_status_last = game_status;
1715 button_nr = -button_nr;
1724 #if defined(PLATFORM_ANDROID)
1725 // when playing, only handle gadgets when using "follow finger" controls
1726 // or when using touch controls in combination with the MM game engine
1727 // or when using gadgets that do not overlap with virtual buttons
1728 // or when touch controls are disabled (e.g., with mouse-only levels)
1730 (game_status != GAME_MODE_PLAYING ||
1731 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1732 strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1733 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1734 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1735 !CheckVirtualButtonPressed(mx, my, button)));
1737 // always recognize potentially releasing already pressed gadgets
1738 if (button == MB_RELEASED)
1739 handle_gadgets = TRUE;
1741 // always recognize pressing or releasing overlay touch buttons
1742 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1743 handle_gadgets = TRUE;
1746 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1748 // do not handle this button event anymore
1749 return; // force mouse event not to be handled at all
1752 if (handle_gadgets && HandleGadgets(mx, my, button))
1754 // do not handle this button event anymore with position on screen
1755 mx = my = -32; // force mouse event to be outside screen tiles
1757 // do not handle this button event anymore if game status has changed
1758 if (game_status != game_status_last)
1762 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1765 // do not use scroll wheel button events for anything other than gadgets
1766 if (IS_WHEEL_BUTTON(button_nr))
1769 switch (game_status)
1771 case GAME_MODE_TITLE:
1772 HandleTitleScreen(mx, my, 0, 0, button);
1775 case GAME_MODE_MAIN:
1776 HandleMainMenu(mx, my, 0, 0, button);
1779 case GAME_MODE_PSEUDO_TYPENAME:
1780 case GAME_MODE_PSEUDO_TYPENAMES:
1781 HandleTypeName(KSYM_Return);
1784 case GAME_MODE_NAMES:
1785 HandleChoosePlayerName(mx, my, 0, 0, button);
1788 case GAME_MODE_LEVELS:
1789 HandleChooseLevelSet(mx, my, 0, 0, button);
1792 case GAME_MODE_LEVELNR:
1793 HandleChooseLevelNr(mx, my, 0, 0, button);
1796 case GAME_MODE_SCORES:
1797 HandleHallOfFame(mx, my, 0, 0, button);
1800 case GAME_MODE_SCOREINFO:
1801 HandleScoreInfo(mx, my, 0, 0, button);
1804 case GAME_MODE_EDITOR:
1805 HandleLevelEditorIdle();
1808 case GAME_MODE_INFO:
1809 HandleInfoScreen(mx, my, 0, 0, button);
1812 case GAME_MODE_SETUP:
1813 HandleSetupScreen(mx, my, 0, 0, button);
1816 case GAME_MODE_PLAYING:
1817 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1818 HandleButtonOrFinger(mx, my, button);
1820 SetPlayerMouseAction(mx, my, button);
1823 if (button == MB_PRESSED && !motion_status && !button_hold &&
1824 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1825 DumpTileFromScreen(mx, my);
1835 #define MAX_CHEAT_INPUT_LEN 32
1837 static void HandleKeysSpecial(Key key)
1839 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1840 char letter = getCharFromKey(key);
1841 int cheat_input_len = strlen(cheat_input);
1847 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1849 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1850 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1852 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1855 cheat_input[cheat_input_len++] = letter;
1856 cheat_input[cheat_input_len] = '\0';
1858 #if DEBUG_EVENTS_KEY
1859 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1862 if (game_status == GAME_MODE_MAIN)
1864 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1865 strSuffix(cheat_input, ":ist"))
1867 InsertSolutionTape();
1869 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1870 strSuffix(cheat_input, ":pst"))
1874 else if (strSuffix(cheat_input, ":reload-graphics") ||
1875 strSuffix(cheat_input, ":rg"))
1877 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1880 else if (strSuffix(cheat_input, ":reload-sounds") ||
1881 strSuffix(cheat_input, ":rs"))
1883 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1886 else if (strSuffix(cheat_input, ":reload-music") ||
1887 strSuffix(cheat_input, ":rm"))
1889 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1892 else if (strSuffix(cheat_input, ":reload-artwork") ||
1893 strSuffix(cheat_input, ":ra"))
1895 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1896 1 << ARTWORK_TYPE_SOUNDS |
1897 1 << ARTWORK_TYPE_MUSIC);
1900 else if (strSuffix(cheat_input, ":dump-level") ||
1901 strSuffix(cheat_input, ":dl"))
1905 else if (strSuffix(cheat_input, ":dump-tape") ||
1906 strSuffix(cheat_input, ":dt"))
1910 else if (strSuffix(cheat_input, ":undo-tape") ||
1911 strSuffix(cheat_input, ":ut"))
1915 else if (strSuffix(cheat_input, ":fix-tape") ||
1916 strSuffix(cheat_input, ":ft"))
1918 FixTape_ForceSinglePlayer();
1920 else if (strSuffix(cheat_input, ":save-native-level") ||
1921 strSuffix(cheat_input, ":snl"))
1923 SaveNativeLevel(&level);
1925 else if (strSuffix(cheat_input, ":frames-per-second") ||
1926 strSuffix(cheat_input, ":fps"))
1928 global.show_frames_per_second = !global.show_frames_per_second;
1930 else if (strSuffix(cheat_input, ":xsn"))
1932 tile_cursor.xsn_debug = TRUE;
1935 else if (game_status == GAME_MODE_PLAYING)
1938 if (strSuffix(cheat_input, ".q"))
1939 DEBUG_SetMaximumDynamite();
1942 else if (game_status == GAME_MODE_EDITOR)
1944 if (strSuffix(cheat_input, ":dump-brush") ||
1945 strSuffix(cheat_input, ":DB"))
1949 else if (strSuffix(cheat_input, ":DDB"))
1954 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1956 if (letter == 'x') // copy brush to clipboard (small size)
1958 CopyBrushToClipboard_Small();
1960 else if (letter == 'c') // copy brush to clipboard (normal size)
1962 CopyBrushToClipboard();
1964 else if (letter == 'v') // paste brush from Clipboard
1966 CopyClipboardToBrush();
1968 else if (letter == 'z') // undo or redo last operation
1970 if (GetKeyModState() & KMOD_Shift)
1971 RedoLevelEditorOperation();
1973 UndoLevelEditorOperation();
1978 // special key shortcuts for all game modes
1979 if (strSuffix(cheat_input, ":dump-event-actions") ||
1980 strSuffix(cheat_input, ":dea") ||
1981 strSuffix(cheat_input, ":DEA"))
1983 DumpGadgetIdentifiers();
1984 DumpScreenIdentifiers();
1988 boolean HandleKeysDebug(Key key, int key_status)
1993 if (key_status != KEY_PRESSED)
1996 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1998 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
2000 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2002 if (key == setup.debug.frame_delay_key[i] &&
2003 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2005 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2006 setup.debug.frame_delay[i] : setup.game_frame_delay);
2008 if (!setup.debug.frame_delay_game_only)
2009 MenuFrameDelay = GameFrameDelay;
2011 SetVideoFrameDelay(GameFrameDelay);
2013 if (GameFrameDelay > ONE_SECOND_DELAY)
2014 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2015 else if (GameFrameDelay != 0)
2016 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2017 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2018 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2020 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2027 if (game_status == GAME_MODE_PLAYING)
2031 options.debug = !options.debug;
2033 Debug("event:key:debug", "debug mode %s",
2034 (options.debug ? "enabled" : "disabled"));
2038 else if (key == KSYM_v)
2040 Debug("event:key:debug", "currently using game engine version %d",
2041 game.engine_version);
2051 void HandleKey(Key key, int key_status)
2053 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2054 static boolean ignore_repeated_key = FALSE;
2055 static struct SetupKeyboardInfo ski;
2056 static struct SetupShortcutInfo ssi;
2065 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2066 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2067 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2068 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2069 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2070 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2075 if (HandleKeysDebug(key, key_status))
2076 return; // do not handle already processed keys again
2078 // map special keys (media keys / remote control buttons) to default keys
2079 if (key == KSYM_PlayPause)
2081 else if (key == KSYM_Select)
2084 HandleSpecialGameControllerKeys(key, key_status);
2086 if (game_status == GAME_MODE_PLAYING)
2088 // only needed for single-step tape recording mode
2089 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2092 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2094 byte key_action = 0;
2095 byte key_snap_action = 0;
2097 if (setup.input[pnr].use_joystick)
2100 ski = setup.input[pnr].key;
2102 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2103 if (key == *key_info[i].key_custom)
2104 key_action |= key_info[i].action;
2106 // use combined snap+direction keys for the first player only
2109 ssi = setup.shortcut;
2111 // also remember normal snap key when handling snap+direction keys
2112 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2114 for (i = 0; i < NUM_DIRECTIONS; i++)
2116 if (key == *key_info[i].key_snap)
2118 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2119 key_snap_action |= key_info[i].action;
2121 tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2126 if (key_status == KEY_PRESSED)
2128 stored_player[pnr].action |= key_action;
2129 stored_player[pnr].snap_action |= key_snap_action;
2133 stored_player[pnr].action &= ~key_action;
2134 stored_player[pnr].snap_action &= ~key_snap_action;
2137 // restore snap action if one of several pressed snap keys was released
2138 if (stored_player[pnr].snap_action)
2139 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2141 if (tape.recording && tape.pausing && tape.use_key_actions)
2143 if (tape.single_step)
2145 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2147 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2149 // if snap key already pressed, keep pause mode when releasing
2150 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2151 has_snapped[pnr] = TRUE;
2153 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2155 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2157 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2158 getRedDiskReleaseFlag_SP() == 0)
2160 // add a single inactive frame before dropping starts
2161 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2162 stored_player[pnr].force_dropping = TRUE;
2165 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2167 // if snap key was pressed without direction, leave pause mode
2168 if (!has_snapped[pnr])
2169 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2171 has_snapped[pnr] = FALSE;
2176 // prevent key release events from un-pausing a paused game
2177 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2178 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2182 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2183 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2186 // for any keyboard event, enable playfield mouse cursor
2187 if (key_action && key_status == KEY_PRESSED)
2188 SetPlayfieldMouseCursorEnabled(TRUE);
2193 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2194 if (key == key_info[i].key_default)
2195 joy |= key_info[i].action;
2200 if (key_status == KEY_PRESSED)
2201 key_joystick_mapping |= joy;
2203 key_joystick_mapping &= ~joy;
2208 if (game_status != GAME_MODE_PLAYING)
2209 key_joystick_mapping = 0;
2211 if (key_status == KEY_RELEASED)
2213 // reset flag to ignore repeated "key pressed" events after key release
2214 ignore_repeated_key = FALSE;
2216 // send key release event to global animation event handling
2217 HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2222 if ((key == KSYM_F11 ||
2223 ((key == KSYM_Return ||
2224 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2225 video.fullscreen_available &&
2226 !ignore_repeated_key)
2228 setup.fullscreen = !setup.fullscreen;
2230 ToggleFullscreenIfNeeded();
2232 if (game_status == GAME_MODE_SETUP)
2233 RedrawSetupScreenAfterFullscreenToggle();
2235 UpdateMousePosition();
2237 // set flag to ignore repeated "key pressed" events
2238 ignore_repeated_key = TRUE;
2243 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2244 key == KSYM_minus || key == KSYM_KP_Subtract ||
2245 key == KSYM_plus || key == KSYM_KP_Add ||
2246 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2247 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2248 video.window_scaling_available &&
2249 !video.fullscreen_enabled)
2251 if (key == KSYM_0 || key == KSYM_KP_0)
2252 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2253 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2254 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2256 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2258 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2259 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2260 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2261 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2263 ChangeWindowScalingIfNeeded();
2265 if (game_status == GAME_MODE_SETUP)
2266 RedrawSetupScreenAfterFullscreenToggle();
2268 UpdateMousePosition();
2273 // some key events are handled like clicks for global animations
2274 boolean click = (key == KSYM_space ||
2275 key == KSYM_Return ||
2276 key == KSYM_Escape);
2278 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2280 // do not handle this key event anymore
2281 if (key != KSYM_Escape) // always allow ESC key to be handled
2285 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2286 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2293 if (game_status == GAME_MODE_MAIN &&
2294 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2296 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2301 if (game_status == GAME_MODE_MAIN &&
2302 (setup.internal.info_screens_from_main ||
2303 leveldir_current->info_screens_from_main) &&
2304 (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2306 DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2311 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2313 if (key == setup.shortcut.save_game)
2315 else if (key == setup.shortcut.load_game)
2317 else if (key == setup.shortcut.restart_game)
2319 else if (key == setup.shortcut.pause_before_end)
2320 TapeReplayAndPauseBeforeEnd();
2321 else if (key == setup.shortcut.toggle_pause)
2322 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2324 HandleTapeButtonKeys(key);
2325 HandleSoundButtonKeys(key);
2328 if (game_status == GAME_MODE_SCOREINFO)
2330 HandleScreenGadgetKeys(key);
2333 if (game_status == GAME_MODE_PLAYING && !network_playing)
2335 int centered_player_nr_next = -999;
2337 if (key == setup.shortcut.focus_player_all)
2338 centered_player_nr_next = -1;
2340 for (i = 0; i < MAX_PLAYERS; i++)
2341 if (key == setup.shortcut.focus_player[i])
2342 centered_player_nr_next = i;
2344 if (centered_player_nr_next != -999)
2346 game.centered_player_nr_next = centered_player_nr_next;
2347 game.set_centered_player = TRUE;
2351 tape.centered_player_nr_next = game.centered_player_nr_next;
2352 tape.set_centered_player = TRUE;
2357 HandleKeysSpecial(key);
2359 if (HandleGadgetsKeyInput(key))
2360 return; // do not handle already processed keys again
2362 // special case: on "space" key, either continue playing or go to main menu
2363 if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2365 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2370 switch (game_status)
2372 case GAME_MODE_PSEUDO_TYPENAME:
2373 case GAME_MODE_PSEUDO_TYPENAMES:
2374 HandleTypeName(key);
2377 case GAME_MODE_TITLE:
2378 case GAME_MODE_MAIN:
2379 case GAME_MODE_NAMES:
2380 case GAME_MODE_LEVELS:
2381 case GAME_MODE_LEVELNR:
2382 case GAME_MODE_SETUP:
2383 case GAME_MODE_INFO:
2384 case GAME_MODE_SCORES:
2385 case GAME_MODE_SCOREINFO:
2387 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2394 if (game_status == GAME_MODE_TITLE)
2395 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2396 else if (game_status == GAME_MODE_MAIN)
2397 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2398 else if (game_status == GAME_MODE_NAMES)
2399 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2400 else if (game_status == GAME_MODE_LEVELS)
2401 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2402 else if (game_status == GAME_MODE_LEVELNR)
2403 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2404 else if (game_status == GAME_MODE_SETUP)
2405 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2406 else if (game_status == GAME_MODE_INFO)
2407 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2408 else if (game_status == GAME_MODE_SCORES)
2409 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2410 else if (game_status == GAME_MODE_SCOREINFO)
2411 HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2415 if (game_status != GAME_MODE_MAIN)
2416 FadeSkipNextFadeIn();
2418 if (game_status == GAME_MODE_TITLE)
2419 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2420 else if (game_status == GAME_MODE_NAMES)
2421 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2422 else if (game_status == GAME_MODE_LEVELS)
2423 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2424 else if (game_status == GAME_MODE_LEVELNR)
2425 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2426 else if (game_status == GAME_MODE_SETUP)
2427 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2428 else if (game_status == GAME_MODE_INFO)
2429 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2430 else if (game_status == GAME_MODE_SCORES)
2431 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2432 else if (game_status == GAME_MODE_SCOREINFO)
2433 HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2437 if (game_status == GAME_MODE_NAMES)
2438 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2439 else if (game_status == GAME_MODE_LEVELS)
2440 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2441 else if (game_status == GAME_MODE_LEVELNR)
2442 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2443 else if (game_status == GAME_MODE_SETUP)
2444 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2445 else if (game_status == GAME_MODE_INFO)
2446 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2447 else if (game_status == GAME_MODE_SCORES)
2448 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2449 else if (game_status == GAME_MODE_SCOREINFO)
2450 HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2453 case KSYM_Page_Down:
2454 if (game_status == GAME_MODE_NAMES)
2455 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2456 else if (game_status == GAME_MODE_LEVELS)
2457 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2458 else if (game_status == GAME_MODE_LEVELNR)
2459 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2460 else if (game_status == GAME_MODE_SETUP)
2461 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2462 else if (game_status == GAME_MODE_INFO)
2463 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2464 else if (game_status == GAME_MODE_SCORES)
2465 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2466 else if (game_status == GAME_MODE_SCOREINFO)
2467 HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2475 case GAME_MODE_EDITOR:
2476 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2477 HandleLevelEditorKeyInput(key);
2480 case GAME_MODE_PLAYING:
2485 RequestQuitGame(TRUE);
2495 if (key == KSYM_Escape)
2497 SetGameStatus(GAME_MODE_MAIN);
2506 void HandleNoEvent(void)
2508 HandleMouseCursor();
2510 switch (game_status)
2512 case GAME_MODE_PLAYING:
2513 HandleButtonOrFinger(-1, -1, -1);
2518 void HandleEventActions(void)
2520 // if (button_status && game_status != GAME_MODE_PLAYING)
2521 if (button_status && (game_status != GAME_MODE_PLAYING ||
2523 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2525 HandleButton(0, 0, button_status, -button_status);
2532 if (network.enabled)
2535 switch (game_status)
2537 case GAME_MODE_MAIN:
2538 DrawPreviewLevelAnimation();
2541 case GAME_MODE_EDITOR:
2542 HandleLevelEditorIdle();
2550 static void HandleTileCursor(int dx, int dy, int button)
2553 ClearPlayerMouseAction();
2560 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2561 (dx < 0 ? MB_LEFTBUTTON :
2562 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2564 else if (!tile_cursor.moving)
2566 int old_xpos = tile_cursor.xpos;
2567 int old_ypos = tile_cursor.ypos;
2568 int new_xpos = tile_cursor.xpos + dx;
2569 int new_ypos = tile_cursor.ypos + dy;
2571 if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2572 new_xpos = old_xpos;
2574 if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2575 new_ypos = old_ypos;
2577 SetTileCursorTargetXY(new_xpos, new_ypos);
2581 static int HandleJoystickForAllPlayers(void)
2585 boolean no_joysticks_configured = TRUE;
2586 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2587 static byte joy_action_last[MAX_PLAYERS];
2589 for (i = 0; i < MAX_PLAYERS; i++)
2590 if (setup.input[i].use_joystick)
2591 no_joysticks_configured = FALSE;
2593 // if no joysticks configured, map connected joysticks to players
2594 if (no_joysticks_configured)
2595 use_as_joystick_nr = TRUE;
2597 for (i = 0; i < MAX_PLAYERS; i++)
2599 byte joy_action = 0;
2601 joy_action = JoystickExt(i, use_as_joystick_nr);
2602 result |= joy_action;
2604 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2605 joy_action != joy_action_last[i])
2606 stored_player[i].action = joy_action;
2608 joy_action_last[i] = joy_action;
2614 void HandleJoystick(void)
2616 static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2617 static int joytest_last = 0;
2618 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2619 int delay_value = GADGET_FRAME_DELAY;
2620 int joystick = HandleJoystickForAllPlayers();
2621 int keyboard = key_joystick_mapping;
2622 int joy = (joystick | keyboard);
2623 int joytest = joystick;
2624 int left = joy & JOY_LEFT;
2625 int right = joy & JOY_RIGHT;
2626 int up = joy & JOY_UP;
2627 int down = joy & JOY_DOWN;
2628 int button = joy & JOY_BUTTON;
2629 int anybutton = AnyJoystickButton();
2630 int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2631 int dx = (left ? -1 : right ? 1 : 0);
2632 int dy = (up ? -1 : down ? 1 : 0);
2633 boolean use_delay_value_first = (joytest != joytest_last);
2634 boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2635 anybutton == JOY_BUTTON_NEW_RELEASED);
2637 if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2639 // do not handle this button event anymore
2643 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2644 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2645 anyTextGadgetActive()))
2647 // leave name input in main menu or text input gadget
2648 HandleKey(KSYM_Escape, KEY_PRESSED);
2649 HandleKey(KSYM_Escape, KEY_RELEASED);
2654 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2656 if (game_status == GAME_MODE_PLAYING)
2658 // when playing MM style levels, also use delay for keyboard events
2659 joytest |= keyboard;
2661 // only use first delay value for new events, but not for changed events
2662 use_delay_value_first = (!joytest != !joytest_last);
2664 // only use delay after the initial keyboard event
2668 // for any joystick or keyboard event, enable playfield tile cursor
2669 if (dx || dy || button)
2670 SetTileCursorEnabled(TRUE);
2673 // for any joystick event, enable playfield mouse cursor
2674 if (dx || dy || button)
2675 SetPlayfieldMouseCursorEnabled(TRUE);
2677 if (joytest && !button && !DelayReached(&joytest_delay))
2679 // delay joystick/keyboard actions if axes/keys continually pressed
2680 newbutton = dx = dy = 0;
2684 // first start with longer delay, then continue with shorter delay
2685 joytest_delay.value =
2686 (use_delay_value_first ? delay_value_first : delay_value);
2689 joytest_last = joytest;
2691 switch (game_status)
2693 case GAME_MODE_TITLE:
2694 case GAME_MODE_MAIN:
2695 case GAME_MODE_NAMES:
2696 case GAME_MODE_LEVELS:
2697 case GAME_MODE_LEVELNR:
2698 case GAME_MODE_SETUP:
2699 case GAME_MODE_INFO:
2700 case GAME_MODE_SCORES:
2701 case GAME_MODE_SCOREINFO:
2703 if (anyTextGadgetActive())
2706 if (game_status == GAME_MODE_TITLE)
2707 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2708 else if (game_status == GAME_MODE_MAIN)
2709 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2710 else if (game_status == GAME_MODE_NAMES)
2711 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2712 else if (game_status == GAME_MODE_LEVELS)
2713 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2714 else if (game_status == GAME_MODE_LEVELNR)
2715 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2716 else if (game_status == GAME_MODE_SETUP)
2717 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2718 else if (game_status == GAME_MODE_INFO)
2719 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2720 else if (game_status == GAME_MODE_SCORES)
2721 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2722 else if (game_status == GAME_MODE_SCOREINFO)
2723 HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2728 case GAME_MODE_PLAYING:
2730 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2731 if (tape.playing || keyboard)
2732 newbutton = ((joy & JOY_BUTTON) != 0);
2735 if (newbutton && game.all_players_gone)
2742 if (tape.recording && tape.pausing && tape.use_key_actions)
2744 if (tape.single_step)
2746 if (joystick & JOY_ACTION)
2747 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2751 if (joystick & JOY_ACTION)
2752 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2756 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2757 HandleTileCursor(dx, dy, button);
2766 void HandleSpecialGameControllerButtons(Event *event)
2771 switch (event->type)
2773 case SDL_CONTROLLERBUTTONDOWN:
2774 key_status = KEY_PRESSED;
2777 case SDL_CONTROLLERBUTTONUP:
2778 key_status = KEY_RELEASED;
2785 switch (event->cbutton.button)
2787 case SDL_CONTROLLER_BUTTON_START:
2791 case SDL_CONTROLLER_BUTTON_BACK:
2799 HandleKey(key, key_status);
2802 void HandleSpecialGameControllerKeys(Key key, int key_status)
2804 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2805 int button = SDL_CONTROLLER_BUTTON_INVALID;
2807 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2808 if (key == KSYM_Rewind)
2809 button = SDL_CONTROLLER_BUTTON_A;
2810 else if (key == KSYM_FastForward || key == KSYM_Menu)
2811 button = SDL_CONTROLLER_BUTTON_B;
2813 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2817 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2818 SDL_CONTROLLERBUTTONUP);
2820 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2821 event.cbutton.button = button;
2822 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2825 HandleJoystickEvent(&event);
2830 boolean DoKeysymAction(int keysym)
2834 Key key = (Key)(-keysym);
2836 HandleKey(key, KEY_PRESSED);
2837 HandleKey(key, KEY_RELEASED);