diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 575b783494e6..7d72ecf66e50 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 @@ -1068,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); @@ -1085,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); @@ -1107,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) diff --git a/imgui.cpp b/imgui.cpp index b070d1de5bf6..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 @@ -5418,10 +5419,28 @@ 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; + // 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(); } } @@ -17838,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 @@ -21962,7 +21993,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)) { 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 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);