From bb0511ac7b7b87a60b3fb40b82a87c4c311091d3 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Sat, 25 May 2024 06:30:52 +0200 Subject: [PATCH] sys_imgui.cpp: Move code to read/write style to imgui_savestyle.cpp it's nicely self-contained, I'll release it as a mini-library/addon for Dear ImGui on https://github.com/DanielGibson/Snippets/ --- neo/CMakeLists.txt | 1 + neo/sys/imgui_savestyle.cpp | 495 ++++++++++++++++++++++++++++++++++++ neo/sys/sys_imgui.cpp | 404 ++--------------------------- 3 files changed, 513 insertions(+), 387 deletions(-) create mode 100644 neo/sys/imgui_savestyle.cpp diff --git a/neo/CMakeLists.txt b/neo/CMakeLists.txt index 7a46053d..bec62303 100644 --- a/neo/CMakeLists.txt +++ b/neo/CMakeLists.txt @@ -771,6 +771,7 @@ set(src_imgui sys/sys_imgui.h sys/sys_imgui.cpp + sys/imgui_savestyle.cpp ) diff --git a/neo/sys/imgui_savestyle.cpp b/neo/sys/imgui_savestyle.cpp new file mode 100644 index 00000000..cddcaacf --- /dev/null +++ b/neo/sys/imgui_savestyle.cpp @@ -0,0 +1,495 @@ +/* + * Functions to read/write a Dear ImGui style (ImGuiStyle) from/to a (ini-like) textfile. + * + * Hosted at https://github.com/DanielGibson/Snippets/ + * + * Written for/tested with Dear ImGui v1.90.6 + * + * If anything changes in struct ImGuiStyle or enum ImGuiCol_, this code should detect it + * during compilation and give (hopefully) helpful errors with static_assert() (always check and + * fix the first error that occurs first, the remaining ones might be caused by the first). + * + * Released under the same license as Dear ImGui: + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Daniel Gibson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "imgui.h" +#include + +#if 0 // this is basically the header, just copy it to wherever you want to use these functions +namespace DG { + // writes the given ImGuiStyle to the given filename (opened with fopen()) + // returns true on success, false if opening the file failed + extern bool WriteImGuiStyle( const ImGuiStyle& style, const char* filename ); + + // reads the the given filename (opened with fopen()) + // and sets the given ImGuiStyle accordingly. + // if any attributes/colors/behaviors are missing the the file, + // they are not modified in style, so it probably makes sense to initialize + // style to a sane default before calling that function. + // returns true on success, false if opening the file failed + extern bool ReadImGuiStyle( ImGuiStyle& style, const char* filename ); +} //namespace +#endif + +// if you want to use another namespace than DG, modify this #define +// (either here or through a compiler argumenet like -DDG_IMSAVESTYLE_NAMESPACE=horst) +#ifndef DG_IMSAVESTYLE_NAMESPACE + #define DG_IMSAVESTYLE_NAMESPACE DG +#endif + +// #define your own DG_IMSAVESTYLE_FOPEN if you want to use an alternative to +// standard fopen(), for example one that supports UTF-8 filenames on Windows +#ifndef DG_IMSAVESTYLE_FOPEN + #define DG_IMSAVESTYLE_FOPEN fopen +#endif + +#undef warnPrintf +// TODO: maybe use your own logging system instead of fprintf() to stderr +#define warnPrintf(...) fprintf( stderr, "Warning: " __VA_ARGS__ ) + +// Note: The trick I'm using with these #defines below is called "X Macro" +// see https://en.wikipedia.org/wiki/X_macro (except I'm not calling the "entries" X, +// and have more then one kind of entry per table) +// If you're wondering about the D3_ prefix on the macros, I originally wrote this for dhewm3. + +// this table contains all members of struct ImGuiStyle that come before the Colors array +// when updating Dear ImGui, this might have to be adjusted for members that have been added/removed/renamed +#define D3_IMSTYLE_ATTRS \ + D3_IMATTR_FLOAT( Alpha ) \ + D3_IMATTR_FLOAT( DisabledAlpha ) \ + D3_IMATTR_VEC2( WindowPadding ) \ + D3_IMATTR_FLOAT( WindowRounding ) \ + D3_IMATTR_FLOAT( WindowBorderSize ) \ + D3_IMATTR_VEC2( WindowMinSize ) \ + D3_IMATTR_VEC2( WindowTitleAlign ) \ + D3_IMATTR_INT( WindowMenuButtonPosition ) \ + D3_IMATTR_FLOAT( ChildRounding ) \ + D3_IMATTR_FLOAT( ChildBorderSize ) \ + D3_IMATTR_FLOAT( PopupRounding ) \ + D3_IMATTR_FLOAT( PopupBorderSize ) \ + D3_IMATTR_VEC2( FramePadding ) \ + D3_IMATTR_FLOAT( FrameRounding ) \ + D3_IMATTR_FLOAT( FrameBorderSize ) \ + D3_IMATTR_VEC2( ItemSpacing ) \ + D3_IMATTR_VEC2( ItemInnerSpacing ) \ + D3_IMATTR_VEC2( CellPadding ) \ + D3_IMATTR_VEC2( TouchExtraPadding ) \ + D3_IMATTR_FLOAT( IndentSpacing ) \ + D3_IMATTR_FLOAT( ColumnsMinSpacing ) \ + D3_IMATTR_FLOAT( ScrollbarSize ) \ + D3_IMATTR_FLOAT( ScrollbarRounding ) \ + D3_IMATTR_FLOAT( GrabMinSize ) \ + D3_IMATTR_FLOAT( GrabRounding ) \ + D3_IMATTR_FLOAT( LogSliderDeadzone ) \ + D3_IMATTR_FLOAT( TabRounding ) \ + D3_IMATTR_FLOAT( TabBorderSize ) \ + D3_IMATTR_FLOAT( TabMinWidthForCloseButton ) \ + D3_IMATTR_FLOAT( TabBarBorderSize ) \ + D3_IMATTR_FLOAT( TableAngledHeadersAngle ) \ + D3_IMATTR_VEC2( TableAngledHeadersTextAlign ) \ + D3_IMATTR_INT( ColorButtonPosition ) \ + D3_IMATTR_VEC2( ButtonTextAlign ) \ + D3_IMATTR_VEC2( SelectableTextAlign ) \ + D3_IMATTR_FLOAT( SeparatorTextBorderSize ) \ + D3_IMATTR_VEC2( SeparatorTextAlign ) \ + D3_IMATTR_VEC2( SeparatorTextPadding ) \ + D3_IMATTR_VEC2( DisplayWindowPadding ) \ + D3_IMATTR_VEC2( DisplaySafeAreaPadding ) \ + D3_IMATTR_FLOAT( MouseCursorScale ) \ + D3_IMATTR_BOOL( AntiAliasedLines ) \ + D3_IMATTR_BOOL( AntiAliasedLinesUseTex ) \ + D3_IMATTR_BOOL( AntiAliasedFill ) \ + D3_IMATTR_FLOAT( CurveTessellationTol ) \ + D3_IMATTR_FLOAT( CircleTessellationMaxError ) + +// this table contains the members of struct ImGuiStyle that come after the Colors array +// when updating Dear ImGui, this might have to be adjusted for members that have been added/removed/renamed +#define D3_IMSTYLE_BEHAVIORS \ + D3_IMATTR_FLOAT( HoverStationaryDelay ) \ + D3_IMATTR_FLOAT( HoverDelayShort ) \ + D3_IMATTR_FLOAT( HoverDelayNormal ) \ + D3_IMATTR_INT( HoverFlagsForTooltipMouse ) \ + D3_IMATTR_INT( HoverFlagsForTooltipNav ) + +// this table contains one entry for every value of enum ImGuiCol_ (except for ImGuiCol_COUNT) +// as you can see, the "ImGuiCol_" prefix is left out in this table, +// it's added programatically when needed +// when updating Dear ImGui, this might have to be adjusted for enum members that have been added/removed/renamed +#define D3_IMSTYLE_COLORS \ + D3_IMSTYLE_COLOR( Text ) \ + D3_IMSTYLE_COLOR( TextDisabled ) \ + D3_IMSTYLE_COLOR( WindowBg ) \ + D3_IMSTYLE_COLOR( ChildBg ) \ + D3_IMSTYLE_COLOR( PopupBg ) \ + D3_IMSTYLE_COLOR( Border ) \ + D3_IMSTYLE_COLOR( BorderShadow ) \ + D3_IMSTYLE_COLOR( FrameBg ) \ + D3_IMSTYLE_COLOR( FrameBgHovered ) \ + D3_IMSTYLE_COLOR( FrameBgActive ) \ + D3_IMSTYLE_COLOR( TitleBg ) \ + D3_IMSTYLE_COLOR( TitleBgActive ) \ + D3_IMSTYLE_COLOR( TitleBgCollapsed ) \ + D3_IMSTYLE_COLOR( MenuBarBg ) \ + D3_IMSTYLE_COLOR( ScrollbarBg ) \ + D3_IMSTYLE_COLOR( ScrollbarGrab ) \ + D3_IMSTYLE_COLOR( ScrollbarGrabHovered ) \ + D3_IMSTYLE_COLOR( ScrollbarGrabActive ) \ + D3_IMSTYLE_COLOR( CheckMark ) \ + D3_IMSTYLE_COLOR( SliderGrab ) \ + D3_IMSTYLE_COLOR( SliderGrabActive ) \ + D3_IMSTYLE_COLOR( Button ) \ + D3_IMSTYLE_COLOR( ButtonHovered ) \ + D3_IMSTYLE_COLOR( ButtonActive ) \ + D3_IMSTYLE_COLOR( Header ) \ + D3_IMSTYLE_COLOR( HeaderHovered ) \ + D3_IMSTYLE_COLOR( HeaderActive ) \ + D3_IMSTYLE_COLOR( Separator ) \ + D3_IMSTYLE_COLOR( SeparatorHovered ) \ + D3_IMSTYLE_COLOR( SeparatorActive ) \ + D3_IMSTYLE_COLOR( ResizeGrip ) \ + D3_IMSTYLE_COLOR( ResizeGripHovered ) \ + D3_IMSTYLE_COLOR( ResizeGripActive ) \ + D3_IMSTYLE_COLOR( Tab ) \ + D3_IMSTYLE_COLOR( TabHovered ) \ + D3_IMSTYLE_COLOR( TabActive ) \ + D3_IMSTYLE_COLOR( TabUnfocused ) \ + D3_IMSTYLE_COLOR( TabUnfocusedActive ) \ + D3_IMSTYLE_COLOR( PlotLines ) \ + D3_IMSTYLE_COLOR( PlotLinesHovered ) \ + D3_IMSTYLE_COLOR( PlotHistogram ) \ + D3_IMSTYLE_COLOR( PlotHistogramHovered ) \ + D3_IMSTYLE_COLOR( TableHeaderBg ) \ + D3_IMSTYLE_COLOR( TableBorderStrong ) \ + D3_IMSTYLE_COLOR( TableBorderLight ) \ + D3_IMSTYLE_COLOR( TableRowBg ) \ + D3_IMSTYLE_COLOR( TableRowBgAlt ) \ + D3_IMSTYLE_COLOR( TextSelectedBg ) \ + D3_IMSTYLE_COLOR( DragDropTarget ) \ + D3_IMSTYLE_COLOR( NavHighlight ) \ + D3_IMSTYLE_COLOR( NavWindowingHighlight ) \ + D3_IMSTYLE_COLOR( NavWindowingDimBg ) \ + D3_IMSTYLE_COLOR( ModalWindowDimBg ) + +static inline char* skipWhitespace( const char* s ) { + while( *s == ' ' || *s == '\t' ) + ++s; + return (char*)s; +} + +#define D3_IMATTR_FLOAT( NAME ) \ + if ( sscanf( line, #NAME " = %f", &f1 ) == 1 ) { \ + s . NAME = f1; \ + return; \ + } + +#define D3_IMATTR_VEC2( NAME ) \ + if ( sscanf( line, #NAME " = %f , %f", &f1, &f2 ) == 2 ) { \ + s . NAME .x = f1; s . NAME .y = f2; \ + return; \ + } + +#define D3_IMATTR_INT( NAME ) \ + if ( sscanf( line, #NAME " = %d", &i ) == 1 ) { \ + s . NAME = i; \ + return; \ + } + +#define D3_IMATTR_BOOL( NAME ) \ + if ( sscanf( line, #NAME " = %d", &i ) == 1 ) { \ + s . NAME = ( i != 0 ); \ + return; \ + } + + +static void parseStyleLine( ImGuiStyle& s, const char* line ) +{ + float f1=0, f2=0; + int i=0; + + // with the D3_IMATTR_* #defines above, the following + // `D3_IMSTYLE_ATTRS` line turns into: + // if ( sscanf( line, "Alpha = %f", &f1 ) == 1 ) { + // s . Alpha = f1; + // return; + // } + // if ( sscanf( line, "DisabledAlpha = %f", &f1 ) == 1 ) { + // s . DisabledAlpha = f1; + // return; + // } + // if ( sscanf( line, "WindowPadding = %f , %f", &f1, &f2 ) == 2 ) { + // s . WindowPadding .x = f1; s . WindowPadding .y = f2; + // return; + // } + // etc + + D3_IMSTYLE_ATTRS + + // NOTE: if a member is renamed, for backwards-compatibility you could add code here like + // if ( sscanf( line, "OldName = %f", %f1 ) == 1 ) { + // s.NewName = f; + // return; + // } + + warnPrintf( "Invalid line in ImGui style under [style] section: '%s'\n", line ); +} + +static void parseBehaviorLine( ImGuiStyle& s, const char* line ) +{ + float f1=0, f2=0; + (void)f2; // currently unused in behavior section + int i=0; + + D3_IMSTYLE_BEHAVIORS + + // NOTE: same thing for backwards-compatbility as in parseStyleLine applies here + + warnPrintf( "Invalid line in ImGui style under [behaviors] section: '%s'\n", line ); +} + +#undef D3_IMATTR_FLOAT +#undef D3_IMATTR_VEC2 +#undef D3_IMATTR_INT +#undef D3_IMATTR_BOOL + +static void parseColorLine( ImGuiStyle& s, const char* line ) +{ + ImVec4 c; + +#define D3_IMSTYLE_COLOR( NAME ) \ + if ( sscanf( line, #NAME " = %f , %f , %f , %f", &c.x, &c.y, &c.z, &c.w) == 4 ) { \ + s.Colors[ ImGuiCol_ ## NAME ] = c; \ + return; \ + } + + D3_IMSTYLE_COLORS + +#undef D3_IMSTYLE_COLOR + + // NOTE: here backwards-compat is also possible, like + // if ( sscanf( line, "OldColorName = %f , %f , %f , %f", &c.x, &c.y, &c.z, &c.w) == 4 ) { + // s.Colors[ ImGuiCol_NewColorName = c; + // return; + // } + + warnPrintf( "Invalid line in ImGui style under [colors] section: '%s'\n", line ); +} + +namespace DG_IMSAVESTYLE_NAMESPACE { + +bool ReadImGuiStyle( ImGuiStyle& s, const char* filename ) +{ + FILE* f = DG_IMSAVESTYLE_FOPEN( filename, "r" ); // TODO: "rt" on Windows? + if ( f == nullptr ) { + warnPrintf( "Couldn't open '%s' for reading\n", filename ); + return false; + } + + char lineBuf[256]; + + int section = -1; // 0: style, 1: behaviors, 2: colors + + for ( char* line = fgets( lineBuf, sizeof(lineBuf), f ); + line != nullptr; + line = fgets( lineBuf, sizeof(lineBuf), f ) ) + { + // skip whitespace, if any + line = skipWhitespace(line); + if ( *line == '#' ) // skip comment lines + continue; + if ( *line == '[' ) { // start of a section + const char* secStr = line+1; // skip '[' + secStr = skipWhitespace(secStr); + // "[style]" "[behaviors]" "[colors]" + if ( strncmp(secStr, "style", 5) == 0 ) { + section = 0; + } else if ( strncmp(secStr, "behaviors", 9) == 0 ) { + section = 1; + } else if ( strncmp(secStr, "colors", 6) == 0 ) { + section = 2; + } else { + warnPrintf( "Invalid line that looks like a section in ImGui style: '%s'\n", line ); + } + continue; + } + if ( *line == '\r' || *line == '\n' ) { + continue; // empty line + } + if ( section == 0 ) { + parseStyleLine( s, line ); + } else if ( section == 1 ) { + parseBehaviorLine( s, line ); + } else if ( section == 2 ) { + parseColorLine( s, line ); + } else { + warnPrintf( "Invalid line in ImGui before start of any section: '%s'\n", line ); + } + } + + return true; +} + +bool WriteImGuiStyle( const ImGuiStyle& s, const char* filename ) { + FILE* f = DG_IMSAVESTYLE_FOPEN( filename, "w" ); // TODO: "wt" on Windows? + if ( f == nullptr ) { + warnPrintf( "Couldn't open '%s' for writing\n", filename ); + return false; + } + + fprintf( f, "[style]\n" ); + +#define D3_IMATTR_FLOAT( NAME ) \ + fprintf( f, #NAME " = %g\n", s . NAME ); +#define D3_IMATTR_VEC2( NAME ) \ + fprintf( f, #NAME " = %g, %g\n", s . NAME . x, s . NAME . y ); +#define D3_IMATTR_INT( NAME ) \ + fprintf( f, #NAME " = %d\n", s . NAME ); +#define D3_IMATTR_BOOL( NAME ) \ + fprintf( f, #NAME " = %d\n", (int) ( s . NAME ) ); + + // this (together with the D3_IMATTR_* defines in the previous lines) + // expands the D3_IMSTYLE_ATTRS table to + // fprintf( f, "Alpha = %g\n", s . Alpha ); + // fprintf( f, "DisabledAlpha = %g\n", s . DisabledAlpha ); + // fprintf( f, "WindowPadding = %g %g\n", s . WindowPadding . x, s . WindowPadding . y ); + // etc + D3_IMSTYLE_ATTRS + + fprintf( f, "\n[behaviors]\n" ); + + // same for behaviors + D3_IMSTYLE_BEHAVIORS + +#undef D3_IMATTR_FLOAT +#undef D3_IMATTR_VEC2 +#undef D3_IMATTR_INT +#undef D3_IMATTR_BOOL + + fprintf( f, "\n[colors]\n" ); + +#define D3_IMSTYLE_COLOR( NAME ) { \ + const ImVec4& c = s.Colors[ ImGuiCol_ ## NAME ]; \ + fprintf( f, #NAME " = %g, %g, %g, %g\n", c.x, c.y, c.z, c.w ); \ + } + + // this turns into + // { const ImVec4& c = s.Colors[ ImGuiCol_Text ]; fprintf( f, "Text = %g %g %g %g\n", c.x, c.y, c.z, c.w ); } + // { const ImVec4& c = s.Colors[ ImGuiCol_TextDisabled ]; fprintf( f, "TextDisabled = %g %g %g %g\n", c.x, c.y, c.z, c.w ); } + // etc + D3_IMSTYLE_COLORS + +#undef D3_IMSTYLE_COLOR + + fprintf( f, "\n" ); + fflush( f ); + fclose( f ); + + return true; +} + +} //namespace DG + +// check correctness of the X macro tables above (detect when something is added/removed/renamed in struct ImGuiStyle or enum ImGuiCol_) +namespace { + +#define D3_IMSTYLE_COLOR(C) + 1 +// "0 D3_IMSTYLE_COLORS" is expanded to 0 + 1 + 1 ... for each D3_IMSTYLE_COLOR entry +// => it should have the same value as the ImGuiCol_COUNT constant +static_assert( ImGuiCol_COUNT == 0 D3_IMSTYLE_COLORS, + "something was added or removed in enum ImGuiCol_ => adjust D3_IMSTYLE_COLORS table above" ); +#undef D3_IMSTYLE_COLOR + +// recreate struct ImGuiStyle from the tables above and see if they're identical +// (this struct is only used for the static assertions below) +struct D3_ImGuiStyle_Check { + +#define D3_IMATTR_FLOAT( NAME ) float NAME ; +#define D3_IMATTR_VEC2( NAME ) ImVec2 NAME ; +#define D3_IMATTR_INT( NAME ) int NAME ; +#define D3_IMATTR_BOOL( NAME ) bool NAME ; + + // this expands to all the ImGuiStyle members, up to (excluding) Colors + // exactly like in ImGuiStyle (except the pseudo-enums like ImGuiDir are plain ints here) + D3_IMSTYLE_ATTRS + + ImVec4 Colors[ImGuiCol_COUNT]; + + // just like the other members/attributes, expand the behaviors + D3_IMSTYLE_BEHAVIORS + +#undef D3_IMATTR_FLOAT +#undef D3_IMATTR_VEC2 +#undef D3_IMATTR_INT +#undef D3_IMATTR_BOOL + +}; + +template +struct is_same { + static constexpr bool value = false; +}; + +template +struct is_same { + static constexpr bool value = true; +}; + +#define D3_IMATTR_FLOAT( NAME ) \ + static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ + "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ + static_assert( is_same< decltype( ImGuiStyle :: NAME ), float >::value, "expected member " #NAME "to be a float - adjust the list!" ); + +#define D3_IMATTR_VEC2( NAME ) \ + static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ + "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ + static_assert( is_same< decltype( ImGuiStyle :: NAME ), ImVec2 >::value, "expected member " #NAME " to be an ImVec2 - adjust the list!" ); + +#define D3_IMATTR_INT( NAME ) \ + static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ + "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ + static_assert( is_same< decltype( ImGuiStyle :: NAME ), int >::value, "expected member " #NAME " to be an int - adjust the list!" ); + +#define D3_IMATTR_BOOL( NAME ) \ + static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ + "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ + static_assert( is_same< decltype( ImGuiStyle :: NAME ), bool >::value, "expected member " #NAME " to be a bool - adjust the list!" ); + +// expanding those static assertions for offset and type for all attributes and behaviors + +D3_IMSTYLE_ATTRS + +D3_IMSTYLE_BEHAVIORS + +#undef D3_IMATTR_FLOAT +#undef D3_IMATTR_VEC2 +#undef D3_IMATTR_INT +#undef D3_IMATTR_BOOL + +static_assert( offsetof( ImGuiStyle, Colors ) == offsetof( D3_ImGuiStyle_Check, Colors ), "member Colors not at expected offset" ); + +// if all other static assertions passed and the following failed, probably a member was added at the end of the ImGuiStyle struct +static_assert( sizeof(D3_ImGuiStyle_Check) == sizeof(ImGuiStyle), + "something seems to be missing or wrong type in D3_IMSTYLE_ATTRS or D3_IMSTYLE_BEHAVIORS" ); + +} //anon namespace diff --git a/neo/sys/sys_imgui.cpp b/neo/sys/sys_imgui.cpp index a4fc362c..f6088101 100644 --- a/neo/sys/sys_imgui.cpp +++ b/neo/sys/sys_imgui.cpp @@ -26,6 +26,21 @@ static idCVar imgui_scale( "imgui_scale", "-1.0", CVAR_SYSTEM|CVAR_FLOAT|CVAR_AR idCVar imgui_style( "imgui_style", "0", CVAR_SYSTEM|CVAR_INTEGER|CVAR_ARCHIVE, "Which ImGui style to use. 0: Dhewm3 theme, 1: Default ImGui theme, 2: User theme", 0.0f, 2.0f ); +// implemented in imgui_savestyle.cpp +namespace DG { + // writes the given ImGuiStyle to the given filename (opened with fopen()) + // returns true on success, false if opening the file failed + extern bool WriteImGuiStyle( const ImGuiStyle& style, const char* filename ); + + // reads the the given filename (opened with fopen()) + // and sets the given ImGuiStyle accordingly. + // if any attributes/colors/behaviors are missing the the file, + // they are not modified in style, so it probably makes sense to initialize + // style to a sane default before calling that function. + // returns true on success, false if opening the file failed + extern bool ReadImGuiStyle( ImGuiStyle& style, const char* filename ); +} //namespace DG + namespace D3 { namespace ImGuiHooks { @@ -46,9 +61,6 @@ static idStr warningOverlayText; static double warningOverlayStartTime = -100.0; static ImVec2 warningOverlayStartPos; -bool WriteStyle( const ImGuiStyle& s, const char* filename ); -bool ReadStyle( ImGuiStyle& s, const char* filename ); - idStr GetUserStyleFilename() { // TODO: put this into the config dir @@ -211,7 +223,7 @@ bool Init(void* _sdlWindow, void* sdlGlContext) SetImGuiStyle( Style::Dhewm3 ); userStyle = ImGui::GetStyle(); // set dhewm3 style as default, in case the user style is missing values - if ( ReadStyle( userStyle, GetUserStyleFilename() ) && imgui_style.GetInteger() == 2 ) { + if ( DG::ReadImGuiStyle( userStyle, GetUserStyleFilename() ) && imgui_style.GetInteger() == 2 ) { ImGui::GetStyle() = userStyle; } else if ( imgui_style.GetInteger() == 1 ) { ImGui::GetStyle() = ImGuiStyle(); @@ -503,393 +515,11 @@ void SetUserStyleColors() bool WriteUserStyle() { userStyle = ImGui::GetStyle(); - if( !WriteStyle( ImGui::GetStyle(), GetUserStyleFilename() ) ) { + if( !DG::WriteImGuiStyle( ImGui::GetStyle(), GetUserStyleFilename() ) ) { common->Warning( "Couldn't write ImGui userstyle!\n" ); return false; } return true; } -// Note: The trick I'm using with these #defines below is called "X Macro" -// see https://en.wikipedia.org/wiki/X_macro (except I'm not calling the "entries" X) - -#define D3_IMSTYLE_ATTRS \ - D3_IMATTR_FLOAT( Alpha ) \ - D3_IMATTR_FLOAT( DisabledAlpha ) \ - D3_IMATTR_VEC2( WindowPadding ) \ - D3_IMATTR_FLOAT( WindowRounding ) \ - D3_IMATTR_FLOAT( WindowBorderSize ) \ - D3_IMATTR_VEC2( WindowMinSize ) \ - D3_IMATTR_VEC2( WindowTitleAlign ) \ - D3_IMATTR_INT( WindowMenuButtonPosition ) \ - D3_IMATTR_FLOAT( ChildRounding ) \ - D3_IMATTR_FLOAT( ChildBorderSize ) \ - D3_IMATTR_FLOAT( PopupRounding ) \ - D3_IMATTR_FLOAT( PopupBorderSize ) \ - D3_IMATTR_VEC2( FramePadding ) \ - D3_IMATTR_FLOAT( FrameRounding ) \ - D3_IMATTR_FLOAT( FrameBorderSize ) \ - D3_IMATTR_VEC2( ItemSpacing ) \ - D3_IMATTR_VEC2( ItemInnerSpacing ) \ - D3_IMATTR_VEC2( CellPadding ) \ - D3_IMATTR_VEC2( TouchExtraPadding ) \ - D3_IMATTR_FLOAT( IndentSpacing ) \ - D3_IMATTR_FLOAT( ColumnsMinSpacing ) \ - D3_IMATTR_FLOAT( ScrollbarSize ) \ - D3_IMATTR_FLOAT( ScrollbarRounding ) \ - D3_IMATTR_FLOAT( GrabMinSize ) \ - D3_IMATTR_FLOAT( GrabRounding ) \ - D3_IMATTR_FLOAT( LogSliderDeadzone ) \ - D3_IMATTR_FLOAT( TabRounding ) \ - D3_IMATTR_FLOAT( TabBorderSize ) \ - D3_IMATTR_FLOAT( TabMinWidthForCloseButton ) \ - D3_IMATTR_FLOAT( TabBarBorderSize ) \ - D3_IMATTR_FLOAT( TableAngledHeadersAngle ) \ - D3_IMATTR_VEC2( TableAngledHeadersTextAlign ) \ - D3_IMATTR_INT( ColorButtonPosition ) \ - D3_IMATTR_VEC2( ButtonTextAlign ) \ - D3_IMATTR_VEC2( SelectableTextAlign ) \ - D3_IMATTR_FLOAT( SeparatorTextBorderSize ) \ - D3_IMATTR_VEC2( SeparatorTextAlign ) \ - D3_IMATTR_VEC2( SeparatorTextPadding ) \ - D3_IMATTR_VEC2( DisplayWindowPadding ) \ - D3_IMATTR_VEC2( DisplaySafeAreaPadding ) \ - D3_IMATTR_FLOAT( MouseCursorScale ) \ - D3_IMATTR_BOOL( AntiAliasedLines ) \ - D3_IMATTR_BOOL( AntiAliasedLinesUseTex ) \ - D3_IMATTR_BOOL( AntiAliasedFill ) \ - D3_IMATTR_FLOAT( CurveTessellationTol ) \ - D3_IMATTR_FLOAT( CircleTessellationMaxError ) - -#define D3_IMSTYLE_BEHAVIORS \ - D3_IMATTR_FLOAT( HoverStationaryDelay ) \ - D3_IMATTR_FLOAT( HoverDelayShort ) \ - D3_IMATTR_FLOAT( HoverDelayNormal ) \ - D3_IMATTR_INT( HoverFlagsForTooltipMouse ) \ - D3_IMATTR_INT( HoverFlagsForTooltipNav ) - -#define D3_IMSTYLE_COLORS \ - D3_IMSTYLE_COLOR( Text ) \ - D3_IMSTYLE_COLOR( TextDisabled ) \ - D3_IMSTYLE_COLOR( WindowBg ) \ - D3_IMSTYLE_COLOR( ChildBg ) \ - D3_IMSTYLE_COLOR( PopupBg ) \ - D3_IMSTYLE_COLOR( Border ) \ - D3_IMSTYLE_COLOR( BorderShadow ) \ - D3_IMSTYLE_COLOR( FrameBg ) \ - D3_IMSTYLE_COLOR( FrameBgHovered ) \ - D3_IMSTYLE_COLOR( FrameBgActive ) \ - D3_IMSTYLE_COLOR( TitleBg ) \ - D3_IMSTYLE_COLOR( TitleBgActive ) \ - D3_IMSTYLE_COLOR( TitleBgCollapsed ) \ - D3_IMSTYLE_COLOR( MenuBarBg ) \ - D3_IMSTYLE_COLOR( ScrollbarBg ) \ - D3_IMSTYLE_COLOR( ScrollbarGrab ) \ - D3_IMSTYLE_COLOR( ScrollbarGrabHovered ) \ - D3_IMSTYLE_COLOR( ScrollbarGrabActive ) \ - D3_IMSTYLE_COLOR( CheckMark ) \ - D3_IMSTYLE_COLOR( SliderGrab ) \ - D3_IMSTYLE_COLOR( SliderGrabActive ) \ - D3_IMSTYLE_COLOR( Button ) \ - D3_IMSTYLE_COLOR( ButtonHovered ) \ - D3_IMSTYLE_COLOR( ButtonActive ) \ - D3_IMSTYLE_COLOR( Header ) \ - D3_IMSTYLE_COLOR( HeaderHovered ) \ - D3_IMSTYLE_COLOR( HeaderActive ) \ - D3_IMSTYLE_COLOR( Separator ) \ - D3_IMSTYLE_COLOR( SeparatorHovered ) \ - D3_IMSTYLE_COLOR( SeparatorActive ) \ - D3_IMSTYLE_COLOR( ResizeGrip ) \ - D3_IMSTYLE_COLOR( ResizeGripHovered ) \ - D3_IMSTYLE_COLOR( ResizeGripActive ) \ - D3_IMSTYLE_COLOR( Tab ) \ - D3_IMSTYLE_COLOR( TabHovered ) \ - D3_IMSTYLE_COLOR( TabActive ) \ - D3_IMSTYLE_COLOR( TabUnfocused ) \ - D3_IMSTYLE_COLOR( TabUnfocusedActive ) \ - D3_IMSTYLE_COLOR( PlotLines ) \ - D3_IMSTYLE_COLOR( PlotLinesHovered ) \ - D3_IMSTYLE_COLOR( PlotHistogram ) \ - D3_IMSTYLE_COLOR( PlotHistogramHovered ) \ - D3_IMSTYLE_COLOR( TableHeaderBg ) \ - D3_IMSTYLE_COLOR( TableBorderStrong ) \ - D3_IMSTYLE_COLOR( TableBorderLight ) \ - D3_IMSTYLE_COLOR( TableRowBg ) \ - D3_IMSTYLE_COLOR( TableRowBgAlt ) \ - D3_IMSTYLE_COLOR( TextSelectedBg ) \ - D3_IMSTYLE_COLOR( DragDropTarget ) \ - D3_IMSTYLE_COLOR( NavHighlight ) \ - D3_IMSTYLE_COLOR( NavWindowingHighlight ) \ - D3_IMSTYLE_COLOR( NavWindowingDimBg ) \ - D3_IMSTYLE_COLOR( ModalWindowDimBg ) - - -bool WriteStyle( const ImGuiStyle& s, const char* filename ) { - FILE* f = fopen( filename, "w" ); // TODO: "wt" on Windows? - if ( f == nullptr ) { - - return false; - } - - fprintf( f, "[style]\n" ); - -#define D3_IMATTR_FLOAT( NAME ) \ - fprintf( f, #NAME " = %g\n", s . NAME ); -#define D3_IMATTR_VEC2( NAME ) \ - fprintf( f, #NAME " = %g, %g\n", s . NAME . x, s . NAME . y ); -#define D3_IMATTR_INT( NAME ) \ - fprintf( f, #NAME " = %d\n", s . NAME ); -#define D3_IMATTR_BOOL( NAME ) \ - fprintf( f, #NAME " = %d\n", (int) ( s . NAME ) ); - - // this (together with the D3_IMATTR_* defines in the previous lines) - // extends the D3_IMSTYLE_ATTRS table to - // fprintf( f, "Alpha = %f\n", s . Alpha ); - // fprintf( f, "DisabledAlpha = %f\n", s . DisabledAlpha ); - // fprintf( f, "WindowPadding = %f %f\n", s . WindowPadding . x, s . WindowPadding . y ); - // etc - D3_IMSTYLE_ATTRS - - fprintf( f, "\n[behaviors]\n" ); - - // same for behaviors - D3_IMSTYLE_BEHAVIORS - -#undef D3_IMATTR_FLOAT -#undef D3_IMATTR_VEC2 -#undef D3_IMATTR_INT -#undef D3_IMATTR_BOOL - - fprintf( f, "\n[colors]\n" ); - -#define D3_IMSTYLE_COLOR( NAME ) { \ - const ImVec4& c = s.Colors[ ImGuiCol_ ## NAME ]; \ - fprintf( f, #NAME " = %g, %g, %g, %g\n", c.x, c.y, c.z, c.w ); \ - } - - // this turns into - // { const ImVec4& c = s.Colors[ ImGuiCol_Text ]; fprintf( f, "Text = %f %f %f %f\n", c.x, c.y, c.z, c.w ); } - // { const ImVec4& c = s.Colors[ ImGuiCol_TextDisabled ]; fprintf( f, "TextDisabled = %f %f %f %f\n", c.x, c.y, c.z, c.w ); } - // etc - D3_IMSTYLE_COLORS - -#undef D3_IMSTYLE_COLOR - - fprintf( f, "\n" ); - fflush( f ); - fclose( f ); - - return true; -} - -static inline char* skipWhitespace( const char* s ) { - while( *s == ' ' || *s == '\t' ) - ++s; - return (char*)s; -} - -#define D3_IMATTR_FLOAT( NAME ) \ - if ( sscanf( line, #NAME " = %f", &f1 ) == 1 ) { \ - s . NAME = f1; \ - return; \ - } - -#define D3_IMATTR_VEC2( NAME ) \ - if ( sscanf( line, #NAME " = %f , %f", &f1, &f2 ) == 2 ) { \ - s . NAME .x = f1; s . NAME .y = f2; \ - return; \ - } - -#define D3_IMATTR_INT( NAME ) \ - if ( sscanf( line, #NAME " = %d", &i ) == 1 ) { \ - s . NAME = i; \ - return; \ - } - -#define D3_IMATTR_BOOL( NAME ) \ - if ( sscanf( line, #NAME " = %d", &i ) == 1 ) { \ - s . NAME = ( i != 0 ); \ - return; \ - } - - -static void parseStyleLine( ImGuiStyle& s, const char* line ) -{ - float f1=0, f2=0; - int i=0; - - D3_IMSTYLE_ATTRS - - common->Warning( "Invalid line in ImGui style under [style] section: '%s'\n", line ); -} - -static void parseBehaviorLine( ImGuiStyle& s, const char* line ) -{ - float f1=0, f2=0; - (void)f2; // currently unused in behavior section - int i=0; - - D3_IMSTYLE_BEHAVIORS - - common->Warning( "Invalid line in ImGui style under [behaviors] section: '%s'\n", line ); -} - -#undef D3_IMATTR_FLOAT -#undef D3_IMATTR_VEC2 -#undef D3_IMATTR_INT -#undef D3_IMATTR_BOOL - -static void parseColorLine( ImGuiStyle& s, const char* line ) -{ - ImVec4 c; - -#define D3_IMSTYLE_COLOR( NAME ) \ - if ( sscanf( line, #NAME " = %f , %f , %f , %f", &c.x, &c.y, &c.z, &c.w) == 4 ) { \ - s.Colors[ ImGuiCol_ ## NAME ] = c; \ - return; \ - } - - D3_IMSTYLE_COLORS - -#undef D3_IMSTYLE_COLOR - - common->Warning( "Invalid line in ImGui style under [colors] section: '%s'\n", line ); - -} - -bool ReadStyle( ImGuiStyle& s, const char* filename ) -{ - FILE* f = fopen( filename, "r" ); // TODO: "rt" on Windows? - if ( f == nullptr ) { - return false; - } - - char lineBuf[256]; - - int section = -1; // 0: style, 1: behaviors, 2: colors - - for ( char* line = fgets( lineBuf, sizeof(lineBuf), f ); - line != nullptr; - line = fgets( lineBuf, sizeof(lineBuf), f ) ) - { - // skip whitespace, if any - line = skipWhitespace(line); - if ( *line == '#' ) // skip comment lines - continue; - if ( *line == '[' ) { // start of a section - const char* secStr = line+1; // skip '[' - secStr = skipWhitespace(secStr); - // "[style]" "[behaviors]" "[colors]" - if ( idStr::Icmpn(secStr, "style", 5) == 0 ) { - section = 0; - } else if ( idStr::Icmpn(secStr, "behaviors", 9) == 0 ) { - section = 1; - } else if ( idStr::Icmpn(secStr, "colors", 6) == 0 ) { - section = 2; - } else { - common->Warning( "Invalid line that looks like a section in ImGui style: '%s'\n", line ); - } - continue; - } - if ( *line == '\r' || *line == '\n' ) { - continue; // empty line - } - if ( section == 0 ) { - parseStyleLine( s, line ); - } else if ( section == 1 ) { - parseBehaviorLine( s, line ); - } else if ( section == 2 ) { - parseColorLine( s, line ); - } else { - common->Warning( "Invalid line in ImGui before start of any section: '%s'\n", line ); - } - } - - return true; -} - -// check correctness of the X macros above (detect when something is added to ImGuiStyle) -namespace { - -#define D3_IMSTYLE_COLOR(C) + 1 -// "0 D3_IMSTYLE_COLORS" is expanded to 0 + 1 + 1 ... for each D3_IMSTYLE_COLOR entry -// => it should have the same value as the ImGuiCol_COUNT constant -static_assert( ImGuiCol_COUNT == 0 D3_IMSTYLE_COLORS, - "something was added or removed in enum ImGuiCol_ => adjust D3_IMSTYLE_COLORS table above" ); -#undef D3_IMSTYLE_COLOR - -// recreate struct ImGuiStyle from the tables above and see if they're identical -// (this struct is only used for the static assertions below) -struct D3_ImGuiStyle_Check { - -#define D3_IMATTR_FLOAT( NAME ) float NAME ; -#define D3_IMATTR_VEC2( NAME ) ImVec2 NAME ; -#define D3_IMATTR_INT( NAME ) int NAME ; -#define D3_IMATTR_BOOL( NAME ) bool NAME ; - - // this expands to all the ImGuiStyle members, up to (excluding) Colors - // exactly like in ImGuiStyle (except the pseudo-enums like ImGuiDir are plain ints here) - D3_IMSTYLE_ATTRS - - ImVec4 Colors[ImGuiCol_COUNT]; - - // just like the other members/attributes, expand the behaviors - D3_IMSTYLE_BEHAVIORS - -#undef D3_IMATTR_FLOAT -#undef D3_IMATTR_VEC2 -#undef D3_IMATTR_INT -#undef D3_IMATTR_BOOL - -}; - -template -struct is_same { - static constexpr bool value = false; -}; - -template -struct is_same { - static constexpr bool value = true; -}; - -#define D3_IMATTR_FLOAT( NAME ) \ - static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ - "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ - static_assert( is_same< decltype( ImGuiStyle :: NAME ), float >::value, "expected member " #NAME "to be a float - adjust the list!" ); - -#define D3_IMATTR_VEC2( NAME ) \ - static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ - "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ - static_assert( is_same< decltype( ImGuiStyle :: NAME ), ImVec2 >::value, "expected member " #NAME " to be an ImVec2 - adjust the list!" ); - -#define D3_IMATTR_INT( NAME ) \ - static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ - "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ - static_assert( is_same< decltype( ImGuiStyle :: NAME ), int >::value, "expected member " #NAME " to be an int - adjust the list!" ); - -#define D3_IMATTR_BOOL( NAME ) \ - static_assert( offsetof( ImGuiStyle, NAME ) == offsetof( D3_ImGuiStyle_Check, NAME ), \ - "member " #NAME " not at expected offset - is the member before it missing from the list, or moved to another position?" ); \ - static_assert( is_same< decltype( ImGuiStyle :: NAME ), bool >::value, "expected member " #NAME " to be a bool - adjust the list!" ); - -// expanding those static assertions for offset and type for all attributes and behaviors - -D3_IMSTYLE_ATTRS - -D3_IMSTYLE_BEHAVIORS - -#undef D3_IMATTR_FLOAT -#undef D3_IMATTR_VEC2 -#undef D3_IMATTR_INT -#undef D3_IMATTR_BOOL - -static_assert( offsetof( ImGuiStyle, Colors ) == offsetof( D3_ImGuiStyle_Check, Colors ), "member Colors not at expected offset" ); - -// if all other static assertions passed and the following failed, probably a member was added at the end of the ImGuiStyle struct -static_assert( sizeof(D3_ImGuiStyle_Check) == sizeof(ImGuiStyle), - "something seems to be missing or wrong type in D3_IMSTYLE_ATTRS or D3_IMSTYLE_BEHAVIORS" ); - -} //anon namespace - }} //namespace D3::ImGuiHooks