diff --git a/imgui.cpp b/imgui.cpp index 1c6568125cd44..d4aff11319e5a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4269,6 +4269,8 @@ static void LockWheelingWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; g.WheelingWindowReleaseTimer = window ? WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER : 0.0f; + if (window == NULL) + g.WheelingWindowStartFrame = -1; if (g.WheelingWindow == window) return; IMGUI_DEBUG_LOG_IO("LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL"); @@ -4276,6 +4278,65 @@ static void LockWheelingWindow(ImGuiWindow* window) g.WheelingWindowRefMousePos = g.IO.MousePos; } +static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel) +{ + ImGuiContext& g = *GImGui; + if (g.WheelingWindow != NULL) + return g.WheelingWindow; + + // For each axis, find window in the hierarchy that may want to use scrolling + ImGuiWindow* candidates[2] = { NULL, NULL }; + for (int axis = 0; axis < 2; axis++) + if (wheel[axis] != 0.0f) + { + // Bubble up into parent window if: + // - a child window doesn't allow any scrolling. + // - a child window doesn't need scrolling because it is already at the edge for the direction we are going in. + // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag. + ImGuiWindow* window = g.HoveredWindow; + while (window->Flags & ImGuiWindowFlags_ChildWindow) + { + const bool has_scrolling = (window->ScrollMax[axis] != 0.0f); + //const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]); + const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs); + if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits) + break; // select this window + window = window->ParentWindow; // move up + } + candidates[axis] = window; + } + if (candidates[ImGuiAxis_X] == NULL && candidates[ImGuiAxis_Y] == NULL) + return NULL; + + // If there's only one window or only one axis then there's no ambiguity + if (candidates[ImGuiAxis_X] == candidates[ImGuiAxis_Y] || candidates[ImGuiAxis_X] == NULL || candidates[ImGuiAxis_Y] == NULL) + return candidates[ImGuiAxis_Y] ? candidates[ImGuiAxis_Y] : candidates[ImGuiAxis_X]; + + // If candidate are different windows we need to decide which one to prioritize + IM_ASSERT(candidates[ImGuiAxis_X] != candidates[ImGuiAxis_Y]); + if (g.WheelingWindowStartFrame == -1) + { + g.WheelingWindowStartFrame = g.FrameCount; + g.WheelingWindowPerAxisData[ImGuiAxis_X] = g.WheelingWindowPerAxisData[ImGuiAxis_Y] = 0.0f; + } + + g.WheelingWindowPerAxisData[ImGuiAxis_X] += ImAbs(wheel.x); + g.WheelingWindowPerAxisData[ImGuiAxis_Y] += ImAbs(wheel.y); + + // First frame: only find a winner if one axis is zero. + if (g.WheelingWindowStartFrame == g.FrameCount) + if (wheel.x != 0.0f && wheel.y != 0.0f) + return NULL; + + // Subsequent frames: only find a winner when one is more than the other. + if (g.WheelingWindowPerAxisData[ImGuiAxis_X] > g.WheelingWindowPerAxisData[ImGuiAxis_Y]) + return candidates[ImGuiAxis_X]; + if (g.WheelingWindowPerAxisData[ImGuiAxis_Y] > g.WheelingWindowPerAxisData[ImGuiAxis_X]) + return candidates[ImGuiAxis_Y]; + + return NULL; +} + void ImGui::UpdateMouseWheel() { ImGuiContext& g = *GImGui; @@ -4336,39 +4397,26 @@ void ImGui::UpdateMouseWheel() wheel.y = 0.0f; } - // Vertical Mouse Wheel scrolling - // Bubble up into parent window if: - // - a child window doesn't allow any scrolling. - // - a child window doesn't need scrolling because it is already at the edge for the direction we are going in. - // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag. - if (wheel.y != 0.0f) - { - ImGuiWindow* window = mouse_window; - while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) - window = window->ParentWindow; - if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) - { - LockWheelingWindow(mouse_window); - float max_step = window->InnerRect.GetHeight() * 0.67f; - float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); - SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); - } - } - - // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held - if (wheel.x != 0.0f) - { - ImGuiWindow* window = mouse_window; - while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) - window = window->ParentWindow; + // Mouse wheel scrolling: find target and apply + // - Don't renew lock if axis doesn't apply on the window. + if (ImGuiWindow* window = FindBestWheelingWindow(wheel)) if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { - LockWheelingWindow(mouse_window); - float max_step = window->InnerRect.GetWidth() * 0.67f; - float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); - SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); + if (wheel.x != 0.0f && window->ScrollMax.x != 0.0f) + { + LockWheelingWindow(window); + float max_step = window->InnerRect.GetWidth() * 0.67f; + float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); + SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); + } + if (wheel.y != 0.0f && window->ScrollMax.y != 0.0f) + { + LockWheelingWindow(window); + float max_step = window->InnerRect.GetHeight() * 0.67f; + float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); + SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); + } } - } } // The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 349e7020da0c5..7024cde5a635a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -241,6 +241,8 @@ void* GImGuiDemoMarkerCallbackUserData = NULL; // - ShowDemoWindowInputs() //----------------------------------------------------------------------------- +#include "imgui_internal.h" + // Demonstrate most Dear ImGui features (this is big function!) // You may execute this function to experiment with the UI and understand what it does. // You may then search for keywords in the code when you are interested by a specific feature. @@ -250,6 +252,32 @@ void ImGui::ShowDemoWindow(bool* p_open) // Most functions would normally just crash if the context is missing. IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing dear imgui context. Refer to examples app!"); +#if 1 + ImGui::Begin("#3795 - Window A"); + for (int n = 0; n < 10; n++) + ImGui::Text("%*s some contents", n, ""); + ImGui::BeginChild("Child B", ImVec2(0.0f, 200.0f), true); + ImGui::Text("this is a long line of text. this is a long line of text. this is a long line of text. this is a long line of text. "); + ImGui::EndChild(); + for (int n = 0; n < 10; n++) + ImGui::Text("%*s some contents", n, ""); + ImGui::End(); + + ImGui::Begin("#4559"); + ImGui::Text("Parent"); + ImGui::BeginChild("Child", ImVec2(0, 0), true); + for (int n = 0; n < 10; n++) + ImGui::Text("%*sthis is a long line of text. this is a long line of text. this is a long line of text. this is a long line of text. ", n * 2, ""); + ImGui::EndChild(); + ImGui::End(); + + + static float avg_wheel_y = 0.0f; + avg_wheel_y = ImExponentialMovingAverage(avg_wheel_y, ImGui::GetIO().MouseWheel, 30); + ImGui::Text("Avg Wheel %.2f", avg_wheel_y); + +#endif + // Examples Apps (accessible from the "Examples" menu) static bool show_app_main_menu_bar = false; static bool show_app_documents = false; diff --git a/imgui_internal.h b/imgui_internal.h index 1baa556a946b6..7392413c2dbfe 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -465,6 +465,7 @@ static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } +static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry @@ -1631,7 +1632,9 @@ struct ImGuiContext ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow. 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; float WheelingWindowReleaseTimer; + float WheelingWindowPerAxisData[2]; // Item/widgets state and tracking information ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] @@ -1885,7 +1888,9 @@ struct ImGuiContext HoveredWindowUnderMovingWindow = NULL; MovingWindow = NULL; WheelingWindow = NULL; + WheelingWindowStartFrame = -1; WheelingWindowReleaseTimer = 0.0f; + WheelingWindowPerAxisData[0] = WheelingWindowPerAxisData[1] = 0.0f; DebugHookIdInfo = 0; HoveredId = HoveredIdPreviousFrame = 0;