From f7e8343ee97726e8e74e19097364dc77ccde79eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 23:00:37 +0200 Subject: [PATCH] Settings, IO: added io.ConfigIniSettingsSaveLastUsedDate, platform_io.Platform_SessionDate, IMGUI_DISABLE_TIME_FUNCTIONS(). (#9460) cc #437 --- docs/CHANGELOG.txt | 8 ++++++++ imconfig.h | 1 + imgui.cpp | 27 +++++++++++++++++++++++++-- imgui.h | 7 +++++++ imgui_demo.cpp | 3 +++ imgui_internal.h | 18 ++++++++++++++++++ imgui_tables.cpp | 6 ++++++ 7 files changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index aca07b0ad..dba212f14 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -116,6 +116,14 @@ Other Changes: The use of this is discouraged because it can easily create problems rendering e.g. contiguous selection. - Scale the NavCursor border thickness when using large values with `ScallAllSizes()`. +- Settings: + - Windows/Tables settings entries can now record the last used date in YYYYMMDD format, + allowing cleanup tools to run to e.g. delete entries that haven't been used in X months. + - Added bool io.ConfigIniSettingsSaveLastUsedDate to disable saving that info. + - The current system date is fed through ImGuiPlatformIO::Platform_SessionDate, + which is automatically set by a call to time() done during context creation. + - Added IMGUI_DISABLE_TIME_FUNCTIONS to disable setting platform_io.Platform_SessionDate. + A custom backend may still set it manually. - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imconfig.h b/imconfig.h index 851a44b1b..b40db3898 100644 --- a/imconfig.h +++ b/imconfig.h @@ -43,6 +43,7 @@ //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). +//#define IMGUI_DISABLE_TIME_FUNCTIONS // Don't setup default platform_io.Platform_SessionDate value using time(), localtime_r(). //#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. diff --git a/imgui.cpp b/imgui.cpp index 2feefc854..844ca0342 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1243,6 +1243,12 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: // System includes #include // vsnprintf, sscanf, printf #include // intptr_t +#ifndef IMGUI_DISABLE_TIME_FUNCTIONS +#include // 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) @@ -1677,6 +1683,7 @@ ImGuiIO::ImGuiIO() ConfigWindowsMoveFromTitleBarOnly = false; ConfigWindowsCopyContentsWithCtrlC = false; ConfigScrollbarScrollByPage = true; + ConfigIniSettingsSaveLastUsedDate = true; ConfigMemoryCompactTimer = 60.0f; ConfigDebugIsDebuggerPresent = false; ConfigDebugHighlightIdConflicts = true; @@ -4452,6 +4459,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; @@ -5568,6 +5583,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); @@ -6692,6 +6708,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); @@ -6699,6 +6716,7 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s if (settings != NULL) { + settings->LastUsedDate = g.SessionDate; SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); ApplyWindowSettings(window, settings); } @@ -15870,6 +15888,7 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = 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; return; } } // Apply to existing windows (if any) @@ -15907,6 +15926,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl settings->IsChild = (window->Flags & ImGuiWindowFlags_ChildWindow) != 0; settings->Collapsed = window->Collapsed; settings->WantDelete = false; + settings->LastUsedDate = g.SessionDate; } // Write to text buffer @@ -15916,7 +15936,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"); @@ -15929,6 +15949,9 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl if (settings->Collapsed) buf->appendf("Collapsed=1\n"); } + if (g.IO.ConfigIniSettingsSaveLastUsedDate) + if (int last_used_date = settings->LastUsedDate.Unpack()) + buf->appendf("LastUsed=%08d\n", last_used_date); buf->append("\n"); } } @@ -17866,7 +17889,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(); diff --git a/imgui.h b/imgui.h index 0d4995699..d469e0fb8 100644 --- a/imgui.h +++ b/imgui.h @@ -2436,6 +2436,9 @@ struct ImGuiIO bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. + // Ini Settings + bool ConfigIniSettingsSaveLastUsedDate;// = true // Enable loading/saving last used day (YYYYMMDD) in some .ini struct, making things easier to audit and allowing custom tools to cleanup old data. + // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. @@ -4039,6 +4042,10 @@ struct ImGuiPlatformIO // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point ImWchar Platform_LocaleDecimalPoint; // '.' + // Optional: Platform time/date + // This is automatically filled on startup. Used to store a "last used date" in some .ini structures. Facilitate creating tools to clean up old/unused data. + int Platform_SessionDate; // Integer storing YYYYMMDD e.g. 20261231 corresponding to the beginning of application session. + //------------------------------------------------------------------ // Input - Interface with Renderer Backend //------------------------------------------------------------------ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 1bd300666..91d7e030d 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -538,6 +538,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors."); ImGui::Text("Also see Style->Rendering for rendering options."); + ImGui::SeparatorText("Settings"); + ImGui::Checkbox("io.ConfigIniSettingsSaveLastUsedDate", &io.ConfigIniSettingsSaveLastUsedDate); + // Also read: https://github.com/ocornut/imgui/wiki/Error-Handling ImGui::SeparatorText("Error Handling"); diff --git a/imgui_internal.h b/imgui_internal.h index 299287e65..c7e1a03b4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -138,6 +138,7 @@ Index of this file: struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) struct ImGuiTextIndex; // Maintain a line index for a text buffer. +struct ImGuiPackedDate; // A date in YYYYMMDD format packed into 16-bits // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance @@ -833,6 +834,20 @@ struct ImGuiTextIndex void append(const char* base, int old_size, int new_size); }; +// Helper: ImGuiPackedDate (sizeof() == 2) +// Store a date in a way that is efficient to read/write in text form. If we stored e.g. number of days since Epoch we'd need costlier back and forth. +// This is specifically designed to be able to prune old .ini data. +struct ImGuiPackedDate +{ + ImU16 Year : 7; // Year since 2000 // We can change to another offset e.g. 1970 but this is easier to watch in debugger. + ImU16 Month : 4; // Month (1-12) + ImU16 Day : 5; // Day (1-31) + + ImGuiPackedDate() { Year = Month = Day = 0; } + ImGuiPackedDate(int yyyymmdd) { Year = (ImU16)((yyyymmdd / 10000) - 2000); Month = (ImU16)((yyyymmdd / 100) % 100); Day = (ImU16)(yyyymmdd % 100); } // Pack + int Unpack() const { return (Year && Month && Day) ? ((Year + 2000) * 10000) + (Month * 100) + Day : 0; } // Unpack +}; + // Helper: ImGuiStorage IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key); @@ -2018,6 +2033,7 @@ struct ImGuiWindowSettings ImGuiID ID; ImVec2ih Pos; ImVec2ih Size; + ImGuiPackedDate LastUsedDate; bool Collapsed : 1; bool IsChild : 1; bool WantApply : 1; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) @@ -2533,6 +2549,7 @@ struct ImGuiContext ImVector UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[]. // Settings + ImGuiPackedDate SessionDate; // Packed copy of platform_io.Platform_SessionDate, when valid. bool SettingsLoaded; float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero ImGuiTextBuffer SettingsIniData; // In memory .ini settings @@ -3208,6 +3225,7 @@ struct ImGuiTableSettings float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. ImGuiTableColumnIdx ColumnsCount; ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher + ImGuiPackedDate LastUsedDate; bool WantApply : 1; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) ImGuiTableSettings() { memset((void*)this, 0, sizeof(*this)); } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b71304f25..1ff368239 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3933,6 +3933,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); } settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount; + settings->LastUsedDate = g.SessionDate; // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings IM_ASSERT(settings->ID == table->ID); @@ -4001,6 +4002,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; + settings->LastUsedDate = g.SessionDate; // TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data. } @@ -4163,6 +4165,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, int column_n = 0, r = 0, n = 0; if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } + if (sscanf(line, "LastUsed=%d", &n) == 1) { settings->LastUsedDate = n; return; } if (sscanf(line, "Column %d%n", &column_n, &r) == 1) { @@ -4218,6 +4221,9 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle if (column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } buf->append("\n"); } + if (g.IO.ConfigIniSettingsSaveLastUsedDate) + if (int last_used_date = settings->LastUsedDate.Unpack()) + buf->appendf("LastUsed=%08d\n", last_used_date); buf->append("\n"); } }