added configurable debug key shortcuts for changing game/menu frame rate
[rocksndiamonds.git] / src / events.c
index 041c49b4e13b54d0314a111cf00773e817b95800..3997764e0dfdb948517b13950db73e2eb260e0ac 100644 (file)
@@ -19,6 +19,7 @@
 #include "editor.h"
 #include "files.h"
 #include "tape.h"
+#include "anim.h"
 #include "network.h"
 
 
@@ -66,7 +67,7 @@ static int FilterEventsExt(const Event *event)
 
   /* do no reset mouse cursor before all pending events have been processed */
   if (gfx.cursor_mode == cursor_mode_last &&
-      ((effectiveGameStatus() == GAME_MODE_TITLE &&
+      ((game_status == GAME_MODE_TITLE &&
        gfx.cursor_mode == CURSOR_NONE) ||
        (game_status == GAME_MODE_PLAYING &&
        gfx.cursor_mode == CURSOR_PLAYFIELD)))
@@ -151,114 +152,63 @@ boolean NextValidEvent(Event *event)
   return FALSE;
 }
 
-void EventLoop(void)
+void HandleEvents()
 {
-  while (1)
+  Event event;
+  unsigned int event_frame_delay = 0;
+  unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
+
+  ResetDelayCounter(&event_frame_delay);
+
+  while (NextValidEvent(&event))
   {
-    if (PendingEvent())                /* got event */
+    switch (event.type)
     {
-      Event event;
+      case EVENT_BUTTONPRESS:
+      case EVENT_BUTTONRELEASE:
+       HandleButtonEvent((ButtonEvent *) &event);
+       break;
 
-      while (NextValidEvent(&event))
-      {
-       switch (event.type)
-       {
-         case EVENT_BUTTONPRESS:
-         case EVENT_BUTTONRELEASE:
-           HandleButtonEvent((ButtonEvent *) &event);
-           break;
-  
-         case EVENT_MOTIONNOTIFY:
-           HandleMotionEvent((MotionEvent *) &event);
-           break;
+      case EVENT_MOTIONNOTIFY:
+       HandleMotionEvent((MotionEvent *) &event);
+       break;
 
 #if defined(TARGET_SDL2)
-         case SDL_WINDOWEVENT:
-           HandleWindowEvent((WindowEvent *) &event);
-           break;
-
-         case EVENT_FINGERPRESS:
-         case EVENT_FINGERRELEASE:
-         case EVENT_FINGERMOTION:
-           HandleFingerEvent((FingerEvent *) &event);
-           break;
-
-         case EVENT_TEXTINPUT:
-           HandleTextEvent((TextEvent *) &event);
-           break;
-
-         case SDL_APP_WILLENTERBACKGROUND:
-         case SDL_APP_DIDENTERBACKGROUND:
-         case SDL_APP_WILLENTERFOREGROUND:
-         case SDL_APP_DIDENTERFOREGROUND:
-           HandlePauseResumeEvent((PauseResumeEvent *) &event);
-           break;
-#endif
-
-         case EVENT_KEYPRESS:
-         case EVENT_KEYRELEASE:
-           HandleKeyEvent((KeyEvent *) &event);
-           break;
-
-         default:
-           HandleOtherEvents(&event);
-           break;
-       }
-      }
-    }
-    else
-    {
-      if (effectiveGameStatus() == GAME_MODE_TITLE)
-      {
-       /* when showing title screens, hide mouse pointer (if not moved) */
-
-       if (gfx.cursor_mode != CURSOR_NONE &&
-           DelayReached(&special_cursor_delay, special_cursor_delay_value))
-       {
-         SetMouseCursor(CURSOR_NONE);
-       }
-      }
-      else if (game_status == GAME_MODE_PLAYING && !tape.pausing)
-      {
-       /* when playing, display a special mouse pointer inside the playfield */
+      case SDL_WINDOWEVENT:
+       HandleWindowEvent((WindowEvent *) &event);
+       break;
 
-       if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
-           cursor_inside_playfield &&
-           DelayReached(&special_cursor_delay, special_cursor_delay_value))
-       {
-         SetMouseCursor(CURSOR_PLAYFIELD);
-       }
-      }
-      else if (gfx.cursor_mode != CURSOR_DEFAULT)
-      {
-       SetMouseCursor(CURSOR_DEFAULT);
-      }
+      case EVENT_FINGERPRESS:
+      case EVENT_FINGERRELEASE:
+      case EVENT_FINGERMOTION:
+       HandleFingerEvent((FingerEvent *) &event);
+       break;
 
-      /* this is set after all pending events have been processed */
-      cursor_mode_last = gfx.cursor_mode;
-    }
+      case EVENT_TEXTINPUT:
+       HandleTextEvent((TextEvent *) &event);
+       break;
 
-    /* also execute after pending events have been processed before */
-    HandleNoEvent();
+      case SDL_APP_WILLENTERBACKGROUND:
+      case SDL_APP_DIDENTERBACKGROUND:
+      case SDL_APP_WILLENTERFOREGROUND:
+      case SDL_APP_DIDENTERFOREGROUND:
+       HandlePauseResumeEvent((PauseResumeEvent *) &event);
+       break;
+#endif
 
-    /* don't use all CPU time when idle; the main loop while playing
-       has its own synchronization and is CPU friendly, too */
+      case EVENT_KEYPRESS:
+      case EVENT_KEYRELEASE:
+       HandleKeyEvent((KeyEvent *) &event);
+       break;
 
-    if (game_status == GAME_MODE_PLAYING)
-    {
-      HandleGameActions();
-    }
-    else
-    {
-      if (!PendingEvent())     /* delay only if no pending events */
-       Delay(10);
+      default:
+       HandleOtherEvents(&event);
+       break;
     }
 
-    /* refresh window contents from drawing buffer, if needed */
-    BackToFront();
-
-    if (game_status == GAME_MODE_QUIT)
-      return;
+    // do not handle events for longer than standard frame delay period
+    if (DelayReached(&event_frame_delay, event_frame_delay_value))
+      break;
   }
 }
 
@@ -304,6 +254,68 @@ void HandleOtherEvents(Event *event)
   }
 }
 
+void HandleMouseCursor()
+{
+  if (game_status == GAME_MODE_TITLE)
+  {
+    /* when showing title screens, hide mouse pointer (if not moved) */
+
+    if (gfx.cursor_mode != CURSOR_NONE &&
+       DelayReached(&special_cursor_delay, special_cursor_delay_value))
+    {
+      SetMouseCursor(CURSOR_NONE);
+    }
+  }
+  else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
+                                               tape.single_step))
+  {
+    /* when playing, display a special mouse pointer inside the playfield */
+
+    if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
+       cursor_inside_playfield &&
+       DelayReached(&special_cursor_delay, special_cursor_delay_value))
+    {
+      SetMouseCursor(CURSOR_PLAYFIELD);
+    }
+  }
+  else if (gfx.cursor_mode != CURSOR_DEFAULT)
+  {
+    SetMouseCursor(CURSOR_DEFAULT);
+  }
+
+  /* this is set after all pending events have been processed */
+  cursor_mode_last = gfx.cursor_mode;
+}
+
+void EventLoop(void)
+{
+  while (1)
+  {
+    if (PendingEvent())
+      HandleEvents();
+    else
+      HandleMouseCursor();
+
+    /* also execute after pending events have been processed before */
+    HandleNoEvent();
+
+    /* don't use all CPU time when idle; the main loop while playing
+       has its own synchronization and is CPU friendly, too */
+
+    if (game_status == GAME_MODE_PLAYING)
+      HandleGameActions();
+
+    /* always copy backbuffer to visible screen for every video frame */
+    BackToFront();
+
+    /* reset video frame delay to default (may change again while playing) */
+    SetVideoFrameDelay(MenuFrameDelay);
+
+    if (game_status == GAME_MODE_QUIT)
+      return;
+  }
+}
+
 void ClearEventQueue()
 {
   while (PendingEvent())
@@ -448,10 +460,13 @@ void HandleWindowEvent(WindowEvent *event)
        event_name, event->data1, event->data2);
 #endif
 
+#if 0
+  // (not needed, as the screen gets redrawn every 20 ms anyway)
   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
       event->event == SDL_WINDOWEVENT_RESIZED ||
       event->event == SDL_WINDOWEVENT_EXPOSED)
     SDLRedrawWindow();
+#endif
 
   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
   {
@@ -465,8 +480,10 @@ void HandleWindowEvent(WindowEvent *event)
       int new_xpercent = (100 * new_window_width  / video.width);
       int new_ypercent = (100 * new_window_height / video.height);
 
-      setup.window_scaling_percent = video.window_scaling_percent =
-       MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
+      // (extreme window scaling allowed, but cannot be saved permanently)
+      video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
+      setup.window_scaling_percent =
+       MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
            MAX_WINDOW_SCALING_PERCENT);
 
       video.window_width  = new_window_width;
@@ -1144,6 +1161,7 @@ static void HandleKeysSpecial(Key key)
 void HandleKey(Key key, int key_status)
 {
   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
+  static boolean ignore_repeated_key = FALSE;
   static struct SetupKeyboardInfo ski;
   static struct SetupShortcutInfo ssi;
   static struct
@@ -1311,12 +1329,18 @@ void HandleKey(Key key, int key_status)
     key_joystick_mapping = 0;
 
   if (key_status == KEY_RELEASED)
+  {
+    // reset flag to ignore repeated "key pressed" events after key release
+    ignore_repeated_key = FALSE;
+
     return;
+  }
 
   if ((key == KSYM_F11 ||
        ((key == KSYM_Return ||
         key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
-      video.fullscreen_available)
+      video.fullscreen_available &&
+      !ignore_repeated_key)
   {
     setup.fullscreen = !setup.fullscreen;
 
@@ -1325,14 +1349,19 @@ void HandleKey(Key key, int key_status)
     if (game_status == GAME_MODE_SETUP)
       RedrawSetupScreenAfterFullscreenToggle();
 
+    // set flag to ignore repeated "key pressed" events
+    ignore_repeated_key = TRUE;
+
     return;
   }
 
   if ((key == KSYM_minus ||
        key == KSYM_plus ||
+       key == KSYM_equal ||    // ("Shift-=" is "+" on US keyboards)
        key == KSYM_0) &&
       ((GetKeyModState() & KMOD_Control) ||
-       (GetKeyModState() & KMOD_Alt)) &&
+       (GetKeyModState() & KMOD_Alt) ||
+       (GetKeyModState() & KMOD_Meta)) &&
       video.window_scaling_available &&
       !video.fullscreen_enabled)
   {
@@ -1494,10 +1523,6 @@ void HandleKey(Key key, int key_status)
          break;
 
 #ifdef DEBUG
-       case KSYM_0:
-         GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
-         break;
-
        case KSYM_b:
          setup.sp_show_border_elements = !setup.sp_show_border_elements;
          printf("Supaplex border elements %s\n",
@@ -1524,20 +1549,6 @@ void HandleKey(Key key, int key_status)
          break;
 
 #ifdef DEBUG
-       case KSYM_0:
-         if (key == KSYM_0)
-         {
-           if (GameFrameDelay == 500)
-             GameFrameDelay = GAME_FRAME_DELAY;
-           else
-             GameFrameDelay = 500;
-         }
-         else
-           GameFrameDelay = (key - KSYM_0) * 10;
-         printf("Game speed == %d%% (%d ms delay between two frames)\n",
-                GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
-         break;
-
        case KSYM_d:
          if (options.debug)
          {
@@ -1566,12 +1577,48 @@ void HandleKey(Key key, int key_status)
     default:
       if (key == KSYM_Escape)
       {
-       game_status = GAME_MODE_MAIN;
+       SetGameStatus(GAME_MODE_MAIN);
+
        DrawMainMenu();
 
        return;
       }
   }
+
+#ifdef DEBUG
+  if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
+  {
+    boolean mod_key_pressed = ((GetKeyModState() & KMOD_Control) ||
+                              (GetKeyModState() & KMOD_Alt) ||
+                              (GetKeyModState() & KMOD_Meta));
+
+    for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
+    {
+      if (key == setup.debug.frame_delay_key[i] &&
+         (mod_key_pressed || !setup.debug.frame_delay_use_mod_key))
+      {
+       GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
+                         setup.debug.frame_delay[i] : GAME_FRAME_DELAY);
+
+       if (!setup.debug.frame_delay_game_only)
+         MenuFrameDelay = GameFrameDelay;
+
+       SetVideoFrameDelay(GameFrameDelay);
+
+       if (GameFrameDelay > ONE_SECOND_DELAY)
+         Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
+       else if (GameFrameDelay != 0)
+         Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
+               GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
+               GAME_FRAME_DELAY * 100 / GameFrameDelay);
+       else
+         Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
+
+       break;
+      }
+    }
+  }
+#endif
 }
 
 void HandleNoEvent()
@@ -1595,15 +1642,6 @@ void HandleNoEvent()
   {
     case GAME_MODE_MAIN:
       DrawPreviewLevelAnimation();
-      DoAnimation();
-      break;
-
-    case GAME_MODE_LEVELS:
-    case GAME_MODE_LEVELNR:
-    case GAME_MODE_SETUP:
-    case GAME_MODE_INFO:
-    case GAME_MODE_SCORES:
-      DoAnimation();
       break;
 
     case GAME_MODE_EDITOR: