Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 198 additions & 3 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ CODE
- Click [X]: Close a window, available when 'bool* p_open' is passed to ImGui::Begin().
- Click ^, Double-Click title: Collapse window.
- Drag on corner/border: Resize window (double-click to auto fit window to its contents).
- Drag on any empty space: Move window (unless io.ConfigWindowsMoveFromTitleBarOnly = true).
- Drag on any empty space: Move window (unless io.ConfigWindowsMoveFromTitleBarOnly = true)
- Drag inside window: Scroll contents (when io.ConfigDragScroll = true) unless drag move is possible.
- Left-click outside popup: Close popup stack (right-click over underlying popup: Partially close popup stack).

- TEXT EDITOR
Expand Down Expand Up @@ -1661,6 +1662,12 @@ ImGuiIO::ImGuiIO()
KeyRepeatDelay = 0.275f;
KeyRepeatRate = 0.050f;

// Drag scroll options
ConfigDragScroll = false;
DragScrollButton = ImGuiMouseButton_Left;
DragScrollDecel = 5000.0f;
DragScrollMinSpeed = 300.0f;

// Platform Functions
// Note: Initialize() will setup default clipboard/ime handlers.
BackendPlatformName = BackendRendererName = NULL;
Expand Down Expand Up @@ -4166,6 +4173,9 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
WheelingWindow = NULL;
WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1;
WheelingWindowReleaseTimer = 0.0f;
DragScrollWindow = NULL;
DragScrollOldValue = ImVec2(0.0f, 0.0f);
DragScrollVelocity = ImVec2(0.0f, 0.0f);

DebugDrawIdConflictsId = 0;
DebugHookIdInfoId = 0;
Expand Down Expand Up @@ -4195,6 +4205,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation));
LastActiveId = 0;
LastActiveIdTimer = 0.0f;
DragAction = false;

LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0;

Expand Down Expand Up @@ -5588,6 +5599,7 @@ void ImGui::NewFrame()
if (g.DeactivatedItemData.ElapseFrame < g.FrameCount)
g.DeactivatedItemData.ID = 0;
g.DeactivatedItemData.IsAlive = false;
g.DragAction = false;

// Record when we have been stationary as this state is preserved while over same item.
// FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values.
Expand Down Expand Up @@ -6055,6 +6067,8 @@ void ImGui::EndFrame()
for (ImFontAtlas* atlas : g.FontAtlases)
atlas->Locked = false;

HandleDragScroll();

// Clear Input data for next frame
g.IO.MousePosPrev = g.IO.MousePos;
g.IO.AppFocusLost = false;
Expand Down Expand Up @@ -6360,6 +6374,184 @@ void ImGui::SetActiveIdUsingAllKeyboardKeys()
NavMoveRequestCancel();
}

// Walk up the window hierarchy (up to a root window) until a scrollable window is found.
static ImGuiWindow* FindScrollableWindow(ImGuiWindow* win)
{
for (ImGuiWindow* target = win; target; target = target->ParentWindow)
{
const bool mouse_inputs_forbidden = target->Flags & ImGuiWindowFlags_NoMouseInputs;
const bool mouse_scroll_forbidden = target->Flags & ImGuiWindowFlags_NoScrollWithMouse;
const bool is_scrollable = target->ScrollMax.x > 0 || target->ScrollMax.y > 0;
if (!mouse_inputs_forbidden && !mouse_scroll_forbidden && is_scrollable)
return target;
// Stop if target is a root window.
if (target->ParentWindow == target)
return NULL;
}
return NULL;
}

void ImGui::HandleDragScroll()
{
ImGuiContext& g = *GImGui;
ImGuiIO& io = g.IO;

// Bail out if DragScroll is disabled.
if (!io.ConfigDragScroll)
{
g.DragScrollWindow = NULL;
return;
}

// Bail out if a widget is performing a drag action.
if (IsDragAction())
{
g.DragScrollWindow = NULL;
return;
}

// Bail out if a drag-and-drop operation is ongoing.
if (IsDragDropActive())
{
g.DragScrollWindow = NULL;
return;
}

// Bail out if a window is being moved.
if (g.MovingWindow)
{
g.DragScrollWindow = NULL;
return;
}

if (g.DragScrollWindow)
{
// Bail out if it was garbage-collected.
if (g.DragScrollWindow->MemoryCompacted)
{
g.DragScrollWindow = NULL;
return;
}

// Bail out if the window is collapsed.
if (g.DragScrollWindow->Collapsed)
{
g.DragScrollWindow = NULL;
return;
}

// Bail out when drag move conflicts with drag scroll.
const bool is_movable = !(g.DragScrollWindow->Flags & ImGuiWindowFlags_NoMove);
const bool is_drag_movable = g.DragScrollWindow->BgClickFlags & ImGuiWindowBgClickFlags_Move;
if (is_movable && is_drag_movable)
{
g.DragScrollWindow = NULL;
return;
}

// Bail out if window content is not hoverable (e.g. modal on top.)
if (!IsWindowContentHoverable(g.DragScrollWindow))
{
g.DragScrollWindow = NULL;
return;
}
}

if (IsMouseDown(io.DragScrollButton))
{
// Button is down.

// Never allow gliding while the drag scroll button is down.
g.DragScrollVelocity = ImVec2(0.0f, 0.0f);

if (IsMouseClicked(io.DragScrollButton))
{
// Just clicked.
const ImVec2 clicked_pos = io.MouseClickedPos[io.DragScrollButton];

// Bail out if clicked position is not valid.
if (!IsMousePosValid(&clicked_pos))
return;

ImGuiWindow* pointed_window = NULL;
FindHoveredWindowEx(clicked_pos, false, &pointed_window, NULL);

g.DragScrollWindow = FindScrollableWindow(pointed_window);
// Save original scroll value.
if (g.DragScrollWindow)
g.DragScrollOldValue = g.DragScrollWindow->Scroll;
}

// Bail out if there's no window to scroll.
if (!g.DragScrollWindow)
return;

// Bail out if not (yet) in a dragging state.
if (!IsMouseDragging(io.DragScrollButton))
return;

// Perform drag scroll.
ImVec2 drag_delta = GetMouseDragDelta(io.DragScrollButton);
SetScrollX(g.DragScrollWindow, g.DragScrollOldValue.x - drag_delta.x);
SetScrollY(g.DragScrollWindow, g.DragScrollOldValue.y - drag_delta.y);

// Remember velocity for when the button is released.
g.DragScrollVelocity = - io.MouseDelta / io.DeltaTime;

// Ensure no widget is active, to avoid activating buttons, menus,etc.
ClearActiveID();
}
else
{
// Button is not down.

// Bail out if no window to scroll.
if (!g.DragScrollWindow)
return;

const float min_speed_2 = io.DragScrollMinSpeed * io.DragScrollMinSpeed;
ImVec2& vel = g.DragScrollVelocity;
const float speed_2 = ImLengthSqr(vel);

// Check if speed high is enough to keep gliding.
const bool is_gliding = speed_2 > min_speed_2;

// Perform kinetic scrolling if gliding.
if (is_gliding)
{
const ImVec2 old_pos = g.DragScrollWindow->Scroll;
const ImVec2 new_pos = old_pos + vel * io.DeltaTime;
SetScrollX(g.DragScrollWindow, new_pos.x);
SetScrollY(g.DragScrollWindow, new_pos.y);

// Decelerate scroll velocity.
// integrate deceleration over the delta time
const float decel_speed = io.DragScrollDecel * io.DeltaTime;
const float speed = ImSqrt(speed_2);
if (speed <= decel_speed)
vel = ImVec2(0.0f, 0.0f);
else
// deceleration velocity is always opposed to velocity (vel / speed == normalized(vel))
vel -= vel * decel_speed / speed;

// Cancel velocity when hitting a scroll boundary.
const ImVec2 max = g.DragScrollWindow->ScrollMax;
if ((new_pos.x <= 0 && vel.x < 0) || (new_pos.x >= max.x && vel.x > 0))
vel.x = 0;
if ((new_pos.y <= 0 && vel.y < 0) || (new_pos.y >= max.y && vel.y > 0))
vel.y = 0;

// When gliding, we don't want any hover events.
ClearActiveID();
// If Touchscreen, invalidate mouse position and drag delta, so we don't generate hover events.
if (io.MouseSource == ImGuiMouseSource_TouchScreen) {
io.MousePos = io.MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
ResetMouseDragDelta(io.DragScrollButton);
}
}
}
}

ImGuiID ImGui::GetItemID()
{
ImGuiContext& g = *GImGui;
Expand Down Expand Up @@ -6949,6 +7141,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, int* border_hove
ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size, def.CornerPosN); // Corner of the window corresponding to our corner grip
corner_target = ImClamp(corner_target, clamp_min, clamp_max);
CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target);
SetDragAction();
}

// Only lower-left grip is visible before hovering/activating
Expand Down Expand Up @@ -7028,8 +7221,10 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, int* border_hove
ImVec2 clamp_min(border_n == ImGuiDir_Right ? clamp_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down || (border_n == ImGuiDir_Up && window_move_from_title_bar) ? clamp_rect.Min.y : -FLT_MAX);
ImVec2 clamp_max(border_n == ImGuiDir_Left ? clamp_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? clamp_rect.Max.y : +FLT_MAX);
border_target = ImClamp(border_target, clamp_min, clamp_max);
if (!ignore_resize)
if (!ignore_resize) {
CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target);
SetDragAction();
}
}
if (hovered)
*border_hovered = border_n;
Expand Down Expand Up @@ -9331,7 +9526,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE
// - IsMouseDragPastThreshold() [Internal]
// - IsMouseDragging()
// - GetMousePos()
// - SetMousePos() [Internal]
// - TeleportMousePos() [Internal]
// - GetMousePosOnOpeningCurrentPopup()
// - IsMousePosValid()
// - IsAnyMouseDown()
Expand Down
6 changes: 6 additions & 0 deletions imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,12 @@ struct ImGuiIO
float KeyRepeatDelay; // = 0.275f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.).
float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds.

// Drag scrolling behavior.
bool ConfigDragScroll; // = false // Dragging with a mouse button will scroll the content.
ImGuiMouseButton DragScrollButton; // ImGuiMouseButton_Left // What mouse button is used to detect drag scrolling. See ImGuiConfigFlags_DragScroll.
float DragScrollDecel; // = 5000.0f // How much of the scroll speed decelerates, in pixels per second.
float DragScrollMinSpeed; // = 300.0f // Minimum kinetic scroll speed, in pixels per second, before the scroll is stopped.

//------------------------------------------------------------------
// Debug options
//------------------------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,18 @@ void ImGui::ShowDemoWindow(bool* p_open)
if (!io.ConfigErrorRecoveryEnableAssert && !io.ConfigErrorRecoveryEnableDebugLog && !io.ConfigErrorRecoveryEnableTooltip)
io.ConfigErrorRecoveryEnableAssert = io.ConfigErrorRecoveryEnableDebugLog = io.ConfigErrorRecoveryEnableTooltip = true;

ImGui::SeparatorText("Dragging and scrolling");
ImGui::Checkbox("io.ConfigDragScroll", &io.ConfigDragScroll);
ImGui::SameLine(); HelpMarker("Enable drag-to-scroll interactions.");
ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f);
ImGui::DragFloat("io.MouseDragThreshold", &io.MouseDragThreshold, 0.5f, 0.0f, 100.0f, "%.0f");
ImGui::SameLine(); HelpMarker("Distance threshold before considering we are dragging.");
ImGui::DragFloat("io.DragScrollDecel", &io.DragScrollDecel, 10.0f, 0.0f, 10000.0f, "%.0f");
ImGui::SameLine(); HelpMarker("How much of the scroll speed decelerates, in pixels per second.");
ImGui::DragFloat("io.DragScrollMinSpeed", &io.DragScrollMinSpeed, 1.0f, 0.0f, 1000.0f, "%.0f");
ImGui::SameLine(); HelpMarker("Minimum kinetic scroll speed, in pixels per second, before the scroll is stopped.");
ImGui::PopItemWidth();

// Also read: https://github.com/ocornut/imgui/wiki/Debug-Tools
ImGui::SeparatorText("Debug");
ImGui::Checkbox("io.ConfigDebugIsDebuggerPresent", &io.ConfigDebugIsDebuggerPresent);
Expand Down
10 changes: 9 additions & 1 deletion imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer
#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose
#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! ImTrunc() is not inlined in MSVC debug builds
#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only!
#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only!
//#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023)

// Hint for branch prediction
Expand Down Expand Up @@ -2242,6 +2242,9 @@ struct ImGuiContext
float WheelingWindowReleaseTimer;
ImVec2 WheelingWindowWheelRemainder;
ImVec2 WheelingAxisAvg;
ImGuiWindow* DragScrollWindow; // Track which window is the target of a drag scroll.
ImVec2 DragScrollOldValue; // Store original scroll value before a drag scroll starts.
ImVec2 DragScrollVelocity;

// Item/widgets state and tracking information
ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier
Expand Down Expand Up @@ -2274,6 +2277,7 @@ struct ImGuiContext
ImGuiDataTypeStorage ActiveIdValueOnActivation; // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX.
ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation.
float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation.
bool DragAction; // True when a widget is handling a drag-like action, to avoid conflicts with the drag scrolling code.

// Key/Input Ownership + Shortcut Routing system
// - The idea is that instead of "eating" a given key, we can link to an owner.
Expand Down Expand Up @@ -3411,6 +3415,10 @@ namespace ImGui
IMGUI_API void SetActiveIdUsingAllKeyboardKeys();
inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; }

inline void SetDragAction(bool state = true) { ImGuiContext& g = *GImGui; g.DragAction = state; } // Set to true when a widget is using a mouse drag interaction.
inline bool IsDragAction() { ImGuiContext& g = *GImGui; return g.DragAction; } // Query if a widget is using a mouse drag interaction.
IMGUI_API void HandleDragScroll();

// [EXPERIMENTAL] Low-Level: Key/Input Ownership
// - The idea is that instead of "eating" a given input, we can link to an owner id.
// - Ownership is most often claimed as a result of reacting to a press/down event (but occasionally may be claimed ahead).
Expand Down
Loading