Fix GLFW X11 viewport synchronization

This commit is contained in:
Bjorn Schobben 2026-08-12 15:51:11 +02:00
parent 83f668625a
commit b3ea4243b1
6 changed files with 279 additions and 29 deletions

View File

@ -31,6 +31,8 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-08-12: [Docking] Multi-viewport: Synchronize window manager adjustments instead of treating them as setter echoes. On X11, when the native API is available, observe synthetic ConfigureNotify events so fully rejected requests also synchronize without a timeout. (#9356, #9398, #9442)
// 2026-08-12: [Docking] Multi-viewport: Disable support on Wayland with legacy GLFW versions as global window positioning is unavailable.
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-04-21: Added a Win32-specific implementation of ImGui_ImplGlfw_GetContentScaleXXXX functions for legacy GLFW 3.2.
// 2026-03-25: Mouse cursor is properly restored if changed by user app/code while using glfwSetInputMode(..., GLFW_CURSOR_DISABLED) or ImGuiConfigFlags_NoMouseCursorChange. Amend change from 2025-12-10.
@ -235,6 +237,12 @@ typedef Atom (*PFN_XInternAtom)(Display*, const char* ,Bool);
typedef int (*PFN_XChangeProperty)(Display*, Window, Atom, Atom, int, int, const unsigned char*, int);
typedef int (*PFN_XChangeWindowAttributes)(Display*, Window, unsigned long, XSetWindowAttributes*);
typedef int (*PFN_XFlush)(Display*);
typedef Display* (*PFN_XOpenDisplay)(const char*);
typedef int (*PFN_XCloseDisplay)(Display*);
typedef int (*PFN_XSelectInput)(Display*, Window, long);
typedef int (*PFN_XPending)(Display*);
typedef int (*PFN_XNextEvent)(Display*, XEvent*);
typedef int (*PFN_XSync)(Display*, Bool);
#endif
// GLFW data
@ -281,6 +289,13 @@ struct ImGui_ImplGlfw_Data
PFN_XChangeProperty XChangeProperty;
PFN_XChangeWindowAttributes XChangeWindowAttributes;
PFN_XFlush XFlush;
PFN_XOpenDisplay XOpenDisplay;
PFN_XCloseDisplay XCloseDisplay;
PFN_XSelectInput XSelectInput;
PFN_XPending XPending;
PFN_XNextEvent XNextEvent;
PFN_XSync XSync;
Display* X11EventObserverDisplay;
#endif
ImGui_ImplGlfw_Data() { memset((void*)this, 0, sizeof(*this)); }
@ -310,6 +325,9 @@ static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData(GLFWwindow* window)
static void ImGui_ImplGlfw_UpdateMonitors();
static void ImGui_ImplGlfw_InitMultiViewportSupport();
static void ImGui_ImplGlfw_ShutdownMultiViewportSupport();
#if GLFW_HAS_X11
static void ImGui_ImplGlfw_UpdateX11ConfigureEvents();
#endif
// Functions
static bool ImGui_ImplGlfw_IsWayland()
@ -739,11 +757,7 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
bool has_viewports = false;
#ifndef __EMSCRIPTEN__
has_viewports = true;
#if GLFW_HAS_GETPLATFORM
if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND)
has_viewports = false;
#endif
has_viewports = !bd->IsWayland;
if (has_viewports)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
#endif
@ -835,11 +849,20 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
const char* x11_module_path = "libX11.so.6";
#endif
bd->X11Module = dlopen(x11_module_path, RTLD_LAZY | RTLD_LOCAL);
bd->XInternAtom = (PFN_XInternAtom)dlsym(bd->X11Module, "XInternAtom");
bd->XChangeProperty = (PFN_XChangeProperty)dlsym(bd->X11Module, "XChangeProperty");
bd->XChangeWindowAttributes = (PFN_XChangeWindowAttributes)dlsym(bd->X11Module, "XChangeWindowAttributes");
bd->XFlush = (PFN_XFlush)dlsym(bd->X11Module, "XFlush");
IM_ASSERT(bd->XInternAtom != nullptr && bd->XChangeProperty != nullptr && bd->XChangeWindowAttributes != nullptr && bd->XFlush != nullptr);
if (bd->X11Module != nullptr)
{
bd->XInternAtom = (PFN_XInternAtom)dlsym(bd->X11Module, "XInternAtom");
bd->XChangeProperty = (PFN_XChangeProperty)dlsym(bd->X11Module, "XChangeProperty");
bd->XChangeWindowAttributes = (PFN_XChangeWindowAttributes)dlsym(bd->X11Module, "XChangeWindowAttributes");
bd->XFlush = (PFN_XFlush)dlsym(bd->X11Module, "XFlush");
bd->XOpenDisplay = (PFN_XOpenDisplay)dlsym(bd->X11Module, "XOpenDisplay");
bd->XCloseDisplay = (PFN_XCloseDisplay)dlsym(bd->X11Module, "XCloseDisplay");
bd->XSelectInput = (PFN_XSelectInput)dlsym(bd->X11Module, "XSelectInput");
bd->XPending = (PFN_XPending)dlsym(bd->X11Module, "XPending");
bd->XNextEvent = (PFN_XNextEvent)dlsym(bd->X11Module, "XNextEvent");
bd->XSync = (PFN_XSync)dlsym(bd->X11Module, "XSync");
IM_ASSERT(bd->XInternAtom != nullptr && bd->XChangeProperty != nullptr && bd->XChangeWindowAttributes != nullptr && bd->XFlush != nullptr);
}
}
#endif
@ -907,6 +930,11 @@ void ImGui_ImplGlfw_Shutdown()
#endif
#if GLFW_HAS_X11
if (bd->X11EventObserverDisplay != nullptr)
{
bd->XCloseDisplay(bd->X11EventObserverDisplay);
bd->X11EventObserverDisplay = nullptr;
}
if (bd->X11Module != nullptr)
dlclose(bd->X11Module);
#endif
@ -1201,6 +1229,9 @@ void ImGui_ImplGlfw_NewFrame()
// Setup main viewport size (every frame to accommodate for window resizing)
ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale);
ImGui_ImplGlfw_UpdateMonitors();
#if GLFW_HAS_X11
ImGui_ImplGlfw_UpdateX11ConfigureEvents();
#endif
// Setup time step
// (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644)
@ -1294,6 +1325,11 @@ struct ImGui_ImplGlfw_ViewportData
bool WindowOwned;
int IgnoreWindowPosEventFrame;
int IgnoreWindowSizeEventFrame;
int LastWindowPosRequest[2];
int LastWindowSizeRequest[2];
#if GLFW_HAS_X11
::Window X11Window;
#endif
#ifdef _WIN32
WNDPROC PrevWndProc;
#endif
@ -1314,30 +1350,75 @@ static void ImGui_ImplGlfw_WindowCloseCallback(GLFWwindow* window)
// - on Linux it is queued and invoked during glfwPollEvents()
// Because the event doesn't always fire on glfwSetWindowXXX() we use a frame counter tag to only
// ignore recent glfwSetWindowXXX() calls.
static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int, int)
static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int x, int y)
{
if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window))
{
if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData)
{
bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowPosEventFrame + 1);
//data->IgnoreWindowPosEventFrame = -1;
if (ignore_event)
// A window manager may adjust the requested position. Only ignore the callback when it reports
// the latest position passed to glfwSetWindowPos() and the viewport still expects that position.
// Otherwise query and apply the authoritative platform position.
const bool is_latest_request = (x == vd->LastWindowPosRequest[0] && y == vd->LastWindowPosRequest[1]);
const bool is_current_viewport_pos = (x == (int)viewport->Pos.x && y == (int)viewport->Pos.y);
if (ignore_event && is_latest_request && is_current_viewport_pos)
return;
}
viewport->PlatformRequestMove = true;
}
}
static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int, int)
#if GLFW_HAS_X11
// X11 window managers report a completely rejected ConfigureRequest with a synthetic ConfigureNotify containing the
// unchanged geometry. GLFW consumes that event but doesn't invoke its public callback when the cached geometry didn't
// change. Observe synthetic ConfigureNotify events through a separate X connection so those rejections still reach
// Dear ImGui without relying on a timing heuristic. The application should call glfwPollEvents() before this NewFrame().
static void ImGui_ImplGlfw_UpdateX11ConfigureEvents()
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->X11EventObserverDisplay == nullptr)
return;
while (bd->XPending(bd->X11EventObserverDisplay) > 0)
{
XEvent event;
bd->XNextEvent(bd->X11EventObserverDisplay, &event);
if (event.type != ConfigureNotify || !event.xconfigure.send_event)
continue;
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int viewport_n = 1; viewport_n < platform_io.Viewports.Size; viewport_n++)
{
ImGuiViewport* viewport = platform_io.Viewports[viewport_n];
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
if (vd == nullptr || vd->X11Window != event.xconfigure.window)
continue;
int x, y, width, height;
glfwGetWindowPos(vd->Window, &x, &y);
glfwGetWindowSize(vd->Window, &width, &height);
if (x != (int)viewport->Pos.x || y != (int)viewport->Pos.y)
viewport->PlatformRequestMove = true;
if (width != (int)viewport->Size.x || height != (int)viewport->Size.y)
viewport->PlatformRequestResize = true;
break;
}
}
}
#endif
static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int width, int height)
{
if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window))
{
if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData)
{
bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowSizeEventFrame + 1);
//data->IgnoreWindowSizeEventFrame = -1;
if (ignore_event)
// Preserve delayed setter-echo suppression only while the viewport still expects that exact size.
const bool is_latest_request = (width == vd->LastWindowSizeRequest[0] && height == vd->LastWindowSizeRequest[1]);
const bool is_current_viewport_size = (width == (int)viewport->Size.x && height == (int)viewport->Size.y);
if (ignore_event && is_latest_request && is_current_viewport_size)
return;
}
viewport->PlatformRequestResize = true;
@ -1349,7 +1430,7 @@ static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int, int)
static void ImGui_ImplGlfw_SetWindowFloating(ImGui_ImplGlfw_Data* bd, GLFWwindow* window)
{
#ifdef GLFW_EXPOSE_NATIVE_X11
if (glfwGetPlatform() == GLFW_PLATFORM_X11)
if (glfwGetPlatform() == GLFW_PLATFORM_X11 && bd->XInternAtom != nullptr && bd->XChangeProperty != nullptr && bd->XChangeWindowAttributes != nullptr && bd->XFlush != nullptr)
{
Display* display = glfwGetX11Display();
Window xwindow = glfwGetX11Window(window);
@ -1405,6 +1486,26 @@ static void ImGui_ImplGlfw_CreateWindow(ImGuiViewport* viewport)
viewport->PlatformHandleRaw = (void*)glfwGetCocoaWindow(vd->Window);
#endif
glfwSetWindowPos(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y);
#if GLFW_HAS_X11
#if GLFW_HAS_GETPLATFORM
const bool is_x11 = (glfwGetPlatform() == GLFW_PLATFORM_X11);
#else
const bool is_x11 = !bd->IsWayland;
#endif
const bool has_x11_observer_api = (bd->XOpenDisplay != nullptr && bd->XCloseDisplay != nullptr && bd->XSelectInput != nullptr && bd->XPending != nullptr && bd->XNextEvent != nullptr && bd->XSync != nullptr);
if (is_x11 && has_x11_observer_api && bd->X11EventObserverDisplay == nullptr)
bd->X11EventObserverDisplay = bd->XOpenDisplay(DisplayString(glfwGetX11Display()));
if (bd->X11EventObserverDisplay != nullptr)
{
bd->XSync(glfwGetX11Display(), False);
vd->X11Window = glfwGetX11Window(vd->Window);
if (vd->X11Window != None)
{
bd->XSelectInput(bd->X11EventObserverDisplay, vd->X11Window, StructureNotifyMask);
bd->XSync(bd->X11EventObserverDisplay, False);
}
}
#endif
// Install GLFW callbacks for secondary viewports
glfwSetWindowFocusCallback(vd->Window, ImGui_ImplGlfw_WindowFocusCallback);
@ -1429,6 +1530,14 @@ static void ImGui_ImplGlfw_DestroyWindow(ImGuiViewport* viewport)
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData)
{
#if GLFW_HAS_X11
if (bd->X11EventObserverDisplay != nullptr && vd->X11Window != 0)
{
bd->XSelectInput(bd->X11EventObserverDisplay, vd->X11Window, NoEventMask);
bd->XSync(bd->X11EventObserverDisplay, False);
ImGui_ImplGlfw_UpdateX11ConfigureEvents();
}
#endif
if (vd->WindowOwned)
{
#if !GLFW_HAS_MOUSE_PASSTHROUGH && GLFW_HAS_WINDOW_HOVERED && defined(_WIN32)
@ -1499,7 +1608,9 @@ static void ImGui_ImplGlfw_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
{
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
vd->IgnoreWindowPosEventFrame = ImGui::GetFrameCount();
glfwSetWindowPos(vd->Window, (int)pos.x, (int)pos.y);
vd->LastWindowPosRequest[0] = (int)pos.x;
vd->LastWindowPosRequest[1] = (int)pos.y;
glfwSetWindowPos(vd->Window, vd->LastWindowPosRequest[0], vd->LastWindowPosRequest[1]);
}
static ImVec2 ImGui_ImplGlfw_GetWindowSize(ImGuiViewport* viewport)
@ -1521,10 +1632,15 @@ static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
int x, y, width, height;
glfwGetWindowPos(vd->Window, &x, &y);
glfwGetWindowSize(vd->Window, &width, &height);
glfwSetWindowPos(vd->Window, x, y - height + size.y);
vd->IgnoreWindowPosEventFrame = ImGui::GetFrameCount();
vd->LastWindowPosRequest[0] = x;
vd->LastWindowPosRequest[1] = (int)(y - height + size.y);
glfwSetWindowPos(vd->Window, vd->LastWindowPosRequest[0], vd->LastWindowPosRequest[1]);
#endif
vd->IgnoreWindowSizeEventFrame = ImGui::GetFrameCount();
glfwSetWindowSize(vd->Window, (int)size.x, (int)size.y);
vd->LastWindowSizeRequest[0] = (int)size.x;
vd->LastWindowSizeRequest[1] = (int)size.y;
glfwSetWindowSize(vd->Window, vd->LastWindowSizeRequest[0], vd->LastWindowSizeRequest[1]);
}
static ImVec2 ImGui_ImplGlfw_GetWindowFramebufferScale(ImGuiViewport* viewport)

View File

@ -70,7 +70,15 @@ Other Changes:
Docking+Viewports Branch:
- Viewports: fixed platform position/size requests adjusted or rejected by a window manager
leaving Dear ImGui geometry out of sync with the native window. Active window dragging now
continues from the platform-accepted position instead of repeatedly requesting rejected
geometry. (#9356, #9398, #9442)
- Backends:
- GLFW: on X11, observe synthetic ConfigureNotify events when the native API is available
so completely rejected viewport geometry requests are synchronized without a timeout.
- GLFW: disabled multi-viewports on Wayland with legacy GLFW versions, where global window
positioning is unavailable.
- Metal: fixed resizing windows losing framebuffer scale. (#6828, #8856) [@laikoken]
- Examples:
- SDL3+Metal, GLFW+Metal: fix multi-viewport support.
@ -9671,4 +9679,3 @@ Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v
Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.00
- Initial release.

View File

@ -0,0 +1,94 @@
# GLFW/X11 multi-viewport geometry synchronization
## Problem
Dear ImGui's GLFW backend drives secondary viewport geometry through
`glfwSetWindowPos()` and `glfwSetWindowSize()`. Those calls are asynchronous on
X11: the window manager may accept, adjust, or reject the request after GLFW has
returned.
The portable GLFW API does not expose a request identifier or an acknowledgement
containing the geometry accepted by the window manager. Its position and size
callbacks are change notifications, not completion notifications.
This matters for a completely rejected X11 `ConfigureRequest`. ICCCM-compliant
window managers send a synthetic `ConfigureNotify` containing the unchanged
geometry, but GLFW only invokes its public position and size callbacks when that
geometry differs from GLFW's cached state. GLFW consumes the synthetic event, so
the application receives no callback. Dear ImGui can consequently retain the
requested geometry while the native window remains at its previous geometry.
Rendering, hit testing, mouse coordinates, and subsequent drag requests then use
inconsistent origins.
This is best described as a GLFW API limitation rather than an unambiguous GLFW
bug: an unchanged position is not necessarily expected to produce a "position
changed" callback. However, the current API is insufficient for robust
requested-versus-observed window geometry synchronization on asynchronous window
systems.
## Why the obvious alternatives are insufficient
- **Immediate readback after a setter:** the window manager is a separate X11
client. `glfwGetWindowPos()` may still return the old geometry before the
window manager processes the request, even when the request will be accepted.
- **Readback after a fixed timeout:** reduces the race but does not eliminate it.
It also introduces latency and frame-rate-, load-, and window-manager-dependent
behavior.
- **Removing GLFW's setter-echo suppression:** regresses delayed position/size
callback cases previously fixed for #2117 and #2756.
- **Observing GLFW's X11 connection:** GLFW owns and drains that event queue; the
backend has no public native-event hook through which to observe consumed
events.
## Current compatibility approach
The backend keeps the existing callback path and classifies a recent callback as
a harmless setter echo only when its integer geometry matches both the latest
issued setter and the geometry still expected by the viewport. Adjusted or stale
geometry requests an authoritative readback during the next Dear ImGui frame.
For the unchanged-rejection case, the X11 backend optionally opens a second X11
connection and selects `StructureNotifyMask` on secondary viewport windows. This
connection receives the synthetic `ConfigureNotify` that GLFW otherwise filters.
The backend uses it only as a signal to compare native and viewport geometry; the
normal GLFW getters remain the authoritative source.
Using a second X11 connection requires explicit cross-connection ordering during
window creation and destruction. It is deliberately optional: if the native X11
symbols or observer connection are unavailable, the backend falls back to the
ordinary GLFW callback behavior rather than failing initialization.
This avoids a timing heuristic, but it is compatibility machinery that would be
preferable to remove once supported GLFW versions expose an adequate event or
acknowledgement facility.
## Preferred GLFW-side capability
A long-term GLFW solution could provide any one of:
1. a configure-request completion notification, including unchanged rejection;
2. a native event hook that observes X11 events before GLFW filters them; or
3. documented delivery of synthetic `ConfigureNotify` through an additional
callback whose semantics do not imply that geometry changed.
With such a capability, Dear ImGui could retain its portable requested/observed
state handling while removing the separate X11 observer for newer GLFW versions.
## Required regression coverage
The synchronization contract should cover:
- synchronous and delayed exact setter echoes;
- adjusted/clamped position and size;
- complete rejection with unchanged native geometry;
- two outstanding requests whose responses arrive in separate frames;
- simultaneous position and size changes;
- active dragging at screen boundaries without snap-back or setter oscillation;
- collapse/uncollapse and delayed-size behavior from #2117/#2756; and
- native Xorg with Mutter, since XWayland is useful but not equivalent evidence
for this event path.
The Test Engine mock can deterministically validate Dear ImGui's core ownership
and drag-rebasing behavior. It cannot replace backend-level or native-X11 tests,
because `-viewport-mock` replaces the GLFW platform handlers and therefore does
not execute GLFW's callback filtering or the X11 observer.

View File

@ -4334,6 +4334,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
HoveredWindowUnderMovingWindow = NULL;
HoveredWindowBeforeClear = NULL;
MovingWindow = NULL;
MovingWindowClickOffset = ImVec2(-1, -1);
WheelingWindow = NULL;
WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1;
WheelingWindowReleaseTimer = 0.0f;
@ -5465,6 +5466,7 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
if (g.IO.ConfigNavCursorVisibleAuto)
g.NavCursorVisible = false;
g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos;
g.MovingWindowClickOffset = g.ActiveIdClickOffset;
g.ActiveIdNoClearOnFocusLoss = true;
SetActiveIdUsingAllKeyboardKeys();
@ -5550,7 +5552,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame()
const bool window_disappeared = (!moving_window->WasActive && !moving_window->Active);
if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappeared)
{
ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
ImVec2 pos = g.IO.MousePos - g.MovingWindowClickOffset;
if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
{
SetWindowPos(moving_window, pos, ImGuiCond_Always);
@ -17210,7 +17212,22 @@ static void ImGui::UpdateViewportsNewFrame()
{
// Viewport->WorkPos and WorkSize will be updated below
if (viewport->PlatformRequestMove)
viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
{
const ImVec2 requested_pos = viewport->Pos;
const ImVec2 platform_pos = g.PlatformIO.Platform_GetWindowPos(viewport);
// When a window manager adjusts an actively dragged viewport, rebase the mutable drag offset.
// Only rebase adjusted axes: accepted axes should retain any mouse movement received this frame.
// Keep ActiveIdClickOffset unchanged: docking uses it as the original click location.
ImGuiWindow* moving_root = g.MovingWindow ? g.MovingWindow->RootWindowDockTree : NULL;
if (moving_root && viewport->Window == moving_root && moving_root->ViewportOwned && IsMousePosValid(&g.IO.MousePos))
{
if (platform_pos.x != requested_pos.x)
g.MovingWindowClickOffset.x = g.IO.MousePos.x - platform_pos.x;
if (platform_pos.y != requested_pos.y)
g.MovingWindowClickOffset.y = g.IO.MousePos.y - platform_pos.y;
}
viewport->Pos = viewport->LastPlatformPos = platform_pos;
}
if (viewport->PlatformRequestResize)
viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
if (g.PlatformIO.Platform_GetWindowFramebufferScale != NULL)
@ -17763,15 +17780,26 @@ void ImGui::UpdatePlatformWindows()
viewport->PlatformWindowCreated = true;
}
// Consume requests received before this update. Callbacks raised synchronously by setters below must survive
// until the next NewFrame(), so clear the old requests before invoking platform functions instead of afterward.
const bool platform_request_move = viewport->PlatformRequestMove;
const bool platform_request_resize = viewport->PlatformRequestResize;
viewport->PlatformRequestMove = viewport->PlatformRequestResize = false;
// Apply Position and Size (from ImGui to Platform/Renderer backends)
if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove)
if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !platform_request_move)
{
g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !viewport->PlatformRequestResize)
viewport->LastPlatformPos = viewport->Pos;
}
if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !platform_request_resize)
{
g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
viewport->LastPlatformSize = viewport->Size;
}
if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) && g.PlatformIO.Renderer_SetWindowSize)
g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
viewport->LastPlatformPos = viewport->Pos;
viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
viewport->LastRendererSize = viewport->Size;
// Update title bar (if it changed)
if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window))
@ -17808,8 +17836,10 @@ void ImGui::UpdatePlatformWindows()
g.PlatformIO.Platform_ShowWindow(viewport);
}
// Clear request flags
viewport->ClearRequestFlags();
// Position/size requests raised synchronously above need to survive until the next NewFrame().
// Close requests keep their previous lifetime and are consumed once per platform update.
viewport->PlatformRequestClose = false;
}
}
@ -19259,6 +19289,7 @@ static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWind
IM_ASSERT(node->WantMouseMove == true);
StartMouseMovingWindow(window);
g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
g.MovingWindowClickOffset = g.ActiveIdClickOffset;
g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision.
node->WantMouseMove = false;
}

View File

@ -2492,6 +2492,7 @@ struct ImGuiContext
ImGuiWindow* HoveredWindowUnderMovingWindow; // Hovered window ignoring MovingWindow. Only set if MovingWindow is set.
ImGuiWindow* HoveredWindowBeforeClear; // Window the mouse is hovering. Filled even with _NoMouse. This is currently useful for multi-context compositors.
ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindowDockTree.
ImVec2 MovingWindowClickOffset; // Click offset used to position MovingWindow. May be rebased when a platform window manager adjusts our requested position.
ImGuiWindow* WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window.
ImVec2 WheelingWindowRefMousePos;
int WheelingWindowStartFrame; // This may be set one frame before WheelingWindow is != NULL

View File

@ -10908,6 +10908,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
g.MovingWindow = docked_window;
SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
g.MovingWindowClickOffset = g.ActiveIdClickOffset;
g.ActiveIdNoClearOnFocusLoss = true;
SetActiveIdUsingAllKeyboardKeys();
}