Skip to content

Commit c41f267

Browse files
committed
WIP Scrolling for 3795, v2.3
squashed
1 parent 8a44c31 commit c41f267

File tree

3 files changed

+108
-32
lines changed

3 files changed

+108
-32
lines changed

imgui.cpp

+83-32
Original file line numberDiff line numberDiff line change
@@ -4274,6 +4274,50 @@ static void LockWheelingWindow(ImGuiWindow* window)
42744274
IMGUI_DEBUG_LOG_IO("LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL");
42754275
g.WheelingWindow = window;
42764276
g.WheelingWindowRefMousePos = g.IO.MousePos;
4277+
if (window == NULL)
4278+
{
4279+
g.WheelingWindowStartFrame = -1;
4280+
g.WheelingAxisAvg = ImVec2(0.0f, 0.0f);
4281+
}
4282+
}
4283+
4284+
static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel)
4285+
{
4286+
// For each axis, find window in the hierarchy that may want to use scrolling
4287+
ImGuiContext& g = *GImGui;
4288+
ImGuiWindow* windows[2] = { NULL, NULL };
4289+
for (int axis = 0; axis < 2; axis++)
4290+
if (wheel[axis] != 0.0f)
4291+
for (ImGuiWindow* window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow; window = windows[axis] = window->ParentWindow)
4292+
{
4293+
// Bubble up into parent window if:
4294+
// - a child window doesn't allow any scrolling.
4295+
// - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag.
4296+
//// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in (FIXME-WIP)
4297+
const bool has_scrolling = (window->ScrollMax[axis] != 0.0f);
4298+
const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs);
4299+
//const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]);
4300+
if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits)
4301+
break; // select this window
4302+
}
4303+
if (windows[0] == NULL && windows[1] == NULL)
4304+
return NULL;
4305+
4306+
// If there's only one window or only one axis then there's no ambiguity
4307+
if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL)
4308+
return windows[1] ? windows[1] : windows[0];
4309+
4310+
// If candidate are different windows we need to decide which one to prioritize
4311+
// - First frame: only find a winner if one axis is zero.
4312+
// - Subsequent frames: only find a winner when one is more than the other.
4313+
if (g.WheelingWindowStartFrame == -1)
4314+
g.WheelingWindowStartFrame = g.FrameCount;
4315+
if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) || (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y))
4316+
{
4317+
g.WheelingWindowWheelRemainder = wheel;
4318+
return NULL;
4319+
}
4320+
return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1];
42774321
}
42784322

42794323
void ImGui::UpdateMouseWheel()
@@ -4297,8 +4341,6 @@ void ImGui::UpdateMouseWheel()
42974341
ImVec2 wheel;
42984342
wheel.x = (!hovered_id_using_mouse_wheel && !active_id_using_mouse_wheel_x) ? g.IO.MouseWheelH : 0.0f;
42994343
wheel.y = (!hovered_id_using_mouse_wheel && !active_id_using_mouse_wheel_y) ? g.IO.MouseWheel : 0;
4300-
if (wheel.x == 0.0f && wheel.y == 0.0f)
4301-
return;
43024344

43034345
//IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
43044346
ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
@@ -4336,39 +4378,41 @@ void ImGui::UpdateMouseWheel()
43364378
wheel.y = 0.0f;
43374379
}
43384380

4339-
// Vertical Mouse Wheel scrolling
4340-
// Bubble up into parent window if:
4341-
// - a child window doesn't allow any scrolling.
4342-
// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in.
4343-
// - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag.
4344-
if (wheel.y != 0.0f)
4345-
{
4346-
ImGuiWindow* window = mouse_window;
4347-
while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
4348-
window = window->ParentWindow;
4349-
if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
4350-
{
4351-
LockWheelingWindow(mouse_window);
4352-
float max_step = window->InnerRect.GetHeight() * 0.67f;
4353-
float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step));
4354-
SetScrollY(window, window->Scroll.y - wheel.y * scroll_step);
4355-
}
4356-
}
4381+
// Maintain a rough average of moving magnitude on both axises
4382+
// FIXME: should by based on wall clock time rather than frame-counter
4383+
g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30);
4384+
g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30);
43574385

4358-
// Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held
4359-
if (wheel.x != 0.0f)
4360-
{
4361-
ImGuiWindow* window = mouse_window;
4362-
while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
4363-
window = window->ParentWindow;
4386+
// In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now.
4387+
wheel += g.WheelingWindowWheelRemainder;
4388+
g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f);
4389+
if (wheel.x == 0.0f && wheel.y == 0.0f)
4390+
return;
4391+
4392+
// Mouse wheel scrolling: find target and apply
4393+
// - don't renew lock if axis doesn't apply on the window.
4394+
// - select a main axis when both axises are being moved.
4395+
if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel)))
43644396
if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
43654397
{
4366-
LockWheelingWindow(mouse_window);
4367-
float max_step = window->InnerRect.GetWidth() * 0.67f;
4368-
float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step));
4369-
SetScrollX(window, window->Scroll.x - wheel.x * scroll_step);
4398+
bool do_scroll[2] = { wheel.x != 0.0f && window->ScrollMax.x != 0.0f, wheel.y != 0.0f && window->ScrollMax.y != 0.0f };
4399+
if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y])
4400+
do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false;
4401+
if (do_scroll[ImGuiAxis_X])
4402+
{
4403+
LockWheelingWindow(window);
4404+
float max_step = window->InnerRect.GetWidth() * 0.67f;
4405+
float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step));
4406+
SetScrollX(window, window->Scroll.x - wheel.x * scroll_step);
4407+
}
4408+
if (do_scroll[ImGuiAxis_Y])
4409+
{
4410+
LockWheelingWindow(window);
4411+
float max_step = window->InnerRect.GetHeight() * 0.67f;
4412+
float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step));
4413+
SetScrollY(window, window->Scroll.y - wheel.y * scroll_step);
4414+
}
43704415
}
4371-
}
43724416
}
43734417

43744418
// 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)
@@ -8132,7 +8176,7 @@ static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e)
81328176
ImGuiContext& g = *GImGui;
81338177
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; }
81348178
if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("%s: MouseButton %d %s\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up"); return; }
8135-
if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.1f, %.1f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; }
8179+
if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.3f, %.3f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; }
81368180
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; }
81378181
if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("%s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; }
81388182
if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("%s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; }
@@ -12934,6 +12978,13 @@ void ImGui::ShowMetricsWindow(bool* p_open)
1293412978
Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
1293512979
Unindent();
1293612980

12981+
Text("MOUSE WHEELING");
12982+
Indent();
12983+
Text("WheelingWindow: '%s'", g.WheelingWindow ? g.WheelingWindow->Name : "NULL");
12984+
Text("WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer);
12985+
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>");
12986+
Unindent();
12987+
1293712988
TreePop();
1293812989
}
1293912990

imgui_demo.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,26 @@ void ImGui::ShowDemoWindow(bool* p_open)
250250
// Most functions would normally just crash if the context is missing.
251251
IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing dear imgui context. Refer to examples app!");
252252

253+
#if 1
254+
ImGui::Begin("#3795 - Window A");
255+
for (int n = 0; n < 10; n++)
256+
ImGui::Text("%*s some contents", n, "");
257+
ImGui::BeginChild("Child B", ImVec2(0.0f, 200.0f), true);
258+
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. ");
259+
ImGui::EndChild();
260+
for (int n = 0; n < 10; n++)
261+
ImGui::Text("%*s some contents", n, "");
262+
ImGui::End();
263+
264+
ImGui::Begin("#4559");
265+
ImGui::Text("Parent");
266+
ImGui::BeginChild("Child", ImVec2(0, 0), true);
267+
for (int n = 0; n < 10; n++)
268+
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, "");
269+
ImGui::EndChild();
270+
ImGui::End();
271+
#endif
272+
253273
// Examples Apps (accessible from the "Examples" menu)
254274
static bool show_app_main_menu_bar = false;
255275
static bool show_app_documents = false;

imgui_internal.h

+5
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a)
465465
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; }
466466
static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); }
467467
static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; }
468+
static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; }
468469
IM_MSVC_RUNTIME_CHECKS_RESTORE
469470

470471
// Helpers: Geometry
@@ -1631,7 +1632,10 @@ struct ImGuiContext
16311632
ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow.
16321633
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.
16331634
ImVec2 WheelingWindowRefMousePos;
1635+
int WheelingWindowStartFrame; // This may be set one frame before WheelingWindow is != NULL
16341636
float WheelingWindowReleaseTimer;
1637+
ImVec2 WheelingWindowWheelRemainder;
1638+
ImVec2 WheelingAxisAvg;
16351639

16361640
// Item/widgets state and tracking information
16371641
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,6 +1889,7 @@ struct ImGuiContext
18851889
HoveredWindowUnderMovingWindow = NULL;
18861890
MovingWindow = NULL;
18871891
WheelingWindow = NULL;
1892+
WheelingWindowStartFrame = -1;
18881893
WheelingWindowReleaseTimer = 0.0f;
18891894

18901895
DebugHookIdInfo = 0;

0 commit comments

Comments
 (0)