SDL: Allow the application to draw while Windows is in a modal move/resize loop

From 02f356439d1fedf0b4e5f96cd23c2d570e2f7be2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 8 Nov 2023 13:11:21 -0800
Subject: [PATCH] Allow the application to draw while Windows is in a modal
 move/resize loop

If you're using the application main callbacks, your SDL_AppIterate() function will be called while Windows is moving and resizing your window. If not, then SDL will send an SDL_EVENT_WINDOW_EXPOSED event for your window and you can use an event watcher to redraw your window directly from the callback.

Fixes https://github.com/libsdl-org/SDL/issues/1059
Closes https://github.com/libsdl-org/SDL/pull/4836
---
 src/main/SDL_main_callbacks.c            | 24 ++++++++++++++-----
 src/main/SDL_main_callbacks.h            |  5 ++--
 src/main/generic/SDL_sysmain_callbacks.c |  2 +-
 src/video/windows/SDL_windowsevents.c    | 30 ++++++++++++++++++++++++
 4 files changed, 52 insertions(+), 9 deletions(-)

diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c
index 0c091323f03e..0e7fb8d2895f 100644
--- a/src/main/SDL_main_callbacks.c
+++ b/src/main/SDL_main_callbacks.c
@@ -80,6 +80,14 @@ static int SDLCALL SDL_MainCallbackEventWatcher(void *userdata, SDL_Event *event
     return 0;
 }
 
+SDL_bool SDL_HasMainCallbacks()
+{
+    if (SDL_main_iteration_callback) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
 int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
 {
     SDL_main_iteration_callback = appiter;
@@ -104,16 +112,20 @@ int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_
     return SDL_AtomicGet(&apprc);
 }
 
-int SDL_IterateMainCallbacks(void)
+int SDL_IterateMainCallbacks(SDL_bool pump_events)
 {
-    SDL_PumpEvents();
+    if (pump_events) {
+        SDL_PumpEvents();
+    }
     SDL_DispatchMainCallbackEvents();
 
-    int rc = SDL_main_iteration_callback();
-    if (!SDL_AtomicCAS(&apprc, 0, rc)) {
-        rc = SDL_AtomicGet(&apprc);  // something else already set a quit result, keep that.
+    int rc = SDL_AtomicGet(&apprc);
+    if (rc == 0) {
+        rc = SDL_main_iteration_callback();
+        if (!SDL_AtomicCAS(&apprc, 0, rc)) {
+            rc = SDL_AtomicGet(&apprc); // something else already set a quit result, keep that.
+        }
     }
-
     return rc;
 }
 
diff --git a/src/main/SDL_main_callbacks.h b/src/main/SDL_main_callbacks.h
index 9df171a99c13..229fec7766a0 100644
--- a/src/main/SDL_main_callbacks.h
+++ b/src/main/SDL_main_callbacks.h
@@ -22,8 +22,9 @@
 #ifndef SDL_main_callbacks_h_
 #define SDL_main_callbacks_h_
 
-int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func _appiter, SDL_AppEvent_func _appevent, SDL_AppQuit_func _appquit);
-int SDL_IterateMainCallbacks(void);
+SDL_bool SDL_HasMainCallbacks();
+int SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func _appiter, SDL_AppEvent_func _appevent, SDL_AppQuit_func _appquit);
+int SDL_IterateMainCallbacks(SDL_bool pump_events);
 void SDL_QuitMainCallbacks(void);
 
 #endif // SDL_main_callbacks_h_
diff --git a/src/main/generic/SDL_sysmain_callbacks.c b/src/main/generic/SDL_sysmain_callbacks.c
index 1fcb7d767f8f..fa18e80002c5 100644
--- a/src/main/generic/SDL_sysmain_callbacks.c
+++ b/src/main/generic/SDL_sysmain_callbacks.c
@@ -45,7 +45,7 @@ int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit,
 
         Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0;
 
-        while ((rc = SDL_IterateMainCallbacks()) == 0) {
+        while ((rc = SDL_IterateMainCallbacks(SDL_TRUE)) == 0) {
             // !!! FIXME: this can be made more complicated if we decide to
             // !!! FIXME: optionally hand off callback responsibility to the
             // !!! FIXME: video subsystem (for example, if Wayland has a
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 57abda4fb1bc..cc6b1f63b0ee 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -28,6 +28,7 @@
 #include "../../events/SDL_events_c.h"
 #include "../../events/SDL_touch_c.h"
 #include "../../events/scancodes_windows.h"
+#include "../../main/SDL_main_callbacks.h"
 
 /* Dropfile support */
 #include <shellapi.h>
@@ -106,6 +107,10 @@
 #define IS_SURROGATE_PAIR(h, l) (IS_HIGH_SURROGATE(h) && IS_LOW_SURROGATE(l))
 #endif
 
+#ifndef USER_TIMER_MINIMUM
+#define USER_TIMER_MINIMUM 0x0000000A
+#endif
+
 /* Used to compare Windows message timestamps */
 #define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
 
@@ -1283,6 +1288,31 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         }
     } break;
 
+    case WM_ENTERSIZEMOVE:
+    case WM_ENTERMENULOOP:
+    {
+        SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL);
+    } break;
+
+    case WM_TIMER:
+    {
+        if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) {
+            if (SDL_HasMainCallbacks()) {
+                SDL_IterateMainCallbacks(SDL_FALSE);
+            } else {
+                // Send an expose event so the application can redraw
+                SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
+            }
+            return 0;
+        }
+    } break;
+
+    case WM_EXITSIZEMOVE:
+    case WM_EXITMENULOOP:
+    {
+        KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks);
+    } break;
+
     case WM_SIZE:
     {
         switch (wParam) {