From bbdb64ebc09e00a4bb41733c3612f203a7fad7b6 Mon Sep 17 00:00:00 2001 From: yokljo Date: Wed, 15 Apr 2026 12:29:26 +1000 Subject: [PATCH 1/7] Allow viewport support on Wayland --- backends/imgui_impl_glfw.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 575b783494e6..04beab7d6293 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -703,10 +703,6 @@ 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 if (has_viewports) io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) #endif From deac40a8dda00f8d816bc6761f04ac0d1da48df4 Mon Sep 17 00:00:00 2001 From: yokljo Date: Wed, 15 Apr 2026 15:12:50 +1000 Subject: [PATCH 2/7] Remove Wayland-specific content scaling logic --- backends/imgui_impl_glfw.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 04beab7d6293..7d72ecf66e50 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -1064,11 +1064,6 @@ static void ImGui_ImplGlfw_UpdateMonitors() // - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) { -#if GLFW_HAS_WAYLAND - if (ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window)) - if (bd->IsWayland) - return 1.0f; -#endif #if GLFW_HAS_PER_MONITOR_DPI && !(defined(__APPLE__) || defined(__EMSCRIPTEN__) || defined(__ANDROID__)) float x_scale, y_scale; glfwGetWindowContentScale(window, &x_scale, &y_scale); @@ -1081,10 +1076,6 @@ float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor) { -#if GLFW_HAS_WAYLAND - if (ImGui_ImplGlfw_IsWayland()) // We can't access our bd->IsWayland cache for a monitor. - return 1.0f; -#endif #if GLFW_HAS_PER_MONITOR_DPI && !(defined(__APPLE__) || defined(__EMSCRIPTEN__) || defined(__ANDROID__)) float x_scale, y_scale; glfwGetMonitorContentScale(monitor, &x_scale, &y_scale); @@ -1103,11 +1094,6 @@ static void ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(GLFWwindow* window, glfwGetFramebufferSize(window, &display_w, &display_h); float fb_scale_x = (w > 0) ? (float)display_w / (float)w : 1.0f; float fb_scale_y = (h > 0) ? (float)display_h / (float)h : 1.0f; -#if GLFW_HAS_WAYLAND - ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window); - if (!bd->IsWayland) - fb_scale_x = fb_scale_y = 1.0f; -#endif if (out_size != nullptr) *out_size = ImVec2((float)w, (float)h); if (out_framebuffer_scale != nullptr) From 10fc3f45129f1999df8927add6acc0ef405e75fd Mon Sep 17 00:00:00 2001 From: yokljo Date: Wed, 22 Apr 2026 16:46:28 +1000 Subject: [PATCH 3/7] Fixed incorrect window position information arising in UpdateMouseMovingWindowNewFrame, which would break docking when the windowing system doesn't match the requested window position --- imgui.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b070d1de5bf6..6c06e8ab4a64 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5418,10 +5418,21 @@ void ImGui::UpdateMouseMovingWindowNewFrame() ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) { - SetWindowPos(moving_window, pos, ImGuiCond_Always); + // Use the actual window location returned by the window system if possible, + // since windows can snap to borders and things like that which result in a final + // window location that is not exactly the same as what was requested. + ImVec2 actual = pos; + if (moving_window->Viewport && moving_window->ViewportOwned + && g.PlatformIO.Platform_SetWindowPos && g.PlatformIO.Platform_GetWindowPos) + { + g.PlatformIO.Platform_SetWindowPos(moving_window->Viewport, pos); + actual = g.PlatformIO.Platform_GetWindowPos(moving_window->Viewport); + } + SetWindowPos(moving_window, actual, ImGuiCond_Always); if (moving_window->Viewport && moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. { - moving_window->Viewport->Pos = pos; + moving_window->Viewport->Pos = actual; + moving_window->Viewport->LastPlatformPos = actual; moving_window->Viewport->UpdateWorkRect(); } } From 405786f4c5fc6918d01d85e33d4c87f27cbeccaa Mon Sep 17 00:00:00 2001 From: yokljo Date: Thu, 23 Apr 2026 10:32:26 +1000 Subject: [PATCH 4/7] Fixed jitter problem while dragging docks around --- imgui.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6c06e8ab4a64..844a60d36e70 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5431,8 +5431,15 @@ void ImGui::UpdateMouseMovingWindowNewFrame() SetWindowPos(moving_window, actual, ImGuiCond_Always); if (moving_window->Viewport && moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. { - moving_window->Viewport->Pos = actual; - moving_window->Viewport->LastPlatformPos = actual; + // Use moving_window->Pos (which SetWindowPos truncated via + // ImTrunc) rather than raw `actual`. If viewport->Pos kept + // the untruncated value, a later sync site would overwrite it + // with window->Pos (truncated), and UpdatePlatformWindows + // would see the mismatch against LastPlatformPos and push the + // truncated position to the OS — which maps to a different + // OS-pixel than what we just set, causing oscillation. + moving_window->Viewport->Pos = moving_window->Pos; + moving_window->Viewport->LastPlatformPos = moving_window->Pos; moving_window->Viewport->UpdateWorkRect(); } } From 8a73dbe41c514a7dad15678e4ebf9e4fe6e03750 Mon Sep 17 00:00:00 2001 From: Joshua Worth Date: Fri, 24 Apr 2026 16:46:35 +1000 Subject: [PATCH 5/7] Fixed docking targets not appearing when tearing a tab away from a very specific part of the tab itself --- imgui.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 844a60d36e70..cdf1d503769d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -21980,7 +21980,11 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) g.LastItemData.ID = window->MoveId; window = window->RootWindowDockTree; IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); - bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit + // LuckyEngine: mirrors the custom tab_padding_y in TabItemCalcSize - without this, + // clicks in the lower half of a tall tab fall outside GetFrameHeight() and tear-away + // drags never turn into a drag-drop source (no docking overlay until re-grab). + const float tab_drag_height = ImMax(GetFrameHeight(), g.FontSize + 8.0f * 2.0f); + bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, tab_drag_height).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit ImGuiDragDropFlags drag_drop_flags = ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_PayloadAutoExpire | ImGuiDragDropFlags_PayloadNoCrossContext | ImGuiDragDropFlags_PayloadNoCrossProcess; if (is_drag_docking && BeginDragDropSource(drag_drop_flags)) { From 19ced6692d1502e8f2d65d85e560e634a73270f0 Mon Sep 17 00:00:00 2001 From: yokljo Date: Fri, 1 May 2026 15:17:36 +1000 Subject: [PATCH 6/7] Add a flag to disable splitting out docks into new windows --- imgui.cpp | 13 +++++++++++++ imgui.h | 1 + 2 files changed, 14 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index cdf1d503769d..c627d508eb87 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1637,6 +1637,7 @@ ImGuiIO::ImGuiIO() ConfigViewportsNoTaskBarIcon = false; ConfigViewportsNoDecoration = true; ConfigViewportsNoDefaultParent = true; + ConfigViewportsNoFloatingWindows = false; ConfigViewportsPlatformFocusSetsImGuiFocus = true; // Miscellaneous options @@ -17856,6 +17857,18 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) SetWindowViewport(window, main_viewport); return; } + + // When ConfigViewportsNoFloatingWindows is set, only popups/tooltips/menus + // may create their own viewport. All other windows stay in the main viewport. + if (g.IO.ConfigViewportsNoFloatingWindows) + { + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu)) == 0) + { + SetWindowViewport(window, main_viewport); + return; + } + } + window->ViewportOwned = false; // Appearing popups reset their viewport so they can inherit again diff --git a/imgui.h b/imgui.h index 6c1ac88afded..8fd3b92aa049 100644 --- a/imgui.h +++ b/imgui.h @@ -2521,6 +2521,7 @@ struct ImGuiIO bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it. bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size). bool ConfigViewportsNoDefaultParent; // = true // When false: set secondary viewports' ParentViewportId to main viewport ID by default. Expects the platform backend to setup a parent/child relationship between the OS windows based on this value. Some backend may ignore this. Set to true if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. + bool ConfigViewportsNoFloatingWindows;// = false // When set, only popups, tooltips and menus may create their own viewport. Undocked/floating windows stay inside the main viewport. Useful on platforms (e.g. Wayland) where secondary OS windows for docked panels are problematic. bool ConfigViewportsPlatformFocusSetsImGuiFocus;//= true // When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change). // DPI/Scaling options From 869cddf52ee9430d78852d66bc5eb45c4664dbe3 Mon Sep 17 00:00:00 2001 From: yokljo Date: Thu, 7 May 2026 16:26:17 +1000 Subject: [PATCH 7/7] Fixed bugs in imgui_freetype preventing SVG emojis from rendering correctly --- misc/freetype/imgui_freetype.cpp | 65 +++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 26a087017327..d864c1ad3fc6 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -621,6 +621,9 @@ struct LunasvgPortState FT_Error err = FT_Err_Ok; lunasvg::Matrix matrix; std::unique_ptr svg = nullptr; +#if LUNASVG_VERSION_MAJOR >= 3 + lunasvg::Element glyphElement; +#endif }; static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state) @@ -642,11 +645,20 @@ static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state) if (state->err != FT_Err_Ok) return state->err; - // rows is height, pitch (or stride) equals to width * sizeof(int32) - lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch); #if LUNASVG_VERSION_MAJOR >= 3 - state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated + if (state->glyphElement.isElement()) + { + lunasvg::Bitmap rendered = state->glyphElement.renderToBitmap(slot->bitmap.width, slot->bitmap.rows); + if (rendered.data()) + memcpy(slot->bitmap.buffer, rendered.data(), (size_t)slot->bitmap.rows * slot->bitmap.pitch); + } + else + { + lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch); + state->svg->render(bitmap, state->matrix); + } #else + lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch); state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated #endif @@ -673,10 +685,26 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ } #if LUNASVG_VERSION_MAJOR >= 3 - lunasvg::Box box = state->svg->boundingBox(); + // Multi-glyph SVG documents (e.g. NotoColorEmoji) store all glyphs in one SVG + // with elements identified by "glyph". Find the specific glyph element. + char glyph_id_str[64]; + ImFormatString(glyph_id_str, sizeof(glyph_id_str), "glyph%u", slot->glyph_index); + state->glyphElement = state->svg->getElementById(glyph_id_str); + + lunasvg::Box box; + if (state->glyphElement.isElement()) + box = state->glyphElement.getBoundingBox(); + else + box = state->svg->boundingBox(); #else lunasvg::Box box = state->svg->box(); #endif + if (box.w == 0 || box.h == 0) + { + state->err = FT_Err_Invalid_SVG_Document; + return state->err; + } + double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h); double xx = (double)document->transform.xx / (1 << 16); double xy = -(double)document->transform.xy / (1 << 16); @@ -686,7 +714,34 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem; #if LUNASVG_VERSION_MAJOR >= 3 - // Scale, transform and pre-translate the matrix for the rendering step + if (state->glyphElement.isElement()) + { + // renderToBitmap handles positioning internally, so we just need pixel dimensions + double bitmapW = box.w * scale; + double bitmapH = box.h * scale; + + slot->bitmap.width = (unsigned int)(ImCeil((float)bitmapW)); + slot->bitmap.rows = (unsigned int)(ImCeil((float)bitmapH)); + slot->bitmap.pitch = slot->bitmap.width * 4; + slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + slot->bitmap_left = 0; + slot->bitmap_top = (FT_Int)slot->bitmap.rows; + + slot->metrics.width = FT_Pos(IM_ROUND(bitmapW * 64.0)); + slot->metrics.height = FT_Pos(IM_ROUND(bitmapH * 64.0)); + slot->metrics.horiBearingX = 0; + slot->metrics.horiBearingY = FT_Pos(IM_ROUND(bitmapH * 64.0)); + slot->metrics.vertBearingX = slot->metrics.horiBearingX / 2 - slot->metrics.horiAdvance / 2; + slot->metrics.vertBearingY = (slot->metrics.vertAdvance - slot->metrics.height) / 2; + + if (slot->metrics.vertAdvance == 0) + slot->metrics.vertAdvance = FT_Pos(bitmapH * 1.2 * 64.0); + + state->err = FT_Err_Ok; + return state->err; + } + + // Fallback: single-glyph SVG documents — use standard matrix-based rendering state->matrix = lunasvg::Matrix::translated(-box.x, -box.y); state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0)); state->matrix.scale(scale, scale);