Skip to content

Commit 473a01a

Browse files
committed
Scrolling: Avoid SetScroll, SetScrollFromPos functions from snapping on the edge of scroll limits. (#3379) + Demo: Rename "Layout" to "Layout & Scrolling".
1 parent a24578e commit 473a01a

File tree

4 files changed

+49
-33
lines changed

4 files changed

+49
-33
lines changed

docs/CHANGELOG.txt

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ Other Changes:
4949
clipping, more than 16 KB characters are visible in the same low-level ImDrawList::RenderText
5050
call. ImGui-level functions such as TextUnformatted() are not affected. This is quite rare
5151
but it will be addressed later). (#3349)
52+
- Scrolling: Avoid SetScroll, SetScrollFromPos functions from snapping on the edge of scroll
53+
limits when close-enough by (WindowPadding - ItemPadding), which was a tweak with too many
54+
side-effects. The behavior is still present in SetScrollHere functions as they are more explicitly
55+
aiming at making widgets visible. May later be moved to a flag.
5256
- InvisibleButton: Made public a small selection of ImGuiButtonFlags (previously in imgui_internal.h)
5357
and allowed to pass them to InvisibleButton(): ImGuiButtonFlags_MouseButtonLeft/Right/Middle.
5458
This is a small but rather important change because lots of multi-button behaviors could previously

imgui.cpp

+40-26
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock
805805
static void SetCurrentWindow(ImGuiWindow* window);
806806
static void FindHoveredWindow();
807807
static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags);
808-
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges);
808+
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
809809

810810
static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
811811
static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
@@ -5819,7 +5819,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
58195819
window->ScrollMax.y = ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight());
58205820

58215821
// Apply scrolling
5822-
window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window, true);
5822+
window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
58235823
window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
58245824

58255825
// DRAWING
@@ -7325,30 +7325,20 @@ void ImGui::EndGroup()
73257325
// [SECTION] SCROLLING
73267326
//-----------------------------------------------------------------------------
73277327

7328-
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges)
7328+
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)
73297329
{
7330-
ImGuiContext& g = *GImGui;
73317330
ImVec2 scroll = window->Scroll;
73327331
if (window->ScrollTarget.x < FLT_MAX)
73337332
{
73347333
float cr_x = window->ScrollTargetCenterRatio.x;
73357334
float target_x = window->ScrollTarget.x;
7336-
if (snap_on_edges && cr_x <= 0.0f && target_x <= window->WindowPadding.x)
7337-
target_x = 0.0f;
7338-
else if (snap_on_edges && cr_x >= 1.0f && target_x >= window->ContentSize.x + window->WindowPadding.x + g.Style.ItemSpacing.x)
7339-
target_x = window->ContentSize.x + window->WindowPadding.x * 2.0f;
73407335
scroll.x = target_x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x);
73417336
}
73427337
if (window->ScrollTarget.y < FLT_MAX)
73437338
{
7344-
// 'snap_on_edges' allows for a discontinuity at the edge of scrolling limits to take account of WindowPadding so that scrolling to make the last item visible scroll far enough to see the padding.
73457339
float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
73467340
float cr_y = window->ScrollTargetCenterRatio.y;
73477341
float target_y = window->ScrollTarget.y;
7348-
if (snap_on_edges && cr_y <= 0.0f && target_y <= window->WindowPadding.y)
7349-
target_y = 0.0f;
7350-
if (snap_on_edges && cr_y >= 1.0f && target_y >= window->ContentSize.y + window->WindowPadding.y + g.Style.ItemSpacing.y)
7351-
target_y = window->ContentSize.y + window->WindowPadding.y * 2.0f;
73527342
scroll.y = target_y - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y - decoration_up_height);
73537343
}
73547344
scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f));
@@ -7380,7 +7370,7 @@ ImVec2 ImGui::ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& item_
73807370
else if (item_rect.Max.y >= window_rect.Max.y)
73817371
SetScrollFromPosY(window, item_rect.Max.y - window->Pos.y + g.Style.ItemSpacing.y, 1.0f);
73827372

7383-
ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window, false);
7373+
ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
73847374
delta_scroll = next_scroll - window->Scroll;
73857375
}
73867376

@@ -7441,21 +7431,19 @@ void ImGui::SetScrollY(ImGuiWindow* window, float new_scroll_y)
74417431
window->ScrollTargetCenterRatio.y = 0.0f;
74427432
}
74437433

7444-
7434+
// Note that a local position will vary depending on initial scroll value
7435+
// We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
74457436
void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio)
74467437
{
7447-
// We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
74487438
IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);
74497439
window->ScrollTarget.x = IM_FLOOR(local_x + window->Scroll.x);
74507440
window->ScrollTargetCenterRatio.x = center_x_ratio;
74517441
}
74527442

74537443
void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio)
74547444
{
7455-
// We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
74567445
IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
7457-
const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
7458-
local_y -= decoration_up_height;
7446+
local_y -= window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect
74597447
window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y);
74607448
window->ScrollTargetCenterRatio.y = center_y_ratio;
74617449
}
@@ -7472,25 +7460,51 @@ void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio)
74727460
SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio);
74737461
}
74747462

7463+
// Tweak: snap on edges when aiming at an item very close to the edge,
7464+
// So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling.
7465+
// When we refactor the scrolling API this may be configurable with a flag?
7466+
// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default.
7467+
static float CalcScrollSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio)
7468+
{
7469+
if (target <= snap_min + snap_threshold)
7470+
return ImLerp(snap_min, target, center_ratio);
7471+
if (target >= snap_max - snap_threshold)
7472+
return ImLerp(target, snap_max, center_ratio);
7473+
return target;
7474+
}
7475+
74757476
// center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item.
74767477
void ImGui::SetScrollHereX(float center_x_ratio)
74777478
{
74787479
ImGuiContext& g = *GImGui;
74797480
ImGuiWindow* window = g.CurrentWindow;
7480-
float target_x = window->DC.LastItemRect.Min.x - window->Pos.x; // Left of last item, in window space
7481-
float last_item_width = window->DC.LastItemRect.GetWidth();
7482-
target_x += (last_item_width * center_x_ratio) + (g.Style.ItemSpacing.x * (center_x_ratio - 0.5f) * 2.0f); // Precisely aim before, in the middle or after the last item.
7483-
SetScrollFromPosX(target_x, center_x_ratio);
7481+
float spacing_x = g.Style.ItemSpacing.x;
7482+
float target_x = ImLerp(window->DC.LastItemRect.Min.x - spacing_x, window->DC.LastItemRect.Max.x + spacing_x, center_x_ratio);
7483+
7484+
// Tweak: snap on edges when aiming at an item very close to the edge
7485+
const float snap_x_threshold = ImMax(0.0f, window->WindowPadding.x - spacing_x);
7486+
const float snap_x_min = window->DC.CursorStartPos.x - window->WindowPadding.x;
7487+
const float snap_x_max = window->DC.CursorStartPos.x + window->ContentSize.x + window->WindowPadding.x;
7488+
target_x = CalcScrollSnap(target_x, snap_x_min, snap_x_max, snap_x_threshold, center_x_ratio);
7489+
7490+
SetScrollFromPosX(window, target_x - window->Pos.x, center_x_ratio);
74847491
}
74857492

74867493
// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
74877494
void ImGui::SetScrollHereY(float center_y_ratio)
74887495
{
74897496
ImGuiContext& g = *GImGui;
74907497
ImGuiWindow* window = g.CurrentWindow;
7491-
float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space
7492-
target_y += (window->DC.PrevLineSize.y * center_y_ratio) + (g.Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line.
7493-
SetScrollFromPosY(target_y, center_y_ratio);
7498+
float spacing_y = g.Style.ItemSpacing.y;
7499+
float target_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y, window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio);
7500+
7501+
// Tweak: snap on edges when aiming at an item very close to the edge
7502+
const float snap_y_threshold = ImMax(0.0f, window->WindowPadding.y - spacing_y);
7503+
const float snap_y_min = window->DC.CursorStartPos.y - window->WindowPadding.y;
7504+
const float snap_y_max = window->DC.CursorStartPos.y + window->ContentSize.y + window->WindowPadding.y;
7505+
target_y = CalcScrollSnap(target_y, snap_y_min, snap_y_max, snap_y_threshold, center_y_ratio);
7506+
7507+
SetScrollFromPosY(window, target_y - window->Pos.y, center_y_ratio);
74947508
}
74957509

74967510
//-----------------------------------------------------------------------------

imgui.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,8 @@ namespace ImGui
333333
// Windows Scrolling
334334
IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()]
335335
IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()]
336-
IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X
337-
IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y
336+
IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.x - WindowSize.x
337+
IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.y - WindowSize.y
338338
IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()]
339339
IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()]
340340
IMGUI_API void SetScrollHereX(float center_x_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_x_ratio=0.0: left, 0.5: center, 1.0: right. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead.

imgui_demo.cpp

+3-5
Original file line numberDiff line numberDiff line change
@@ -2490,11 +2490,9 @@ static void ShowDemoWindowLayout()
24902490
ImGui::Spacing();
24912491
HelpMarker(
24922492
"Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\n\n"
2493-
"Using the \"Scroll To Pos\" button above will make the discontinuity at edges visible: "
2494-
"scrolling to the top/bottom/left/right-most item will add an additional WindowPadding to reflect "
2495-
"on reaching the edge of the list.\n\nBecause the clipping rectangle of most window hides half "
2496-
"worth of WindowPadding on the left/right, using SetScrollFromPosX(+1) will usually result in "
2497-
"clipped text whereas the equivalent SetScrollFromPosY(+1) wouldn't.");
2493+
"Because the clipping rectangle of most window hides half worth of WindowPadding on the "
2494+
"left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the "
2495+
"equivalent SetScrollFromPosY(+1) wouldn't.");
24982496
ImGui::PushID("##HorizontalScrolling");
24992497
for (int i = 0; i < 5; i++)
25002498
{

0 commit comments

Comments
 (0)