Merge branch 'master' into docking

# Conflicts:
#	backends/imgui_impl_glfw.cpp
#	backends/imgui_impl_sdl2.cpp
#	backends/imgui_impl_sdl3.cpp
#	imgui.cpp
#	imgui.h
#	imgui_internal.h
This commit is contained in:
ocornut
2026-07-03 16:59:31 +02:00
26 changed files with 1413 additions and 74 deletions

182
imgui.cpp
View File

@@ -1257,6 +1257,12 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures:
// System includes
#include <stdio.h> // vsnprintf, sscanf, printf
#include <stdint.h> // intptr_t
#ifndef IMGUI_DISABLE_TIME_FUNCTIONS
#include <time.h> // time(), localtime_r()/localtime_s()
#if defined(_WIN32)
static tm* localtime_r(const time_t* timep, tm* result) { return localtime_s(result, timep) == 0 ? result : NULL; }
#endif
#endif
// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled
#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
@@ -1376,6 +1382,7 @@ static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted
// Settings
static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
static void WindowSettingsHandler_Cleanup(ImGuiContext*, ImGuiSettingsHandler*, ImGuiSettingsCleanupArgs* args);
static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
@@ -1547,7 +1554,7 @@ ImGuiStyle::ImGuiStyle()
GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
ImageRounding = 0.0f; // Rounding of Image() calls.
ImageBorderSize = 0.0f; // Thickness of border around tabs.
ImageBorderSize = 0.0f; // Thickness of border around Image() calls.
TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
TabBorderSize = 0.0f; // Thickness of border around tabs.
TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected.
@@ -1722,6 +1729,8 @@ ImGuiIO::ImGuiIO()
ConfigWindowsMoveFromTitleBarOnly = false;
ConfigWindowsCopyContentsWithCtrlC = false;
ConfigScrollbarScrollByPage = true;
ConfigIniSettingsSaveLastUsedDate = true;
ConfigIniSettingsAutoDiscardMonths = 0;
ConfigMemoryCompactTimer = 60.0f;
ConfigDebugIsDebuggerPresent = false;
ConfigDebugHighlightIdConflicts = true;
@@ -1737,6 +1746,7 @@ ImGuiIO::ImGuiIO()
// Inputs Behaviors
MouseDoubleClickTime = 0.30f;
MouseDoubleClickMaxDist = 6.0f;
MouseSingleClickDelay = 0.50f;
MouseDragThreshold = 6.0f;
KeyRepeatDelay = 0.275f;
KeyRepeatRate = 0.050f;
@@ -4312,6 +4322,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
ActiveIdIsAlive = 0;
ActiveIdTimer = 0.0f;
ActiveIdIsJustActivated = false;
ActiveIdWasSelected = ActiveIdWasSoleSelected = false;
ActiveIdAllowOverlap = false;
ActiveIdNoClearOnFocusLoss = false;
ActiveIdHasBeenPressedBefore = false;
@@ -4328,6 +4339,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation));
LastActiveId = 0;
LastActiveIdTimer = 0.0f;
LastActiveIdWasSelected = LastActiveIdWasSoleSelected = false;
LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0;
@@ -4519,6 +4531,7 @@ void ImGui::Initialize()
ini_handler.TypeName = "Window";
ini_handler.TypeHash = ImHashStr("Window");
ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll;
ini_handler.CleanupFn = WindowSettingsHandler_Cleanup;
ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen;
ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine;
ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll;
@@ -4536,6 +4549,14 @@ void ImGui::Initialize()
g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl;
g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl;
// Setup session starting date
#ifndef IMGUI_DISABLE_TIME_FUNCTIONS
const time_t session_time = time(NULL);
struct tm session_datetime = {};
if (localtime_r(&session_time, &session_datetime))
g.PlatformIO.Platform_SessionDate = (session_datetime.tm_year + 1900) * 10000 + (session_datetime.tm_mon + 1) * 100 + session_datetime.tm_mday;
#endif
// Create default viewport
ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)();
viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;
@@ -4591,10 +4612,14 @@ void ImGui::Shutdown()
for (ImFontAtlas* atlas : g.FontAtlases)
{
UnregisterFontAtlas(atlas);
if (atlas->RefCount == 0 && atlas->OwnerContext == &g)
if (atlas->OwnerContext == &g)
{
atlas->Locked = false;
IM_DELETE(atlas);
IM_ASSERT(atlas->RefCount == 0 && "Destroying context owning a ImFontAtlas which is still used elsewhere!");
if (atlas->RefCount == 0)
{
atlas->Locked = false;
IM_DELETE(atlas);
}
}
}
g.DrawListSharedData.TempBuffer.clear();
@@ -5752,6 +5777,7 @@ void ImGui::NewFrame()
g.Time += g.IO.DeltaTime;
g.FrameCount += 1;
g.SessionDate = ImGuiPackedDate(g.PlatformIO.Platform_SessionDate);
g.TooltipOverrideCount = 0;
g.WindowsActiveCount = 0;
g.MenusIdSubmittedThisFrame.resize(0);
@@ -5823,6 +5849,11 @@ void ImGui::NewFrame()
// Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)
if (g.ActiveId)
g.ActiveIdTimer += g.IO.DeltaTime;
if (g.ActiveId && g.ActiveId == g.LastActiveId)
{
g.LastActiveIdWasSelected = g.ActiveIdWasSelected;
g.LastActiveIdWasSoleSelected = g.ActiveIdWasSoleSelected;
}
g.LastActiveIdTimer += g.IO.DeltaTime;
g.ActiveIdPreviousFrame = g.ActiveId;
g.ActiveIdIsAlive = 0;
@@ -6966,6 +6997,7 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s
{
// Initial window state with e.g. default/arbitrary window position
// Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
ImGuiContext& g = *GImGui;
const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
window->Pos = main_viewport->Pos + ImVec2(60, 60);
window->Size = window->SizeFull = ImVec2(0, 0);
@@ -6974,6 +7006,7 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s
if (settings != NULL)
{
settings->LastUsedDate = g.SessionDate;
SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
ApplyWindowSettings(window, settings);
}
@@ -10040,6 +10073,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE
// - IsMouseReleased()
// - IsMouseDoubleClicked()
// - GetMouseClickedCount()
// - GetItemClickedCountWithSingleClickDelay()
// - IsMouseHoveringRect() [Internal]
// - IsMouseDragPastThreshold() [Internal]
// - IsMouseDragging()
@@ -10619,6 +10653,8 @@ bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id)
return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id)
}
// Prefer higher-level helper GetItemClickedCountWithSingleClickDelay()
// Use if you absolutely need to distinguish single-click from double-click by introducing a delay.
// Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test.
// This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename.
@@ -10626,8 +10662,12 @@ bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown));
if (IsMouseDown(button))
return false;
if (delay < 0.0f)
delay = g.IO.MouseSingleClickDelay;
const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]);
return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay);
return (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay);
}
bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
@@ -10651,6 +10691,62 @@ int ImGui::GetMouseClickedCount(ImGuiMouseButton button)
return g.IO.MouseClickedCount[button];
}
// FIXME: This is close to what BeginDragDropSource() is doing, maybe rework.
static ImGuiID LastItemOverlayButtonForNullId(ImGuiMouseButton mouse_button)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(g.LastItemData.ID == 0);
ImGuiWindow* window = g.CurrentWindow;
ImGuiID id = window->GetIDFromRectangle(g.LastItemData.Rect);
if (g.IO.MouseClicked[mouse_button] && ImGui::ItemHoverable(g.LastItemData.Rect, id, g.LastItemData.ItemFlags))
{
ImGui::SetActiveID(id, window);
ImGui::FocusWindow(window);
}
else if (g.ActiveId == id)
{
ImGui::KeepAliveID(id);
if (!g.IO.MouseDown[mouse_button])
ImGui::ClearActiveID();
}
return id;
}
// [BETA] Building block for disambiguation between single-click and double-click.
// - Returns 1 on single-click but delayed by io.MouseSingleClickDelay (which is always > io.MouseDoubleClickTime) after mouse release.
// - Returns 2+ on double-click and subsequent repeated clicks.
// In order use that to replicate Windows Explorer's "click on label to rename after a delay",
// When the function returns 1 for a delayed single click, you can add with further tests:
// - If you want to test that the mouse position AT THE TIME of the click (before the delay): you can use the 'io.MouseClickedPos[mouse_button]' position.
// - If you want to test that the mouse position is still over the item at the end of delay: you can use '&& IsItemHovered()'.
// - If you want to test that the item was selected or the sole selection AT THE TIME of the click (before the delay): you can test for 'g.LastActiveIdWasSelected' or 'g.LastActiveIdWasSoleSelected'.
// e.g.
// int click_count = ImGui::GetItemClickedCountWithSingleClickDelay(mouse_button);
// if (click_count == 1 && ImGui::GetCurrentContext()->LastActiveIdWasSoleSelected)
// StartRename();
// if (click_count == 2)
// Launch();
int ImGui::GetItemClickedCountWithSingleClickDelay(ImGuiMouseButton mouse_button, float delay)
{
// Action: double-click and subsequent clicks
ImGuiContext& g = *GImGui;
if (g.IO.MouseClickedCount[mouse_button] >= 2 && IsItemClicked(mouse_button))
return g.IO.MouseClickedCount[mouse_button];
// Action: second click, delayed
ImGuiID id = g.LastItemData.ID;
if (id == 0)
id = LastItemOverlayButtonForNullId(mouse_button);
if (g.LastActiveId == id)
{
if (delay >= 0.0f)
delay = ImMax(delay, g.IO.MouseDoubleClickTime + 0.01f);
if (IsMouseReleasedWithDelay(mouse_button, delay) && g.IO.MouseClickedLastCount[mouse_button] == 1)
return 1;
}
return 0;
}
// Test if mouse cursor is hovering given rectangle
// NB- Rectangle is clipped by our current clip setting
// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
@@ -11608,6 +11704,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right);
IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes);
IM_ASSERT(g.IO.MouseSingleClickDelay > g.IO.MouseDoubleClickTime);
// Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way.
if (g.IO.ConfigErrorRecovery)
@@ -16322,6 +16419,19 @@ void ImGui::ClearIniSettings()
handler.ClearAllFn(&g, &handler);
}
void ImGui::CleanupIniSettings(ImGuiSettingsCleanupArgs* args)
{
ImGuiContext& g = *GImGui;
if (g.PlatformIO.Platform_SessionDate == 0)
return;
ImGuiPackedDate discard_older_than_date_p = g.PlatformIO.Platform_SessionDate;
discard_older_than_date_p.SubtractMonths(args->DiscardOlderThanMonths);
args->_DiscardOlderThanDate = discard_older_than_date_p.Unpack();
for (ImGuiSettingsHandler& handler : g.SettingsHandlers)
if (handler.CleanupFn != NULL)
handler.CleanupFn(&g, &handler, args);
}
void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
{
size_t file_data_size = 0;
@@ -16400,6 +16510,9 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
memcpy(buf, ini_data, ini_size);
// Call post-read handlers
ImGuiSettingsCleanupArgs cleanup_args;
if (g.IO.ConfigIniSettingsAutoDiscardMonths > 0)
cleanup_args.DiscardOlderThanMonths = g.IO.ConfigIniSettingsAutoDiscardMonths;
for (ImGuiSettingsHandler& handler : g.SettingsHandlers)
if (handler.ApplyAllFn != NULL)
handler.ApplyAllFn(&g, &handler);
@@ -16499,6 +16612,18 @@ static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandl
g.SettingsWindows.clear();
}
static void WindowSettingsHandler_Cleanup(ImGuiContext* ctx, ImGuiSettingsHandler*, ImGuiSettingsCleanupArgs* args)
{
ImGuiContext& g = *ctx;
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
{
if (args->_DiscardOlderThanDate != 0 && settings->LastUsedDate.Unpack() < args->_DiscardOlderThanDate)
settings->WantDelete = true;
if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && settings->LastUsedDate.IsValid() == false))
settings->LastUsedDate = g.SessionDate;
}
}
static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
{
ImGuiID id = ImHashStr(name);
@@ -16524,6 +16649,7 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*,
else if (sscanf(line, "ViewportPos=%i,%i", &x, &y) == 2){ settings->ViewportPos = ImVec2ih((short)x, (short)y); }
else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); }
else if (sscanf(line, "IsChild=%d", &i) == 1) { settings->IsChild = (i != 0); }
else if (sscanf(line, "LastUsed=%d", &i) == 1) { settings->LastUsedDate = i; }
else if (sscanf(line, "DockId=0x%X,%d", &u1, &i) == 2) { settings->DockId = u1; settings->DockOrder = (short)i; }
else if (sscanf(line, "DockId=0x%X", &u1) == 1) { settings->DockId = u1; settings->DockOrder = -1; }
else if (sscanf(line, "ClassId=0x%X", &u1) == 1) { settings->ClassId = u1; }
@@ -16556,6 +16682,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl
if (!settings)
{
settings = ImGui::CreateNewWindowSettings(window->Name);
settings->LastUsedDate = g.SessionDate;
window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
}
IM_ASSERT(settings->ID == window->ID);
@@ -16570,6 +16697,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl
settings->Collapsed = window->Collapsed;
settings->IsChild = (window->RootWindow != window); // Cannot rely on ImGuiWindowFlags_ChildWindow here as docked windows have this set.
settings->WantDelete = false;
settings->LastUsedDate = g.SessionDate;
}
// Write to text buffer
@@ -16579,7 +16707,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl
if (settings->WantDelete)
continue;
const char* settings_name = settings->GetName();
buf->appendf("[%s][%s]\n", handler->TypeName, settings_name);
buf->appendf("[%s][%s]\n", handler->TypeName, settings_name); // [Window][name]
if (settings->IsChild)
{
buf->appendf("IsChild=1\n");
@@ -16608,6 +16736,9 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl
buf->appendf("ClassId=0x%08X\n", settings->ClassId);
}
}
if (g.IO.ConfigIniSettingsSaveLastUsedDate)
if (int last_used_date = settings->LastUsedDate.Unpack())
buf->appendf("LastUsed=%08d\n", last_used_date);
buf->append("\n");
}
}
@@ -22393,6 +22524,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
{
ImGuiContext& g = *GImGui;
ImGuiIO& io = g.IO;
ImGuiPlatformIO& platform_io = g.PlatformIO;
ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
if (cfg->ShowDebugLog)
ShowDebugLogWindow(&cfg->ShowDebugLog);
@@ -22780,8 +22912,36 @@ void ImGui::ShowMetricsWindow(bool* p_open)
Text("\"%s\"", g.IO.IniFilename);
else
TextUnformatted("<NULL>");
Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings);
Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer);
int highlight_older_than_date = 0;
Text("SessionDate: %d", platform_io.Platform_SessionDate);
BeginDisabled(platform_io.Platform_SessionDate == 0);
Checkbox("Highlight Entries Older Than", &cfg->SettingsHighlightOldEntries);
SetNextItemWidth(GetFontSize() * 8);
SameLine();
SliderInt("Months", &cfg->SettingsDiscardMonths, 1, 24);
if (cfg->SettingsHighlightOldEntries && cfg->SettingsDiscardMonths > 0)
{
ImGuiPackedDate cutoff_date = platform_io.Platform_SessionDate;
cutoff_date.SubtractMonths(cfg->SettingsDiscardMonths);
highlight_older_than_date = cutoff_date.Unpack();
SameLine();
ImGuiSettingsCleanupArgs cleanup_args;
cleanup_args.DiscardOlderThanMonths = cfg->SettingsDiscardMonths;
if (Button("Discard"))
CleanupIniSettings(&cleanup_args);
}
EndDisabled();
Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings);
struct ScopedHighlightOlderThan
{
bool Highlight;
ScopedHighlightOlderThan(int cutoff_date, ImGuiPackedDate in_date) { Highlight = cutoff_date != 0 && in_date.Unpack() < cutoff_date; if (Highlight) PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f)); }
~ScopedHighlightOlderThan() { if (Highlight) PopStyleColor(); }
};
if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size))
{
for (ImGuiSettingsHandler& handler : g.SettingsHandlers)
@@ -22791,14 +22951,20 @@ void ImGui::ShowMetricsWindow(bool* p_open)
if (TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size()))
{
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
{
ScopedHighlightOlderThan scoped_highlight(highlight_older_than_date, settings->LastUsedDate);
DebugNodeWindowSettings(settings);
}
TreePop();
}
if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size()))
{
for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
{
ScopedHighlightOlderThan scoped_highlight(highlight_older_than_date, settings->LastUsedDate);
DebugNodeTableSettings(settings, NULL);
}
TreePop();
}
@@ -23769,7 +23935,7 @@ void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings)
{
if (settings->WantDelete)
BeginDisabled();
Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d",
BulletText("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d",
settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed);
if (settings->WantDelete)
EndDisabled();