fix(window): defer caption-button window ops out of the ImGui frame

The custom minimize / maximize / restore buttons are drawn from inside
app.render(), while an ImGui frame is open. Calling SDL to change the
window state there pushes resize events synchronously, which re-enters
the resize watcher and can nest a new frame inside the open one.

- A click now records the requested op instead of acting on it, and the
  window state change is applied once the frame has closed, after the
  pacing block so the follow-up redraw is not dropped.

Windows-only; it rides the borderless caption controls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 18:16:50 -05:00
co-authored by Claude Opus 4.8
parent 49240facff
commit 53da160546
+19 -5
View File
@@ -334,6 +334,11 @@ static LRESULT CALLBACK BorderlessWndProc(HWND hwnd, UINT msg,
return CallWindowProcW(g_sdlWndProc, hwnd, msg, wParam, lParam);
}
// Caption-button window ops are deferred out of the open ImGui frame and run
// after the frame closes (SDL pushes resize events synchronously). (C146)
enum class CaptionOp { None, Minimize, Maximize, Restore };
static CaptionOp g_pendingCaptionOp = CaptionOp::None;
// Draw custom window-control buttons (minimize / maximize-restore / close)
// on the foreground draw list, and handle title-bar dragging.
static void renderBorderlessControls(SDL_Window* window, HWND /*hwnd*/,
@@ -380,7 +385,7 @@ static void renderBorderlessControls(SDL_Window* window, HWND /*hwnd*/,
float cy = oy + btnH * 0.5f, lx = minX + btnW * 0.5f - iconHalf;
fg->AddLine(ImVec2(lx, cy), ImVec2(lx + iconHalf * 2.0f, cy),
wcIcon, lineThk);
if (hov && click) SDL_MinimizeWindow(window);
if (hov && click) g_pendingCaptionOp = CaptionOp::Minimize;
}
// --- Maximize / Restore button ---
@@ -402,10 +407,7 @@ static void renderBorderlessControls(SDL_Window* window, HWND /*hwnd*/,
ImVec2(cx + iconHalf, cy + iconHalf - dp),
wcIcon, 0, 0, lineThk);
}
if (hov && click) {
if (maximized) SDL_RestoreWindow(window);
else SDL_MaximizeWindow(window);
}
if (hov && click) g_pendingCaptionOp = maximized ? CaptionOp::Restore : CaptionOp::Maximize;
}
// --- Close button ---
@@ -2374,6 +2376,18 @@ int main(int argc, char* argv[])
nextAmbientFrameMs = lastFrameStartNs / 1000000 + interval;
}
}
#ifdef DRAGONX_USE_DX11
if (g_pendingCaptionOp != CaptionOp::None) {
switch (g_pendingCaptionOp) {
case CaptionOp::Minimize: SDL_MinimizeWindow(window); break;
case CaptionOp::Maximize: SDL_MaximizeWindow(window); break;
case CaptionOp::Restore: SDL_RestoreWindow(window); break;
default: break;
}
g_pendingCaptionOp = CaptionOp::None;
needsRedraw = true; // after the pacing block, so it is not overwritten
}
#endif
}
// Remove resize event watcher