diff --git a/TODO.txt b/TODO.txt index 28a1db8a..3cd9af46 100644 --- a/TODO.txt +++ b/TODO.txt @@ -259,7 +259,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - focus: unable to use SetKeyboardFocusHere() on clipped widgets. (#787) - viewport: popup/tooltip glitches when appearing near the monitor limits (e.g. opening a menu). - - viewport: clamp windows position within monitors (especially important on monitor configuration change) -> clamp to closest rectangle. - viewport: platform: introduce getfocus/setfocus api, so e.g. focus flags can be honored, imgui-side ctrl-tab can focus os window, OS alt-tab can focus imgui window etc. - viewport: store per-viewport/monitor DPI in .ini file so an application reload or main window changing DPI on reload can be properly patched for. - viewport: IME positioning are wrong. diff --git a/examples/imgui_impl_win32.cpp b/examples/imgui_impl_win32.cpp index 0da56b63..13257360 100644 --- a/examples/imgui_impl_win32.cpp +++ b/examples/imgui_impl_win32.cpp @@ -578,7 +578,11 @@ static BOOL CALLBACK ImGui_ImplWin32_UpdateMonitors_EnumFunc(HMONITOR monitor, H imgui_monitor.WorkMin = ImVec2((float)info.rcWork.left, (float)info.rcWork.top); imgui_monitor.WorkMax = ImVec2((float)info.rcWork.right, (float)info.rcWork.bottom); imgui_monitor.DpiScale = ImGui_ImplWin32_GetDpiScaleForMonitor(monitor); - ImGui::GetPlatformIO().Monitors.push_back(imgui_monitor); + ImGuiPlatformIO& io = ImGui::GetPlatformIO(); + if (info.dwFlags & MONITORINFOF_PRIMARY) + io.Monitors.push_front(imgui_monitor); + else + io.Monitors.push_back(imgui_monitor); return TRUE; } diff --git a/imgui.cpp b/imgui.cpp index e3aa2608..95c2d2bc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -832,7 +832,7 @@ ImGuiStyle::ImGuiStyle() GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. - DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + DisplayWindowPadding = ImVec2(20,20); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. @@ -3817,6 +3817,8 @@ void ImGui::NewFrame() // Disable feature, our back-ends do not support it g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable; } + + // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++) { ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[monitor_n]; @@ -6122,13 +6124,37 @@ static int FindPlatformMonitorForPos(ImVec2 platform_pos) ImGuiContext& g = *GImGui; for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++) { - const ImGuiPlatformMonitor* monitor = &g.PlatformIO.Monitors[monitor_n]; - if (ImRect(monitor->FullMin, monitor->FullMax).Contains(platform_pos)) + const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n]; + if (ImRect(monitor.FullMin, monitor.FullMax).Contains(platform_pos)) return monitor_n; } return -1; } +// Search for the monitor with the largest intersection area with the given rectangle +// We generally try to avoid searching loops but the monitor count should be very small here +static int FindPlatformMonitorForRect(const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + float surface_threshold = rect.GetWidth() * rect.GetHeight() * 0.5f; + int best_monitor_n = -1; + float best_monitor_surface = 0.001f; + for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold; monitor_n++) + { + const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n]; + if (ImRect(monitor.FullMin, monitor.FullMax).Contains(rect)) + return monitor_n; + ImRect overlapping_rect = rect; + overlapping_rect.ClipWithFull(ImRect(monitor.FullMin, monitor.FullMax)); + float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight(); + if (overlapping_surface < best_monitor_surface) + continue; + best_monitor_surface = overlapping_surface; + best_monitor_n = monitor_n; + } + return best_monitor_n; +} + // FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten. static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) { @@ -6367,6 +6393,11 @@ static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_au window->Size = window->SizeFull; } +static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& rect, const ImVec2& padding) +{ + window->PosFloat = ImMin(rect.Max - padding, ImMax(window->PosFloat + window->Size, rect.Min + padding) - window->Size); +} + // Push a new ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. @@ -6680,17 +6711,29 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->Viewport->Flags |= ImGuiViewportFlags_NoRendererClear; } - // Clamp position so window stays visible within its viewport + // Clamp position so window stays visible within its viewport or monitor // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. ImRect viewport_rect = window->Viewport->GetRect(); - if (!window->ViewportOwned && !window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) - if (viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f) + if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + { + ImVec2 clamp_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f) + ClampWindowRect(window, viewport_rect, clamp_padding); + else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0) { - ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); - window->PosFloat = ImMax(window->PosFloat + window->Size, viewport_rect.Min + padding) - window->Size; - window->PosFloat = ImMin(window->PosFloat, viewport_rect.Max - padding); + int monitor_n = FindPlatformMonitorForRect(viewport_rect); + if (monitor_n == -1) + { + // Fallback for "lost" window (e.g. a monitor disconnected): we move the window back over the main viewport + window->PosFloat = g.Viewports[0]->Pos + style.DisplayWindowPadding; + window->ViewportTryMerge = true; + } + else + { + ClampWindowRect(window, ImRect(g.PlatformIO.Monitors[monitor_n].WorkMin, g.PlatformIO.Monitors[monitor_n].WorkMax), clamp_padding); + } } - + } window->Pos = ImFloor(window->PosFloat); // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) @@ -14208,6 +14251,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) else ImGui::BulletText("NavRectRel[0]: "); ImGui::BulletText("Viewport: %d, ViewportId: 0x%08X, ViewportPlatformPos: (%.1f,%.1f)", window->Viewport ? window->Viewport->Idx : -1, window->ViewportId, window->ViewportPos.x, window->ViewportPos.y); + ImGui::BulletText("Monitor: %d", FindPlatformMonitorForRect(window->Rect())); if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) diff --git a/imgui.h b/imgui.h index f7e49048..95815a99 100644 --- a/imgui.h +++ b/imgui.h @@ -1004,7 +1004,7 @@ struct ImGuiStyle float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. - ImVec2 DisplayWindowPadding; // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU.