From 0bf43b3a1b8ce9a9f8f6ced6c8a310a7bf8fb4c2 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 7 May 2018 22:18:45 +0200 Subject: [PATCH] Settings: Added LoadIniSettingsFromDisk(), LoadIniSettingsFromMemory(), SaveIniSettingsToDisk(), SaveIniSettingsToMemory(), io.WantSaveIniSettings. (#923, #993) --- CHANGELOG.txt | 2 ++ TODO.txt | 1 - imgui.cpp | 91 ++++++++++++++++++++++++++---------------------- imgui.h | 14 ++++++-- imgui_draw.cpp | 4 +-- imgui_internal.h | 8 +++-- 6 files changed, 69 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ff464619d..848cf5abb 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -53,6 +53,8 @@ Other Changes: - Window: Fixed windows using the ImGuiWindowFlags_NoSavedSettings flag from not using the same default position as other windows. (#1760) - Window: Relaxed the internal stack size checker to allow Push/Begin/Pop/.../End patterns to be used with PushStyleColor, PushStyleVar, PushFont without causing a false positive assert. (#1767) - Columns: Fixed a bug introduced in 1.51 where columns would affect the contents size of their container, often creating feedback loops when ImGuiWindowFlags_AlwaysAutoResize was used. (#1760) +- Settings: Added LoadIniSettingsFromDisk(), LoadIniSettingsFromMemory(), SaveIniSettingsToDisk(), SaveIniSettingsToMemory() to manually load/save .ini settings. (#923, #993) +- Settings: Added io.WantSaveIniSettings flag, which is set to notify the application that e.g. SaveIniSettingsToMemory() should be called. (#923, #993) - MenuBar: Made BeginMainMenuBar() honor style.DisplaySafeAreaPadding so the text can be made visible on TV settings that don't display all pixels. (#1439) [@dougbinks] - InputText: On Mac OS X, filter out characters when the CMD modifier is held. (#1747) [@sivu] - InputText: On Mac OS X, support CMD+SHIFT+Z for Redo. CMD+Y is also supported as major apps seems to default to support both. (#1765) [@lfnoise] diff --git a/TODO.txt b/TODO.txt index 2ddd1cf90..1a79c4eea 100644 --- a/TODO.txt +++ b/TODO.txt @@ -183,7 +183,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - tree node: tweak color scheme to distinguish headers from selected tree node (#581) - tree node: leaf/non-leaf highlight mismatch. -!- settings: expose enough to save/load .ini from RAM instead of fopen - settings: write more decent code to allow saving/loading new fields: columns, selected tree nodes? - settings: api for per-tool simple persistent data (bool,int,float,columns sizes,etc.) in .ini file (#437) - stb: add defines to disable stb implementations diff --git a/imgui.cpp b/imgui.cpp index 318ea616f..c5b16151b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -733,12 +733,6 @@ static void AddWindowToSortedBuffer(ImVector* out_sort static ImGuiWindowSettings* AddWindowSettings(const char* name); -static void LoadIniSettingsFromDisk(const char* ini_filename); -static void LoadIniSettingsFromMemory(const char* buf); -static void SaveIniSettingsToDisk(const char* ini_filename); -static void SaveIniSettingsToMemory(ImVector& out_buf); -static void MarkIniSettingsDirty(ImGuiWindow* window); - static ImRect GetViewportRect(); static void ClosePopupToLevel(int remaining); @@ -1455,7 +1449,7 @@ FILE* ImFileOpen(const char* filename, const char* mode) // Load file content into memory // Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() -void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) +void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes) { IM_ASSERT(filename && file_open_mode); if (out_file_size) @@ -1472,14 +1466,14 @@ void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* return NULL; } - int file_size = (int)file_size_signed; - void* file_data = ImGui::MemAlloc((size_t)(file_size + padding_bytes)); + size_t file_size = (size_t)file_size_signed; + void* file_data = ImGui::MemAlloc(file_size + padding_bytes); if (file_data == NULL) { fclose(f); return NULL; } - if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) + if (fread(file_data, 1, file_size, f) != file_size) { fclose(f); ImGui::MemFree(file_data); @@ -3444,20 +3438,27 @@ void ImGui::NewFrame() if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); - // Load settings on first frame + // Load settings on first frame (if not explicitly loaded manually before) if (!g.SettingsLoaded) { IM_ASSERT(g.SettingsWindows.empty()); - LoadIniSettingsFromDisk(g.IO.IniFilename); + if (g.IO.IniFilename) + LoadIniSettingsFromDisk(g.IO.IniFilename); g.SettingsLoaded = true; } - // Save settings (with a delay so we don't spam disk too much) + // Save settings (with a delay after the last modification, so we don't spam disk too much) if (g.SettingsDirtyTimer > 0.0f) { g.SettingsDirtyTimer -= g.IO.DeltaTime; if (g.SettingsDirtyTimer <= 0.0f) - SaveIniSettingsToDisk(g.IO.IniFilename); + { + if (g.IO.IniFilename != NULL) + SaveIniSettingsToDisk(g.IO.IniFilename); + else + g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves. + g.SettingsDirtyTimer = 0.0f; + } } g.Time += g.IO.DeltaTime; @@ -3685,19 +3686,18 @@ void ImGui::Initialize(ImGuiContext* context) // This function is merely here to free heap allocations. void ImGui::Shutdown(ImGuiContext* context) { - ImGuiContext& g = *context; - // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + ImGuiContext& g = *context; if (g.IO.Fonts && g.FontAtlasOwnedByContext) IM_DELETE(g.IO.Fonts); g.IO.Fonts = NULL; - // Cleanup of other data are conditional on actually having initialize ImGui. + // Cleanup of other data are conditional on actually having initialized ImGui. if (!g.Initialized) return; // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) - if (g.SettingsLoaded) + if (g.SettingsLoaded && g.IO.IniFilename != NULL) SaveIniSettingsToDisk(g.IO.IniFilename); // Clear everything else @@ -3759,14 +3759,13 @@ static ImGuiWindowSettings* AddWindowSettings(const char* name) return settings; } -static void LoadIniSettingsFromDisk(const char* ini_filename) +void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) { - if (!ini_filename) - return; - char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1); + size_t file_data_size = 0; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size); if (!file_data) return; - LoadIniSettingsFromMemory(file_data); + LoadIniSettingsFromMemory(file_data, (size_t)file_data_size); ImGui::MemFree(file_data); } @@ -3781,13 +3780,21 @@ ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) } // Zero-tolerance, no error reporting, cheap .ini parsing -static void LoadIniSettingsFromMemory(const char* buf_readonly) +void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) { - // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy. - char* buf = ImStrdup(buf_readonly); - char* buf_end = buf + strlen(buf); - ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0); + + // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter). + // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy.. + if (ini_size == 0) + ini_size = strlen(ini_data); + char* buf = (char*)ImGui::MemAlloc(ini_size + 1); + char* buf_end = buf + ini_size; + memcpy(buf, ini_data, ini_size); + buf[ini_size] = 0; + void* entry_data = NULL; ImGuiSettingsHandler* entry_handler = NULL; @@ -3820,7 +3827,7 @@ static void LoadIniSettingsFromMemory(const char* buf_readonly) *type_end = 0; // Overwrite first ']' name_start++; // Skip second '[' } - entry_handler = ImGui::FindSettingsHandler(type_start); + entry_handler = FindSettingsHandler(type_start); entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; } else if (entry_handler != NULL && entry_data != NULL) @@ -3833,37 +3840,37 @@ static void LoadIniSettingsFromMemory(const char* buf_readonly) g.SettingsLoaded = true; } -static void SaveIniSettingsToDisk(const char* ini_filename) +void ImGui::SaveIniSettingsToDisk(const char* ini_filename) { ImGuiContext& g = *GImGui; g.SettingsDirtyTimer = 0.0f; if (!ini_filename) return; - ImVector buf; - SaveIniSettingsToMemory(buf); - + size_t ini_data_size = 0; + const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); FILE* f = ImFileOpen(ini_filename, "wt"); if (!f) return; - fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f); + fwrite(ini_data, sizeof(char), ini_data_size, f); fclose(f); } -static void SaveIniSettingsToMemory(ImVector& out_buf) +// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer +const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) { ImGuiContext& g = *GImGui; g.SettingsDirtyTimer = 0.0f; - - ImGuiTextBuffer buf; + g.SettingsIniData.Buf.resize(0); + g.SettingsIniData.Buf.push_back(0); for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) { ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; - handler->WriteAllFn(&g, handler, &buf); + handler->WriteAllFn(&g, handler, &g.SettingsIniData); } - - buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer - out_buf.swap(buf.Buf); + if (out_size) + *out_size = (size_t)g.SettingsIniData.size(); + return g.SettingsIniData.c_str(); } void ImGui::MarkIniSettingsDirty() @@ -3873,7 +3880,7 @@ void ImGui::MarkIniSettingsDirty() g.SettingsDirtyTimer = g.IO.IniSavingRate; } -static void MarkIniSettingsDirty(ImGuiWindow* window) +void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) { ImGuiContext& g = *GImGui; if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) diff --git a/imgui.h b/imgui.h index 02b752196..8a5604ee4 100644 --- a/imgui.h +++ b/imgui.h @@ -542,6 +542,13 @@ namespace ImGui IMGUI_API const char* GetClipboardText(); IMGUI_API void SetClipboardText(const char* text); + // Settings/.Ini Utilities + // The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). Set io.IniFilename to NULL to load/save manually. + IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). + IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. + IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); + IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. + // Memory Utilities // All those functions are not reliant on the current context. // If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again. @@ -1001,11 +1008,11 @@ struct ImGuiIO //------------------------------------------------------------------ ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc. - ImGuiBackendFlags BackendFlags; // = 0 // Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or custom back-end. + ImGuiBackendFlags BackendFlags; // = 0 // Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or custom back-end to communicate features supported by the back-end. ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. - float IniSavingRate; // = 5.0f // Maximum time between saving positions/sizes to .ini file, in seconds. - const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. + float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. Set NULL to disable automatic .ini loading/saving, if e.g. you want to manually load/save from memory. const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. @@ -1081,6 +1088,7 @@ struct ImGuiIO bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, imgui will use the keyboard inputs, do not dispatch them to your main game/application (in both cases, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). bool WantSetMousePos; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. + bool WantSaveIniSettings; // If io.IniFilename == NULL, this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. IMPORTANT: You need to clear io.WantSaveIniSettings yourself. bool NavActive; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. bool NavVisible; // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events). float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames diff --git a/imgui_draw.cpp b/imgui_draw.cpp index efcdacd79..e67d55193 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1541,7 +1541,7 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - int data_size = 0; + size_t data_size = 0; void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) { @@ -1556,7 +1556,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); } - return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges); + return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); } // NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). diff --git a/imgui_internal.h b/imgui_internal.h index 9c6f504eb..031ff82cb 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -96,7 +96,7 @@ IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, cons // Helpers: Misc IMGUI_API ImU32 ImHash(const void* data, int data_size, ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings -IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size = NULL, int padding_bytes = 0); +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size = NULL, int padding_bytes = 0); IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); static inline bool ImCharIsSpace(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } @@ -676,9 +676,10 @@ struct ImGuiContext // Settings bool SettingsLoaded; - float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero - ImVector SettingsWindows; // .ini settings for ImGuiWindow + float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero + ImGuiTextBuffer SettingsIniData; // In memory .ini settings ImVector SettingsHandlers; // List of .ini settings handlers + ImVector SettingsWindows; // ImGuiWindow .ini settings entries (parsed from the last loaded .ini file and maintained on saving) // Logging bool LogEnabled; @@ -1022,6 +1023,7 @@ namespace ImGui IMGUI_API void NewFrameUpdateHoveredWindowAndCaptureFlags(); IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id);