Inputs, Scrolling: better selection of scrolling window when hovering nested windows and backend/OS is emitting dual-axis wheeling inputs. (#3795, #4559)

This commit is contained in:
ocornut
2022-11-30 17:46:57 +01:00
parent a5f3596e6c
commit 87caf27ac4
4 changed files with 95 additions and 34 deletions

118
imgui.cpp
View File

@ -4367,13 +4367,58 @@ static void LockWheelingWindow(ImGuiWindow* window, float wheel_amount)
IMGUI_DEBUG_LOG_IO("LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL");
g.WheelingWindow = window;
g.WheelingWindowRefMousePos = g.IO.MousePos;
if (window == NULL)
{
g.WheelingWindowStartFrame = -1;
g.WheelingAxisAvg = ImVec2(0.0f, 0.0f);
}
}
static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel)
{
// For each axis, find window in the hierarchy that may want to use scrolling
ImGuiContext& g = *GImGui;
ImGuiWindow* windows[2] = { NULL, NULL };
for (int axis = 0; axis < 2; axis++)
if (wheel[axis] != 0.0f)
for (ImGuiWindow* window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow; window = windows[axis] = window->ParentWindow)
{
// Bubble up into parent window if:
// - a child window doesn't allow any scrolling.
// - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag.
//// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in (FIXME-WIP)
const bool has_scrolling = (window->ScrollMax[axis] != 0.0f);
const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs);
//const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]);
if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits)
break; // select this window
}
if (windows[0] == NULL && windows[1] == NULL)
return NULL;
// If there's only one window or only one axis then there's no ambiguity
if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL)
return windows[1] ? windows[1] : windows[0];
// If candidate are different windows we need to decide which one to prioritize
// - First frame: only find a winner if one axis is zero.
// - Subsequent frames: only find a winner when one is more than the other.
if (g.WheelingWindowStartFrame == -1)
g.WheelingWindowStartFrame = g.FrameCount;
if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) || (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y))
{
g.WheelingWindowWheelRemainder = wheel;
return NULL;
}
return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1];
}
void ImGui::UpdateMouseWheel()
{
ImGuiContext& g = *GImGui;
// Reset the locked window if we move the mouse or after the timer elapses
// Reset the locked window if we move the mouse or after the timer elapses.
// FIXME: Ideally we could refactor to have one timer for "changing window w/ same axis" and a shorter timer for "changing window or axis w/ other axis" (#3795)
if (g.WheelingWindow != NULL)
{
g.WheelingWindowReleaseTimer -= g.IO.DeltaTime;
@ -4386,8 +4431,6 @@ void ImGui::UpdateMouseWheel()
ImVec2 wheel;
wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f;
wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? g.IO.MouseWheel : 0.0f;
if (wheel.x == 0.0f && wheel.y == 0.0f)
return;
//IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
@ -4425,39 +4468,41 @@ 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, wheel.y);
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);
}
}
// Maintain a rough average of moving magnitude on both axises
// FIXME: should by based on wall clock time rather than frame-counter
g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30);
g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30);
// 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;
// In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now.
wheel += g.WheelingWindowWheelRemainder;
g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f);
if (wheel.x == 0.0f && wheel.y == 0.0f)
return;
// Mouse wheel scrolling: find target and apply
// - don't renew lock if axis doesn't apply on the window.
// - select a main axis when both axises are being moved.
if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel)))
if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
{
LockWheelingWindow(mouse_window, wheel.x);
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);
bool do_scroll[2] = { wheel.x != 0.0f && window->ScrollMax.x != 0.0f, wheel.y != 0.0f && window->ScrollMax.y != 0.0f };
if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y])
do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false;
if (do_scroll[ImGuiAxis_X])
{
LockWheelingWindow(window, wheel.x);
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 (do_scroll[ImGuiAxis_Y])
{
LockWheelingWindow(window, wheel.y);
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)
@ -8427,7 +8472,7 @@ static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e)
ImGuiContext& g = *GImGui;
if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("%s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("%s: MousePos (%.1f, %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; }
if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("%s: MouseButton %d %s\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up"); return; }
if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.1f, %.1f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; }
if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.3f, %.3f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; }
if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("%s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; }
if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("%s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; }
if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("%s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; }
@ -13409,6 +13454,13 @@ void ImGui::ShowMetricsWindow(bool* p_open)
Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
Unindent();
Text("MOUSE WHEELING");
Indent();
Text("WheelingWindow: '%s'", g.WheelingWindow ? g.WheelingWindow->Name : "NULL");
Text("WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer);
Text("WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y, (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? "X" : (g.WheelingAxisAvg.x < g.WheelingAxisAvg.y) ? "Y" : "<none>");
Unindent();
TreePop();
}