From d9c5d72962b4f88491cf570aaaa2b0ce8e2b6fb9 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 24 Nov 2017 11:26:42 +0100 Subject: [PATCH 01/13] ImGuiStorage: Added BuildSortByKey() helper to rebuild storage from stratch. --- imgui.cpp | 17 +++++++++++++++++ imgui.h | 3 +++ 2 files changed, 20 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 0c209b67..211bd299 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1411,6 +1411,23 @@ static ImVector::iterator LowerBound(ImVectorkey > ((const Pair*)rhs)->key) return +1; + if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); +} + int ImGuiStorage::GetInt(ImGuiID key, int default_val) const { ImVector::iterator it = LowerBound(const_cast&>(Data), key); diff --git a/imgui.h b/imgui.h index 319e76b0..4599d705 100644 --- a/imgui.h +++ b/imgui.h @@ -1097,6 +1097,9 @@ struct ImGuiStorage // Use on your own storage if you know only integer are being stored (open/close all tree nodes) IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); }; // Shared state of InputText(), passed to callback when a ImGuiInputTextFlags_Callback* flag is used and the corresponding callback is triggered. From 0f955b818d729805300f9e0aed936fa794d71b5b Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 26 Nov 2017 11:44:52 +0100 Subject: [PATCH 02/13] Fixed DroidSans font link (#1460) --- extra_fonts/README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra_fonts/README.txt b/extra_fonts/README.txt index a4136083..5d41a6bb 100644 --- a/extra_fonts/README.txt +++ b/extra_fonts/README.txt @@ -153,7 +153,7 @@ DroidSans.ttf Copyright (c) Steve Matteson Apache License, version 2.0 - http://www.google.com/fonts/specimen/Droid+Sans + https://www.fontsquirrel.com/fonts/droid-sans ProggyClean.ttf Copyright (c) 2004, 2005 Tristan Grimmer From 532f564fd325421d11991c21a5e8bdfbb2b1bc26 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 27 Nov 2017 16:55:06 +0100 Subject: [PATCH 03/13] ImGuiTextBuffer: Renamed append() helper to appendf(), appendv() to appendfv(). Added reserve(). --- imgui.cpp | 9 +++++---- imgui.h | 5 +++-- imgui_demo.cpp | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 211bd299..505fa0a8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -213,6 +213,7 @@ Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. Also read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. @@ -1643,7 +1644,7 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const #endif // Helper: Text buffer for logging/accumulating text -void ImGuiTextBuffer::appendv(const char* fmt, va_list args) +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) { va_list args_copy; va_copy(args_copy, args); @@ -1664,11 +1665,11 @@ void ImGuiTextBuffer::appendv(const char* fmt, va_list args) ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy); } -void ImGuiTextBuffer::append(const char* fmt, ...) +void ImGuiTextBuffer::appendf(const char* fmt, ...) { va_list args; va_start(args, fmt); - appendv(fmt, args); + appendfv(fmt, args); va_end(args); } @@ -2947,7 +2948,7 @@ void ImGui::LogText(const char* fmt, ...) } else { - g.LogClipboard->appendv(fmt, args); + g.LogClipboard->appendfv(fmt, args); } va_end(args); } diff --git a/imgui.h b/imgui.h index 4599d705..309730f2 100644 --- a/imgui.h +++ b/imgui.h @@ -1048,9 +1048,10 @@ struct ImGuiTextBuffer int size() const { return Buf.Size - 1; } bool empty() { return Buf.Size <= 1; } void clear() { Buf.clear(); Buf.push_back(0); } + void reserve(int capacity) { Buf.reserve(capacity); } const char* c_str() const { return Buf.Data; } - IMGUI_API void append(const char* fmt, ...) IM_FMTARGS(2); - IMGUI_API void appendv(const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); }; // Helper: Simple Key->value storage diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b1fefba4..30a4ce46 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2707,7 +2707,7 @@ struct ExampleAppLog int old_size = Buf.size(); va_list args; va_start(args, fmt); - Buf.appendv(fmt, args); + Buf.appendfv(fmt, args); va_end(args); for (int new_size = Buf.size(); old_size < new_size; old_size++) if (Buf[old_size] == '\n') @@ -2911,7 +2911,7 @@ static void ShowExampleAppLongText(bool* p_open) if (ImGui::Button("Add 1000 lines")) { for (int i = 0; i < 1000; i++) - log.append("%i The quick brown fox jumps over the lazy dog\n", lines+i); + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); lines += 1000; } ImGui::BeginChild("Log"); From ade09b9e3c7bbc3e46256fea10b64ce992eec331 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 27 Nov 2017 19:01:40 +0100 Subject: [PATCH 04/13] Settings: Basic internal refactor to have functions for saving from/to memory (not exposed) --- imgui.cpp | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 505fa0a8..fcf3e8c3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -646,7 +646,9 @@ static void AddWindowToSortedBuffer(ImVector& out_sort static ImGuiIniData* FindWindowSettings(const char* name); static ImGuiIniData* AddWindowSettings(const char* name); static void LoadIniSettingsFromDisk(const char* ini_filename); +static void LoadIniSettingsFromMemory(const char* buf, const char* buf_end = NULL); static void SaveIniSettingsToDisk(const char* ini_filename); +static void SaveIniSettingsToMemory(ImVector& out_buf); static void MarkIniSettingsDirty(ImGuiWindow* window); static ImRect GetVisibleRect(); @@ -2562,18 +2564,23 @@ static ImGuiIniData* AddWindowSettings(const char* name) // FIXME: Write something less rubbish static void LoadIniSettingsFromDisk(const char* ini_filename) { - ImGuiContext& g = *GImGui; if (!ini_filename) return; - int file_size; char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_size, 1); if (!file_data) return; + LoadIniSettingsFromMemory(file_data, file_data + file_size); + ImGui::MemFree(file_data); +} +static void LoadIniSettingsFromMemory(const char* buf, const char* buf_end) +{ + ImGuiContext& g = *GImGui; + if (!buf_end) + buf_end = buf + strlen(buf); ImGuiIniData* settings = NULL; - const char* buf_end = file_data + file_size; - for (const char* line_start = file_data; line_start < buf_end; ) + for (const char* line_start = buf; line_start < buf_end; ) { const char* line_end = line_start; while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') @@ -2601,8 +2608,6 @@ static void LoadIniSettingsFromDisk(const char* ini_filename) line_start = line_end+1; } - - ImGui::MemFree(file_data); } static void SaveIniSettingsToDisk(const char* ini_filename) @@ -2612,6 +2617,22 @@ static void SaveIniSettingsToDisk(const char* ini_filename) if (!ini_filename) return; + ImVector buf; + SaveIniSettingsToMemory(buf); + + // Write .ini file + FILE* f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f); + fclose(f); +} + +static void SaveIniSettingsToMemory(ImVector& out_buf) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + // Gather data from windows that were active during this session for (int i = 0; i != g.Windows.Size; i++) { @@ -2626,11 +2647,10 @@ static void SaveIniSettingsToDisk(const char* ini_filename) settings->Collapsed = window->Collapsed; } - // Write .ini file + // Write a buffer // If a window wasn't opened in this session we preserve its settings - FILE* f = ImFileOpen(ini_filename, "wt"); - if (!f) - return; + ImGuiTextBuffer buf; + buf.reserve(g.Settings.Size * 64); // ballpark reserve for (int i = 0; i != g.Settings.Size; i++) { const ImGuiIniData* settings = &g.Settings[i]; @@ -2639,14 +2659,14 @@ static void SaveIniSettingsToDisk(const char* ini_filename) const char* name = settings->Name; if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() name = p; - fprintf(f, "[%s]\n", name); - fprintf(f, "Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); - fprintf(f, "Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); - fprintf(f, "Collapsed=%d\n", settings->Collapsed); - fprintf(f, "\n"); + buf.appendf("[%s]\n", name); + buf.appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf.appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf.appendf("Collapsed=%d\n", settings->Collapsed); + buf.appendf("\n"); } - fclose(f); + out_buf.swap(buf.Buf); } static void MarkIniSettingsDirty(ImGuiWindow* window) From d552cabd15017f61a291787935985b97d2e0380c Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 27 Nov 2017 23:00:27 +0100 Subject: [PATCH 05/13] Settings: Internal renaming of structure and fields names. --- imgui.cpp | 50 +++++++++++++++++++++++++----------------------- imgui_internal.h | 6 +++--- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index fcf3e8c3..8b618988 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -643,8 +643,9 @@ static void AddDrawListToRenderList(ImVector& out_rende static void AddWindowToRenderList(ImVector& out_render_list, ImGuiWindow* window); static void AddWindowToSortedBuffer(ImVector& out_sorted_windows, ImGuiWindow* window); -static ImGuiIniData* FindWindowSettings(const char* name); -static ImGuiIniData* AddWindowSettings(const char* name); +static ImGuiSettingsWindow* FindWindowSettings(const char* name); +static ImGuiSettingsWindow* AddWindowSettings(const char* name); + static void LoadIniSettingsFromDisk(const char* ini_filename); static void LoadIniSettingsFromMemory(const char* buf, const char* buf_end = NULL); static void SaveIniSettingsToDisk(const char* ini_filename); @@ -2468,7 +2469,7 @@ void ImGui::Initialize() g.LogClipboard = (ImGuiTextBuffer*)ImGui::MemAlloc(sizeof(ImGuiTextBuffer)); IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer(); - IM_ASSERT(g.Settings.empty()); + IM_ASSERT(g.SettingsWindows.empty()); LoadIniSettingsFromDisk(g.IO.IniFilename); g.Initialized = true; } @@ -2503,9 +2504,9 @@ void ImGui::Shutdown() g.HoveredRootWindow = NULL; g.ActiveIdWindow = NULL; g.MovingWindow = NULL; - for (int i = 0; i < g.Settings.Size; i++) - ImGui::MemFree(g.Settings[i].Name); - g.Settings.clear(); + for (int i = 0; i < g.SettingsWindows.Size; i++) + ImGui::MemFree(g.SettingsWindows[i].Name); + g.SettingsWindows.clear(); g.ColorModifiers.clear(); g.StyleModifiers.clear(); g.FontStack.clear(); @@ -2535,29 +2536,30 @@ void ImGui::Shutdown() g.Initialized = false; } -static ImGuiIniData* FindWindowSettings(const char* name) +static ImGuiSettingsWindow* FindWindowSettings(const char* name) { ImGuiContext& g = *GImGui; ImGuiID id = ImHash(name, 0); - for (int i = 0; i != g.Settings.Size; i++) + for (int i = 0; i != g.SettingsWindows.Size; i++) { - ImGuiIniData* ini = &g.Settings[i]; + ImGuiSettingsWindow* ini = &g.SettingsWindows[i]; if (ini->Id == id) return ini; } return NULL; } -static ImGuiIniData* AddWindowSettings(const char* name) +static ImGuiSettingsWindow* AddWindowSettings(const char* name) { - GImGui->Settings.resize(GImGui->Settings.Size + 1); - ImGuiIniData* ini = &GImGui->Settings.back(); - ini->Name = ImStrdup(name); - ini->Id = ImHash(name, 0); - ini->Collapsed = false; - ini->Pos = ImVec2(FLT_MAX,FLT_MAX); - ini->Size = ImVec2(0,0); - return ini; + ImGuiContext& g = *GImGui; + g.SettingsWindows.resize(g.SettingsWindows.Size + 1); + ImGuiSettingsWindow* settings = &g.SettingsWindows.back(); + settings->Name = ImStrdup(name); + settings->Id = ImHash(name, 0); + settings->Collapsed = false; + settings->Pos = ImVec2(FLT_MAX,FLT_MAX); + settings->Size = ImVec2(0,0); + return settings; } // Zero-tolerance, poor-man .ini parsing @@ -2579,7 +2581,7 @@ static void LoadIniSettingsFromMemory(const char* buf, const char* buf_end) ImGuiContext& g = *GImGui; if (!buf_end) buf_end = buf + strlen(buf); - ImGuiIniData* settings = NULL; + ImGuiSettingsWindow* settings = NULL; for (const char* line_start = buf; line_start < buf_end; ) { const char* line_end = line_start; @@ -2639,7 +2641,7 @@ static void SaveIniSettingsToMemory(ImVector& out_buf) ImGuiWindow* window = g.Windows[i]; if (window->Flags & ImGuiWindowFlags_NoSavedSettings) continue; - ImGuiIniData* settings = FindWindowSettings(window->Name); + ImGuiSettingsWindow* settings = FindWindowSettings(window->Name); if (!settings) // This will only return NULL in the rare instance where the window was first created with ImGuiWindowFlags_NoSavedSettings then had the flag disabled later on. We don't bind settings in this case (bug #1000). continue; settings->Pos = window->Pos; @@ -2650,10 +2652,10 @@ static void SaveIniSettingsToMemory(ImVector& out_buf) // Write a buffer // If a window wasn't opened in this session we preserve its settings ImGuiTextBuffer buf; - buf.reserve(g.Settings.Size * 64); // ballpark reserve - for (int i = 0; i != g.Settings.Size; i++) + buf.reserve(g.SettingsWindows.Size * 64); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) { - const ImGuiIniData* settings = &g.Settings[i]; + const ImGuiSettingsWindow* settings = &g.SettingsWindows[i]; if (settings->Pos.x == FLT_MAX) continue; const char* name = settings->Name; @@ -4008,7 +4010,7 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl window->PosFloat = ImVec2(60, 60); window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); - ImGuiIniData* settings = FindWindowSettings(name); + ImGuiSettingsWindow* settings = FindWindowSettings(name); if (!settings) settings = AddWindowSettings(name); else diff --git a/imgui_internal.h b/imgui_internal.h index f155abf7..ed227929 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -38,7 +38,7 @@ struct ImGuiGroupData; struct ImGuiSimpleColumns; struct ImGuiDrawContext; struct ImGuiTextEditState; -struct ImGuiIniData; +struct ImGuiSettingsWindow; struct ImGuiMouseCursorData; struct ImGuiPopupRef; struct ImGuiWindow; @@ -370,7 +370,7 @@ struct IMGUI_API ImGuiTextEditState }; // Data saved in imgui.ini file -struct ImGuiIniData +struct ImGuiSettingsWindow { char* Name; ImGuiID Id; @@ -438,7 +438,7 @@ struct ImGuiContext ImGuiWindow* ActiveIdWindow; ImGuiWindow* MovingWindow; // Track the child window we clicked on to move a window. ImGuiID MovingWindowMoveId; // == MovingWindow->MoveId - ImVector Settings; // .ini Settings + ImVector SettingsWindows; // .ini Settings float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() From 7e2d0d734c477e033fd57a875705dc5ad8571687 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 27 Nov 2017 23:55:42 +0100 Subject: [PATCH 06/13] Settings: basic refactor so that additional data structures can be loaded/saved. Parser/saver is still the minimum viable poor-man parsing. --- imgui.cpp | 182 ++++++++++++++++++++++++++++++----------------- imgui_internal.h | 20 +++++- 2 files changed, 135 insertions(+), 67 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8b618988..41b3b621 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -647,7 +647,7 @@ static ImGuiSettingsWindow* FindWindowSettings(const char* name); static ImGuiSettingsWindow* AddWindowSettings(const char* name); static void LoadIniSettingsFromDisk(const char* ini_filename); -static void LoadIniSettingsFromMemory(const char* buf, const char* buf_end = NULL); +static void LoadIniSettingsFromMemory(const char* buf); static void SaveIniSettingsToDisk(const char* ini_filename); static void SaveIniSettingsToMemory(ImVector& out_buf); static void MarkIniSettingsDirty(ImGuiWindow* window); @@ -924,8 +924,16 @@ void ImStrncpy(char* dst, const char* src, int count) char* ImStrdup(const char *str) { size_t len = strlen(str) + 1; - void* buff = ImGui::MemAlloc(len); - return (char*)memcpy(buff, (const void*)str, len); + void* buf = ImGui::MemAlloc(len); + return (char*)memcpy(buf, (const void*)str, len); +} + +char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + for ( ; str < str_end; str++) + if (*str == c) + return (char*)str; + return NULL; } int ImStrlenW(const ImWchar* str) @@ -2463,12 +2471,75 @@ void ImGui::NewFrame() ImGui::Begin("Debug##Default"); } +static void* SettingsHandlerWindow_ReadOpenEntry(ImGuiContext&, const char* name) +{ + ImGuiSettingsWindow* settings = FindWindowSettings(name); + if (!settings) + settings = AddWindowSettings(name); + return (void*)settings; +} + +static void SettingsHandlerWindow_ReadLine(ImGuiContext&, void* entry, const char* line) +{ + ImGuiSettingsWindow* settings = (ImGuiSettingsWindow*)entry; + float x, y; + int i; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); + else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); +} + +static void SettingsHandlerWindow_WriteAll(ImGuiContext& g, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + ImGuiSettingsWindow* settings = FindWindowSettings(window->Name); + if (!settings) // This will only return NULL in the rare instance where the window was first created with ImGuiWindowFlags_NoSavedSettings then had the flag disabled later on. We don't bind settings in this case (bug #1000). + continue; + settings->Pos = window->Pos; + settings->Size = window->SizeFull; + settings->Collapsed = window->Collapsed; + } + + // Write a buffer + // If a window wasn't opened in this session we preserve its settings + buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) + { + const ImGuiSettingsWindow* settings = &g.SettingsWindows[i]; + if (settings->Pos.x == FLT_MAX) + continue; + const char* name = settings->Name; + if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + name = p; + buf->appendf("[Window][%s]\n", name); + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->appendf("\n"); + } +} + void ImGui::Initialize() { ImGuiContext& g = *GImGui; g.LogClipboard = (ImGuiTextBuffer*)ImGui::MemAlloc(sizeof(ImGuiTextBuffer)); IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer(); + // Add .ini handle for ImGuiWindow type + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHash("Window", 0, 0); + ini_handler.ReadOpenEntryFn = SettingsHandlerWindow_ReadOpenEntry; + ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; + ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; + g.SettingsHandlers.push_back(ini_handler); + + // Load .ini file IM_ASSERT(g.SettingsWindows.empty()); LoadIniSettingsFromDisk(g.IO.IniFilename); g.Initialized = true; @@ -2506,7 +2577,6 @@ void ImGui::Shutdown() g.MovingWindow = NULL; for (int i = 0; i < g.SettingsWindows.Size; i++) ImGui::MemFree(g.SettingsWindows[i].Name); - g.SettingsWindows.clear(); g.ColorModifiers.clear(); g.StyleModifiers.clear(); g.FontStack.clear(); @@ -2522,6 +2592,9 @@ void ImGui::Shutdown() g.InputTextState.InitialText.clear(); g.InputTextState.TempTextBuffer.clear(); + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + if (g.LogFile && g.LogFile != stdout) { fclose(g.LogFile); @@ -2562,54 +2635,64 @@ static ImGuiSettingsWindow* AddWindowSettings(const char* name) return settings; } -// Zero-tolerance, poor-man .ini parsing -// FIXME: Write something less rubbish static void LoadIniSettingsFromDisk(const char* ini_filename) { if (!ini_filename) return; - int file_size; - char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_size, 1); + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1); if (!file_data) return; - LoadIniSettingsFromMemory(file_data, file_data + file_size); + LoadIniSettingsFromMemory(file_data); ImGui::MemFree(file_data); } -static void LoadIniSettingsFromMemory(const char* buf, const char* buf_end) +// Zero-tolerance, no error reporting, cheap .ini parsing +static void LoadIniSettingsFromMemory(const char* buf_readonly) { + // 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; - if (!buf_end) - buf_end = buf + strlen(buf); - ImGuiSettingsWindow* settings = NULL; - for (const char* line_start = buf; line_start < buf_end; ) + void* entry_data = NULL; + const ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) { - const char* line_end = line_start; + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') line_end++; + line_end[0] = 0; - if (line_start[0] == '[' && line_end > line_start && line_end[-1] == ']') + if (line[0] == '[' && line_end > line && line_end[-1] == ']') { - char name[64]; - ImFormatString(name, IM_ARRAYSIZE(name), "%.*s", (int)(line_end-line_start-2), line_start+1); - settings = FindWindowSettings(name); - if (!settings) - settings = AddWindowSettings(name); + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + char* name_end = line_end - 1; + *name_end = 0; + char* type_start = line + 1; + char* type_end = ImStrchrRange(type_start, name_end, ']'); + char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (type_start && type_end && name_start++ && name_end) + { + const ImGuiID type_hash = ImHash(type_start, type_end - type_start, 0); + entry_handler = NULL; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size && entry_handler == NULL; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + entry_handler = &g.SettingsHandlers[handler_n]; + entry_data = entry_handler ? entry_handler->ReadOpenEntryFn(g, name_start) : NULL; + } } - else if (settings) + else if (entry_handler != NULL && entry_data != NULL) { - float x, y; - int i; - if (sscanf(line_start, "Pos=%f,%f", &x, &y) == 2) - settings->Pos = ImVec2(x, y); - else if (sscanf(line_start, "Size=%f,%f", &x, &y) == 2) - settings->Size = ImMax(ImVec2(x, y), g.Style.WindowMinSize); - else if (sscanf(line_start, "Collapsed=%d", &i) == 1) - settings->Collapsed = (i != 0); + // Let type handler parse the line + entry_handler->ReadLineFn(g, entry_data, line); } - - line_start = line_end+1; } + ImGui::MemFree(buf); } static void SaveIniSettingsToDisk(const char* ini_filename) @@ -2622,7 +2705,6 @@ static void SaveIniSettingsToDisk(const char* ini_filename) ImVector buf; SaveIniSettingsToMemory(buf); - // Write .ini file FILE* f = ImFileOpen(ini_filename, "wt"); if (!f) return; @@ -2635,39 +2717,11 @@ static void SaveIniSettingsToMemory(ImVector& out_buf) ImGuiContext& g = *GImGui; g.SettingsDirtyTimer = 0.0f; - // Gather data from windows that were active during this session - for (int i = 0; i != g.Windows.Size; i++) - { - ImGuiWindow* window = g.Windows[i]; - if (window->Flags & ImGuiWindowFlags_NoSavedSettings) - continue; - ImGuiSettingsWindow* settings = FindWindowSettings(window->Name); - if (!settings) // This will only return NULL in the rare instance where the window was first created with ImGuiWindowFlags_NoSavedSettings then had the flag disabled later on. We don't bind settings in this case (bug #1000). - continue; - settings->Pos = window->Pos; - settings->Size = window->SizeFull; - settings->Collapsed = window->Collapsed; - } - - // Write a buffer - // If a window wasn't opened in this session we preserve its settings ImGuiTextBuffer buf; - buf.reserve(g.SettingsWindows.Size * 64); // ballpark reserve - for (int i = 0; i != g.SettingsWindows.Size; i++) - { - const ImGuiSettingsWindow* settings = &g.SettingsWindows[i]; - if (settings->Pos.x == FLT_MAX) - continue; - const char* name = settings->Name; - if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() - name = p; - buf.appendf("[%s]\n", name); - buf.appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); - buf.appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); - buf.appendf("Collapsed=%d\n", settings->Collapsed); - buf.appendf("\n"); - } + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + g.SettingsHandlers[handler_n].WriteAllFn(g, &buf); + buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer out_buf.swap(buf.Buf); } diff --git a/imgui_internal.h b/imgui_internal.h index ed227929..e5ecb8b0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -106,6 +106,7 @@ IMGUI_API int ImStricmp(const char* str1, const char* str2); IMGUI_API int ImStrnicmp(const char* str1, const char* str2, int count); IMGUI_API void ImStrncpy(char* dst, const char* src, int count); IMGUI_API char* ImStrdup(const char* str); +IMGUI_API char* ImStrchrRange(const char* str_begin, const char* str_end, char c); IMGUI_API int ImStrlenW(const ImWchar* str); IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); @@ -379,6 +380,15 @@ struct ImGuiSettingsWindow bool Collapsed; }; +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) + void* (*ReadOpenEntryFn)(ImGuiContext& ctx, const char* name); + void (*ReadLineFn)(ImGuiContext& ctx, void* entry, const char* line); + void (*WriteAllFn)(ImGuiContext& ctx, ImGuiTextBuffer* out_buf); +}; + // Mouse cursor data (used when io.MouseDrawCursor is set) struct ImGuiMouseCursorData { @@ -438,8 +448,6 @@ struct ImGuiContext ImGuiWindow* ActiveIdWindow; ImGuiWindow* MovingWindow; // Track the child window we clicked on to move a window. ImGuiID MovingWindowMoveId; // == MovingWindow->MoveId - ImVector SettingsWindows; // .ini Settings - float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() ImVector FontStack; // Stack for PushFont()/PopFont() @@ -488,6 +496,11 @@ struct ImGuiContext ImVector PrivateClipboard; // If no custom clipboard handler is defined ImVec2 OsImePosRequest, OsImePosSet; // Cursor position request & last passed to the OS Input Method Editor + // Settings + float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero + ImVector SettingsWindows; // .ini settings for ImGuiWindow + ImVector SettingsHandlers; // List of .ini settings handlers + // Logging bool LogEnabled; FILE* LogFile; // If != NULL log to stdout/ file @@ -532,7 +545,6 @@ struct ImGuiContext ActiveIdWindow = NULL; MovingWindow = NULL; MovingWindowMoveId = 0; - SettingsDirtyTimer = 0.0f; SetNextWindowPosVal = ImVec2(0.0f, 0.0f); SetNextWindowSizeVal = ImVec2(0.0f, 0.0f); @@ -565,6 +577,8 @@ struct ImGuiContext MouseCursor = ImGuiMouseCursor_Arrow; memset(MouseCursorData, 0, sizeof(MouseCursorData)); + SettingsDirtyTimer = 0.0f; + LogEnabled = false; LogFile = NULL; LogClipboard = NULL; From e000ee01169f735b0ddc91553b42ff3a857e9b9b Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 10:48:01 +0100 Subject: [PATCH 07/13] Examples: DirectX9: Handle loss of D3D9 device (D3DERR_DEVICELOST). (#1464) --- examples/directx9_example/main.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index 5a5c0d19..30c820ef 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -154,7 +154,15 @@ int main(int, char**) ImGui::Render(); g_pd3dDevice->EndScene(); } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + HRESULT result = g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + + // Handle loss of D3D9 device + if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET) + { + ImGui_ImplDX9_InvalidateDeviceObjects(); + g_pd3dDevice->Reset(&g_d3dpp); + ImGui_ImplDX9_CreateDeviceObjects(); + } } ImGui_ImplDX9_Shutdown(); From f3ec608c19462e4829ca2bf5833511290ea770b0 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 00:32:25 +0100 Subject: [PATCH 08/13] Settings: Initializing ImGuiSettingsWindow so external users don't end up with uncleared data. Exposed MarkIniSettingsDirty() in imgui_internal.h --- imgui.cpp | 7 +++++++ imgui_internal.h | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 41b3b621..72dda932 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2725,6 +2725,13 @@ static void SaveIniSettingsToMemory(ImVector& out_buf) out_buf.swap(buf.Buf); } +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + static void MarkIniSettingsDirty(ImGuiWindow* window) { ImGuiContext& g = *GImGui; diff --git a/imgui_internal.h b/imgui_internal.h index e5ecb8b0..249dff85 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -378,6 +378,8 @@ struct ImGuiSettingsWindow ImVec2 Pos; ImVec2 Size; bool Collapsed; + + ImGuiSettingsWindow() { Name = NULL; Id = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } }; struct ImGuiSettingsHandler @@ -801,6 +803,8 @@ namespace ImGui IMGUI_API void Initialize(); + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); IMGUI_API void ClearActiveID(); IMGUI_API void SetHoveredID(ImGuiID id); From 4c4f1b0224adcfbd35e6d83577ba12f95067f5ab Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 00:33:03 +0100 Subject: [PATCH 09/13] ImVector: Added ImVector::push_front helper. --- imgui.h | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui.h b/imgui.h index 309730f2..f0920910 100644 --- a/imgui.h +++ b/imgui.h @@ -983,6 +983,7 @@ public: inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size+1)); Data[Size++] = v; } inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const value_type& v) { if (Size == 0) push_back(v); else insert(Data, v); } inline iterator erase(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(Capacity ? Capacity * 2 : 4); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } From e5ebe42207225d5f6e2165f90bbae79b283994c0 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 00:33:12 +0100 Subject: [PATCH 10/13] Settings: Make ImGuiWindow settings always first in the list. --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 72dda932..0ecbedb6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2537,7 +2537,7 @@ void ImGui::Initialize() ini_handler.ReadOpenEntryFn = SettingsHandlerWindow_ReadOpenEntry; ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; - g.SettingsHandlers.push_back(ini_handler); + g.SettingsHandlers.push_front(ini_handler); // Load .ini file IM_ASSERT(g.SettingsWindows.empty()); From c8b5b569dacd25abc45cda1bf70bff53b15ac945 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 11:26:14 +0100 Subject: [PATCH 11/13] Examples: DirectX9: Call EndFrame(), fix for assert added in 9a44d447cd29096c74e38bec917015c0ee1ffaea --- examples/directx9_example/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index 30c820ef..af53ddb3 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -144,6 +144,7 @@ int main(int, char**) } // Rendering + ImGui::EndFrame(); g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); From 1a8a7c9d170a49e9c3170df59674eab74810bab6 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 16:23:46 +0100 Subject: [PATCH 12/13] Settings: Import old style .ini file --- imgui.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 0ecbedb6..60b1d1c5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2676,15 +2676,22 @@ static void LoadIniSettingsFromMemory(const char* buf_readonly) char* type_start = line + 1; char* type_end = ImStrchrRange(type_start, name_end, ']'); char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; - if (type_start && type_end && name_start++ && name_end) + if (!type_end || !name_start) { - const ImGuiID type_hash = ImHash(type_start, type_end - type_start, 0); - entry_handler = NULL; - for (int handler_n = 0; handler_n < g.SettingsHandlers.Size && entry_handler == NULL; handler_n++) - if (g.SettingsHandlers[handler_n].TypeHash == type_hash) - entry_handler = &g.SettingsHandlers[handler_n]; - entry_data = entry_handler ? entry_handler->ReadOpenEntryFn(g, name_start) : NULL; + name_start = type_start; // Import legacy entries that have no type + type_start = "Window"; } + else + { + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + } + const ImGuiID type_hash = ImHash(type_start, 0, 0); + entry_handler = NULL; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size && entry_handler == NULL; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + entry_handler = &g.SettingsHandlers[handler_n]; + entry_data = entry_handler ? entry_handler->ReadOpenEntryFn(g, name_start) : NULL; } else if (entry_handler != NULL && entry_data != NULL) { From 4a43632163b49744455e04ca9654e045708dccab Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Nov 2017 17:27:54 +0100 Subject: [PATCH 13/13] Build fix --- imgui.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 60b1d1c5..91de2d97 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2671,11 +2671,11 @@ static void LoadIniSettingsFromMemory(const char* buf_readonly) if (line[0] == '[' && line_end > line && line_end[-1] == ']') { // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. - char* name_end = line_end - 1; - *name_end = 0; - char* type_start = line + 1; + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; char* type_end = ImStrchrRange(type_start, name_end, ']'); - char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; if (!type_end || !name_start) { name_start = type_start; // Import legacy entries that have no type