Skip to content

Commit

Permalink
WIP Scrolling for 3795
Browse files Browse the repository at this point in the history
  • Loading branch information
ocornut committed Oct 6, 2022
1 parent 4529abf commit a1fad4d
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 30 deletions.
108 changes: 78 additions & 30 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4269,13 +4269,74 @@ 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");
g.WheelingWindow = 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;
Expand Down Expand Up @@ -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)
Expand Down
28 changes: 28 additions & 0 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit a1fad4d

Please sign in to comment.