From ac83bbeb0e2480ea89ca0efac9c8566f2adc4774 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 16 Feb 2023 18:06:40 +0100 Subject: [PATCH] feat: Added a theme manager view to make it easier to make new themes --- lib/libimhex/include/hex/api/event.hpp | 1 + .../include/hex/api/theme_manager.hpp | 37 +- lib/libimhex/source/api/theme_manager.cpp | 96 ++++- main/source/window/window.cpp | 2 + plugins/builtin/CMakeLists.txt | 1 + .../content/views/view_theme_manager.hpp | 28 ++ plugins/builtin/romfs/lang/en_US.json | 6 + plugins/builtin/source/content/themes.cpp | 403 +++++++++++------- plugins/builtin/source/content/views.cpp | 2 + .../content/views/view_pattern_editor.cpp | 11 +- .../content/views/view_theme_manager.cpp | 73 ++++ plugins/builtin/source/plugin_builtin.cpp | 2 + 12 files changed, 494 insertions(+), 168 deletions(-) create mode 100644 plugins/builtin/include/content/views/view_theme_manager.hpp create mode 100644 plugins/builtin/source/content/views/view_theme_manager.cpp diff --git a/lib/libimhex/include/hex/api/event.hpp b/lib/libimhex/include/hex/api/event.hpp index c842f2080..7f29f49cf 100644 --- a/lib/libimhex/include/hex/api/event.hpp +++ b/lib/libimhex/include/hex/api/event.hpp @@ -134,6 +134,7 @@ namespace hex { EVENT_DEF(RequestChangeTheme, std::string); EVENT_DEF(RequestOpenPopup, std::string); EVENT_DEF(RequestCreateProvider, std::string, bool, hex::prv::Provider **); + EVENT_DEF(RequestInitThemeHandlers); EVENT_DEF(RequestShowInfoPopup, std::string); EVENT_DEF(RequestShowErrorPopup, std::string); diff --git a/lib/libimhex/include/hex/api/theme_manager.hpp b/lib/libimhex/include/hex/api/theme_manager.hpp index 832803fbe..9462835b4 100644 --- a/lib/libimhex/include/hex/api/theme_manager.hpp +++ b/lib/libimhex/include/hex/api/theme_manager.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -14,24 +15,54 @@ namespace hex::api { public: constexpr static auto NativeTheme = "Native"; + using ColorMap = std::map; + + struct Style { + std::variant value; + float min; + float max; + }; + using StyleMap = std::map; + static void changeTheme(std::string name); static void addTheme(const std::string &content); - static void addThemeHandler(const std::string &name, const std::function &handler); - static void addStyleHandler(const std::string &name, const std::function &handler); + static void addThemeHandler(const std::string &name, const ColorMap &colorMap, const std::function &getFunction, const std::function &setFunction); + static void addStyleHandler(const std::string &name, const StyleMap &styleMap); static std::vector getThemeNames(); static const std::string &getThemeImagePostfix(); static std::optional parseColorString(const std::string &colorString); + static nlohmann::json exportCurrentTheme(const std::string &name); + static void reset(); + + + public: + struct ThemeHandler { + ColorMap colorMap; + std::function getFunction; + std::function setFunction; + }; + + struct StyleHandler { + StyleMap styleMap; + }; + + static std::map& getThemeHandlers() { return s_themeHandlers; } + static std::map& getStyleHandlers() { return s_styleHandlers; } + private: ThemeManager() = default; + static std::map s_themes; - static std::map> s_themeHandlers, s_styleHandlers; + static std::map s_themeHandlers; + static std::map s_styleHandlers; static std::string s_imagePostfix; + static std::string s_currTheme; }; } \ No newline at end of file diff --git a/lib/libimhex/source/api/theme_manager.cpp b/lib/libimhex/source/api/theme_manager.cpp index df42e1fd4..91ef32dee 100644 --- a/lib/libimhex/source/api/theme_manager.cpp +++ b/lib/libimhex/source/api/theme_manager.cpp @@ -8,15 +8,17 @@ namespace hex::api { std::map ThemeManager::s_themes; - std::map> ThemeManager::s_themeHandlers, ThemeManager::s_styleHandlers; + std::map ThemeManager::s_themeHandlers; + std::map ThemeManager::s_styleHandlers; std::string ThemeManager::s_imagePostfix; + std::string ThemeManager::s_currTheme; - void ThemeManager::addThemeHandler(const std::string &name, const std::function &handler) { - s_themeHandlers[name] = handler; + void ThemeManager::addThemeHandler(const std::string &name, const ColorMap &colorMap, const std::function &getFunction, const std::function &setFunction) { + s_themeHandlers[name] = { colorMap, getFunction, setFunction }; } - void ThemeManager::addStyleHandler(const std::string &name, const std::function &handler) { - s_styleHandlers[name] = handler; + void ThemeManager::addStyleHandler(const std::string &name, const StyleMap &styleMap) { + s_styleHandlers[name] = { styleMap }; } void ThemeManager::addTheme(const std::string &content) { @@ -49,6 +51,42 @@ namespace hex::api { return ImColor(hex::changeEndianess(color, std::endian::big)); } + nlohmann::json ThemeManager::exportCurrentTheme(const std::string &name) { + nlohmann::json theme = { + { "name", name }, + { "image_postfix", s_imagePostfix }, + { "colors", {} }, + { "styles", {} }, + { "base", s_currTheme } + }; + + for (const auto &[type, handler] : s_themeHandlers) { + theme["colors"][type] = {}; + + for (const auto &[key, value] : handler.colorMap) { + auto color = handler.getFunction(value); + theme["colors"][type][key] = fmt::format("#{:08X}", hex::changeEndianess(u32(color), std::endian::big)); + } + } + + for (const auto &[type, handler] : s_styleHandlers) { + theme["styles"][type] = {}; + + for (const auto &[key, style] : handler.styleMap) { + if (std::holds_alternative(style.value)) + theme["styles"][type][key] = *std::get(style.value); + else if (std::holds_alternative(style.value)) { + theme["styles"][type][key] = { + std::get(style.value)->x, + std::get(style.value)->y + }; + } + } + } + + return theme; + } + void ThemeManager::changeTheme(std::string name) { if (!s_themes.contains(name)) { if (s_themes.empty()) { @@ -77,19 +115,52 @@ namespace hex::api { continue; } - for (const auto &[key, value] : content.items()) - s_themeHandlers[type](key, value.get()); + const auto &handler = s_themeHandlers[type]; + for (const auto &[key, value] : content.items()) { + if (!handler.colorMap.contains(key)) { + log::warn("No color found for '{}.{}'", type, key); + continue; + } + + auto color = parseColorString(value.get()); + if (!color.has_value()) { + log::warn("Invalid color '{}' for '{}.{}'", value.get(), type, key); + continue; + } + + s_themeHandlers[type].setFunction(s_themeHandlers[type].colorMap.at(key), color.value()); + } } } if (theme.contains("styles")) { - for (const auto&[key, value] : theme["styles"].items()) { - if (!s_styleHandlers.contains(key)) { - log::warn("No style handler found for '{}'", key); + for (const auto&[type, content] : theme["styles"].items()) { + if (!s_styleHandlers.contains(type)) { + log::warn("No style handler found for '{}'", type); continue; } - s_styleHandlers[key](name, value.get()); + auto &handler = s_styleHandlers[type]; + for (const auto &[key, value] : content.items()) { + if (!handler.styleMap.contains(key)) + continue; + + auto &style = handler.styleMap.at(key); + + if (value.is_number_float()) { + if (auto newValue = std::get_if(&style.value); newValue != nullptr) + **newValue = value.get(); + else + log::warn("Style variable '{}' was of type ImVec2 but a float was expected.", name); + } else if (value.is_array() && value.size() == 2 && value[0].is_number_float() && value[1].is_number_float()) { + if (auto newValue = std::get_if(&style.value); newValue != nullptr) + **newValue = ImVec2(value[0].get(), value[1].get()); + else + log::warn("Style variable '{}' was of type float but a ImVec2 was expected.", name); + } else { + hex::log::error("Theme '{}' has invalid style value for '{}.{}'!", name, type, key); + } + } } } @@ -100,6 +171,8 @@ namespace hex::api { hex::log::error("Theme '{}' has invalid image postfix!", name); } } + + s_currTheme = name; } const std::string &ThemeManager::getThemeImagePostfix() { @@ -119,6 +192,7 @@ namespace hex::api { ThemeManager::s_styleHandlers.clear(); ThemeManager::s_themeHandlers.clear(); ThemeManager::s_imagePostfix.clear(); + ThemeManager::s_currTheme.clear(); } } \ No newline at end of file diff --git a/main/source/window/window.cpp b/main/source/window/window.cpp index 03aedd896..abc98b2b6 100644 --- a/main/source/window/window.cpp +++ b/main/source/window/window.cpp @@ -812,6 +812,8 @@ namespace hex { for (const auto &plugin : PluginManager::getPlugins()) plugin.setImGuiContext(ImGui::GetCurrentContext()); + + EventManager::post(); } void Window::exitGLFW() { diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index 28c838250..4ec4b3c34 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -55,6 +55,7 @@ add_library(${PROJECT_NAME} SHARED source/content/views/view_diff.cpp source/content/views/view_provider_settings.cpp source/content/views/view_find.cpp + source/content/views/view_theme_manager.cpp source/content/helpers/math_evaluator.cpp diff --git a/plugins/builtin/include/content/views/view_theme_manager.hpp b/plugins/builtin/include/content/views/view_theme_manager.hpp new file mode 100644 index 000000000..b0be92d9e --- /dev/null +++ b/plugins/builtin/include/content/views/view_theme_manager.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +#include +#include + +namespace hex::plugin::builtin { + + class ViewThemeManager : public View { + public: + ViewThemeManager(); + ~ViewThemeManager() override = default; + + void drawContent() override; + + [[nodiscard]] bool isAvailable() const override { return true; } + [[nodiscard]] bool hasViewMenuItemEntry() const override { return false; } + + private: + std::string m_themeName; + bool m_viewOpen = false; + }; + +} \ No newline at end of file diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 464b4622b..814bf9f6b 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -798,6 +798,12 @@ "hex.builtin.view.store.tab.patterns": "Patterns", "hex.builtin.view.store.tab.yara": "Yara Rules", "hex.builtin.view.store.update": "Update", + "hex.builtin.view.theme_manager.name": "Theme Manager", + "hex.builtin.view.theme_manager.colors": "Colors", + "hex.builtin.view.theme_manager.export": "Export", + "hex.builtin.view.theme_manager.export.name": "Theme name", + "hex.builtin.view.theme_manager.save_theme": "Save Theme", + "hex.builtin.view.theme_manager.styles": "Styles", "hex.builtin.view.tools.name": "Tools", "hex.builtin.view.yara.error": "Yara Compiler error: ", "hex.builtin.view.yara.header.matches": "Matches", diff --git a/plugins/builtin/source/content/themes.cpp b/plugins/builtin/source/content/themes.cpp index 54bca3c43..109411fc3 100644 --- a/plugins/builtin/source/content/themes.cpp +++ b/plugins/builtin/source/content/themes.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -11,118 +12,118 @@ #include #include +#include + namespace hex::plugin::builtin { - namespace { - - [[maybe_unused]] void printThemeData(const auto &colorMap, const auto &colors) { - for (const auto &[name, id] : colorMap) - fmt::print("\"{}\": \"#{:08X}\",\n", name, hex::changeEndianess(colors[id], std::endian::big)); - } - - } - void registerThemeHandlers() { - api::ThemeManager::addThemeHandler("imgui", [](const std::string &key, const std::string &value) { - const static std::map ColorMap = { - { "text", ImGuiCol_Text }, - { "text-disabled", ImGuiCol_TextDisabled }, - { "window-background", ImGuiCol_WindowBg }, - { "child-background", ImGuiCol_ChildBg }, - { "popup-background", ImGuiCol_PopupBg }, - { "border", ImGuiCol_Border }, - { "border-shadow", ImGuiCol_BorderShadow }, - { "frame-background", ImGuiCol_FrameBg }, - { "frame-background-hovered", ImGuiCol_FrameBgHovered }, - { "frame-background-active", ImGuiCol_FrameBgActive }, - { "title-background", ImGuiCol_TitleBg }, - { "title-background-active", ImGuiCol_TitleBgActive }, - { "title-background-collapse", ImGuiCol_TitleBgCollapsed }, - { "menu-bar-background", ImGuiCol_MenuBarBg }, - { "scrollbar-background", ImGuiCol_ScrollbarBg }, - { "scrollbar-grab", ImGuiCol_ScrollbarGrab }, - { "scrollbar-grab-hovered", ImGuiCol_ScrollbarGrabHovered }, - { "scrollbar-grab-active", ImGuiCol_ScrollbarGrabActive }, - { "check-mark", ImGuiCol_CheckMark }, - { "slider-grab", ImGuiCol_SliderGrab }, - { "slider-grab-active", ImGuiCol_SliderGrabActive }, - { "button", ImGuiCol_Button }, - { "button-hovered", ImGuiCol_ButtonHovered }, - { "button-active", ImGuiCol_ButtonActive }, - { "header", ImGuiCol_Header }, - { "header-hovered", ImGuiCol_HeaderHovered }, - { "header-active", ImGuiCol_HeaderActive }, - { "separator", ImGuiCol_Separator }, - { "separator-hovered", ImGuiCol_SeparatorHovered }, - { "separator-active", ImGuiCol_SeparatorActive }, - { "resize-grip", ImGuiCol_ResizeGrip }, - { "resize-grip-hovered", ImGuiCol_ResizeGripHovered }, - { "resize-grip-active", ImGuiCol_ResizeGripActive }, - { "tab", ImGuiCol_Tab }, - { "tab-hovered", ImGuiCol_TabHovered }, - { "tab-active", ImGuiCol_TabActive }, - { "tab-unfocused", ImGuiCol_TabUnfocused }, - { "tab-unfocused-active", ImGuiCol_TabUnfocusedActive }, - { "docking-preview", ImGuiCol_DockingPreview }, - { "docking-empty-background", ImGuiCol_DockingEmptyBg }, - { "plot-lines", ImGuiCol_PlotLines }, - { "plot-lines-hovered", ImGuiCol_PlotLinesHovered }, - { "plot-histogram", ImGuiCol_PlotHistogram }, - { "plot-histogram-hovered", ImGuiCol_PlotHistogramHovered }, - { "table-header-background", ImGuiCol_TableHeaderBg }, - { "table-border-strong", ImGuiCol_TableBorderStrong }, - { "table-border-light", ImGuiCol_TableBorderLight }, - { "table-row-background", ImGuiCol_TableRowBg }, - { "table-row-background-alt", ImGuiCol_TableRowBgAlt }, - { "text-selected-background", ImGuiCol_TextSelectedBg }, - { "drag-drop-target", ImGuiCol_DragDropTarget }, - { "nav-highlight", ImGuiCol_NavHighlight }, - { "nav-windowing-highlight", ImGuiCol_NavWindowingHighlight }, - { "nav-windowing-background", ImGuiCol_NavWindowingDimBg }, - { "modal-window-dim-background", ImGuiCol_ModalWindowDimBg } - }; + EventManager::subscribe([]() { + { + const static api::ThemeManager::ColorMap ImGuiColorMap = { + { "text", ImGuiCol_Text }, + { "text-disabled", ImGuiCol_TextDisabled }, + { "window-background", ImGuiCol_WindowBg }, + { "child-background", ImGuiCol_ChildBg }, + { "popup-background", ImGuiCol_PopupBg }, + { "border", ImGuiCol_Border }, + { "border-shadow", ImGuiCol_BorderShadow }, + { "frame-background", ImGuiCol_FrameBg }, + { "frame-background-hovered", ImGuiCol_FrameBgHovered }, + { "frame-background-active", ImGuiCol_FrameBgActive }, + { "title-background", ImGuiCol_TitleBg }, + { "title-background-active", ImGuiCol_TitleBgActive }, + { "title-background-collapse", ImGuiCol_TitleBgCollapsed }, + { "menu-bar-background", ImGuiCol_MenuBarBg }, + { "scrollbar-background", ImGuiCol_ScrollbarBg }, + { "scrollbar-grab", ImGuiCol_ScrollbarGrab }, + { "scrollbar-grab-hovered", ImGuiCol_ScrollbarGrabHovered }, + { "scrollbar-grab-active", ImGuiCol_ScrollbarGrabActive }, + { "check-mark", ImGuiCol_CheckMark }, + { "slider-grab", ImGuiCol_SliderGrab }, + { "slider-grab-active", ImGuiCol_SliderGrabActive }, + { "button", ImGuiCol_Button }, + { "button-hovered", ImGuiCol_ButtonHovered }, + { "button-active", ImGuiCol_ButtonActive }, + { "header", ImGuiCol_Header }, + { "header-hovered", ImGuiCol_HeaderHovered }, + { "header-active", ImGuiCol_HeaderActive }, + { "separator", ImGuiCol_Separator }, + { "separator-hovered", ImGuiCol_SeparatorHovered }, + { "separator-active", ImGuiCol_SeparatorActive }, + { "resize-grip", ImGuiCol_ResizeGrip }, + { "resize-grip-hovered", ImGuiCol_ResizeGripHovered }, + { "resize-grip-active", ImGuiCol_ResizeGripActive }, + { "tab", ImGuiCol_Tab }, + { "tab-hovered", ImGuiCol_TabHovered }, + { "tab-active", ImGuiCol_TabActive }, + { "tab-unfocused", ImGuiCol_TabUnfocused }, + { "tab-unfocused-active", ImGuiCol_TabUnfocusedActive }, + { "docking-preview", ImGuiCol_DockingPreview }, + { "docking-empty-background", ImGuiCol_DockingEmptyBg }, + { "plot-lines", ImGuiCol_PlotLines }, + { "plot-lines-hovered", ImGuiCol_PlotLinesHovered }, + { "plot-histogram", ImGuiCol_PlotHistogram }, + { "plot-histogram-hovered", ImGuiCol_PlotHistogramHovered }, + { "table-header-background", ImGuiCol_TableHeaderBg }, + { "table-border-strong", ImGuiCol_TableBorderStrong }, + { "table-border-light", ImGuiCol_TableBorderLight }, + { "table-row-background", ImGuiCol_TableRowBg }, + { "table-row-background-alt", ImGuiCol_TableRowBgAlt }, + { "text-selected-background", ImGuiCol_TextSelectedBg }, + { "drag-drop-target", ImGuiCol_DragDropTarget }, + { "nav-highlight", ImGuiCol_NavHighlight }, + { "nav-windowing-highlight", ImGuiCol_NavWindowingHighlight }, + { "nav-windowing-background", ImGuiCol_NavWindowingDimBg }, + { "modal-window-dim-background", ImGuiCol_ModalWindowDimBg } + }; - auto color = api::ThemeManager::parseColorString(value); - auto colors = ImGui::GetStyle().Colors; + api::ThemeManager::addThemeHandler("imgui", ImGuiColorMap, + [](u32 colorId) -> ImColor { + return ImGui::GetStyle().Colors[colorId]; + }, + [](u32 colorId, ImColor color) { + ImGui::GetStyle().Colors[colorId] = color; + } + ); + } - if (ColorMap.contains(key) && color.has_value()) - colors[ColorMap.at(key)] = color->Value; - }); + { + const static api::ThemeManager::ColorMap ImPlotColorMap = { + { "line", ImPlotCol_Line }, + { "fill", ImPlotCol_Fill }, + { "marker-outline", ImPlotCol_MarkerOutline }, + { "marker-fill", ImPlotCol_MarkerFill }, + { "error-bar", ImPlotCol_ErrorBar }, + { "frame-bg", ImPlotCol_FrameBg }, + { "plot-bg", ImPlotCol_PlotBg }, + { "plot-border", ImPlotCol_PlotBorder }, + { "legend-bg", ImPlotCol_LegendBg }, + { "legend-border", ImPlotCol_LegendBorder }, + { "legend-text", ImPlotCol_LegendText }, + { "title-text", ImPlotCol_TitleText }, + { "inlay-text", ImPlotCol_InlayText }, + { "axis-text", ImPlotCol_AxisText }, + { "axis-grid", ImPlotCol_AxisGrid }, + { "axis-tick", ImPlotCol_AxisTick }, + { "axis-bg", ImPlotCol_AxisBg }, + { "axis-bg-hovered", ImPlotCol_AxisBgHovered }, + { "axis-bg-active", ImPlotCol_AxisBgActive }, + { "selection", ImPlotCol_Selection }, + { "crosshairs", ImPlotCol_Crosshairs } + }; - api::ThemeManager::addThemeHandler("implot", [](const std::string &key, const std::string &value) { - const static std::map ColorMap = { - { "line", ImPlotCol_Line }, - { "fill", ImPlotCol_Fill }, - { "marker-outline", ImPlotCol_MarkerOutline }, - { "marker-fill", ImPlotCol_MarkerFill }, - { "error-bar", ImPlotCol_ErrorBar }, - { "frame-bg", ImPlotCol_FrameBg }, - { "plot-bg", ImPlotCol_PlotBg }, - { "plot-border", ImPlotCol_PlotBorder }, - { "legend-bg", ImPlotCol_LegendBg }, - { "legend-border", ImPlotCol_LegendBorder }, - { "legend-text", ImPlotCol_LegendText }, - { "title-text", ImPlotCol_TitleText }, - { "inlay-text", ImPlotCol_InlayText }, - { "axis-text", ImPlotCol_AxisText }, - { "axis-grid", ImPlotCol_AxisGrid }, - { "axis-tick", ImPlotCol_AxisTick }, - { "axis-bg", ImPlotCol_AxisBg }, - { "axis-bg-hovered", ImPlotCol_AxisBgHovered }, - { "axis-bg-active", ImPlotCol_AxisBgActive }, - { "selection", ImPlotCol_Selection }, - { "crosshairs", ImPlotCol_Crosshairs } - }; + api::ThemeManager::addThemeHandler("implot", ImPlotColorMap, + [](u32 colorId) -> ImColor { + return ImPlot::GetStyle().Colors[colorId]; + }, + [](u32 colorId, ImColor color) { + ImPlot::GetStyle().Colors[colorId] = color; + } + ); + } - auto color = api::ThemeManager::parseColorString(value); - auto &colors = ImPlot::GetStyle().Colors; - - if (ColorMap.contains(key) && color.has_value()) - colors[ColorMap.at(key)] = color->Value; - }); - - api::ThemeManager::addThemeHandler("imnodes", [](const std::string &key, const std::string &value) { - const static std::map ColorMap = { + { + const static api::ThemeManager::ColorMap ImNodesColorMap = { { "node-background", ImNodesCol_NodeBackground }, { "node-background-hovered", ImNodesCol_NodeBackgroundHovered }, { "node-background-selected", ImNodesCol_NodeBackgroundSelected }, @@ -152,17 +153,20 @@ namespace hex::plugin::builtin { { "mini-map-link-selected", ImNodesCol_MiniMapLinkSelected }, { "mini-map-canvas", ImNodesCol_MiniMapCanvas }, { "mini-map-canvas-outline", ImNodesCol_MiniMapCanvasOutline }, - }; + }; - auto color = api::ThemeManager::parseColorString(value); - auto &colors = ImNodes::GetStyle().Colors; + api::ThemeManager::addThemeHandler("imnodes", ImNodesColorMap, + [](u32 colorId) -> ImColor { + return ImNodes::GetStyle().Colors[colorId]; + }, + [](u32 colorId, ImColor color) { + ImNodes::GetStyle().Colors[colorId] = color; + } + ); + } - if (ColorMap.contains(key) && color.has_value()) - colors[ColorMap.at(key)] = *color; - }); - - api::ThemeManager::addThemeHandler("imhex", [](const std::string &key, const std::string &value) { - const static std::map ColorMap = { + { + const static api::ThemeManager::ColorMap ImHexColorMap = { { "desc-button", ImGuiCustomCol_DescButton }, { "desc-button-hovered", ImGuiCustomCol_DescButtonHovered }, { "desc-button-active", ImGuiCustomCol_DescButtonActive }, @@ -174,48 +178,149 @@ namespace hex::plugin::builtin { { "toolbar-purple", ImGuiCustomCol_ToolbarPurple }, { "toolbar-brown", ImGuiCustomCol_ToolbarBrown }, { "highlight", ImGuiCustomCol_Highlight } - }; + }; - auto color = api::ThemeManager::parseColorString(value); - auto &colors = static_cast(GImGui->IO.UserData)->Colors; + api::ThemeManager::addThemeHandler("imhex", ImHexColorMap, + [](u32 colorId) -> ImColor { + return static_cast(GImGui->IO.UserData)->Colors[colorId]; - if (ColorMap.contains(key) && color.has_value()) - colors[ColorMap.at(key)] = color->Value; + }, + [](u32 colorId, ImColor color) { + static_cast(GImGui->IO.UserData)->Colors[colorId] = color; + } + ); + } + + { + const static api::ThemeManager::ColorMap TextEditorColorMap = { + { "default", u32(TextEditor::PaletteIndex::Default) }, + { "keyword", u32(TextEditor::PaletteIndex::Keyword) }, + { "number", u32(TextEditor::PaletteIndex::Number) }, + { "string", u32(TextEditor::PaletteIndex::String) }, + { "char-literal", u32(TextEditor::PaletteIndex::CharLiteral) }, + { "punctuation", u32(TextEditor::PaletteIndex::Punctuation) }, + { "preprocessor", u32(TextEditor::PaletteIndex::Preprocessor) }, + { "identifier", u32(TextEditor::PaletteIndex::Identifier) }, + { "known-identifier", u32(TextEditor::PaletteIndex::KnownIdentifier) }, + { "preproc-identifier", u32(TextEditor::PaletteIndex::PreprocIdentifier) }, + { "comment", u32(TextEditor::PaletteIndex::Comment) }, + { "multi-line-comment", u32(TextEditor::PaletteIndex::MultiLineComment) }, + { "background", u32(TextEditor::PaletteIndex::Background) }, + { "cursor", u32(TextEditor::PaletteIndex::Cursor) }, + { "selection", u32(TextEditor::PaletteIndex::Selection) }, + { "error-marker", u32(TextEditor::PaletteIndex::ErrorMarker) }, + { "breakpoint", u32(TextEditor::PaletteIndex::Breakpoint) }, + { "line-number", u32(TextEditor::PaletteIndex::LineNumber) }, + { "current-line-fill", u32(TextEditor::PaletteIndex::CurrentLineFill) }, + { "current-line-fill-inactive", u32(TextEditor::PaletteIndex::CurrentLineFillInactive) }, + { "current-line-edge", u32(TextEditor::PaletteIndex::CurrentLineEdge) } + }; + + api::ThemeManager::addThemeHandler("text-editor", TextEditorColorMap, + [](u32 colorId) -> ImColor { + return TextEditor::GetPalette()[colorId]; + }, + [](u32 colorId, ImColor color) { + auto palette = TextEditor::GetPalette(); + palette[colorId] = color; + TextEditor::SetPalette(palette); + } + ); + } }); + } - api::ThemeManager::addThemeHandler("text-editor", [](const std::string &key, const std::string &value) { - using enum TextEditor::PaletteIndex; - const static std::map ColorMap = { - { "default", Default }, - { "keyword", Keyword }, - { "number", Number }, - { "string", String }, - { "char-literal", CharLiteral }, - { "punctuation", Punctuation }, - { "preprocessor", Preprocessor }, - { "identifier", Identifier }, - { "known-identifier", KnownIdentifier }, - { "preproc-identifier", PreprocIdentifier }, - { "comment", Comment }, - { "multi-line-comment", MultiLineComment }, - { "background", Background }, - { "cursor", Cursor }, - { "selection", Selection }, - { "error-marker", ErrorMarker }, - { "breakpoint", Breakpoint }, - { "line-number", LineNumber }, - { "current-line-fill", CurrentLineFill }, - { "current-line-fill-inactive", CurrentLineFillInactive }, - { "current-line-edge", CurrentLineEdge } - }; + void registerStyleHandlers() { + EventManager::subscribe([]() { + { + auto &style = ImGui::GetStyle(); + const static api::ThemeManager::StyleMap ImGuiStyleMap = { + { "alpha", { &style.Alpha, 0.001F, 1.0F } }, + { "disabled-alpha", { &style.DisabledAlpha, 0.0F, 1.0F } }, + { "window-padding", { &style.WindowPadding, 0.0F, 20.0F } }, + { "window-rounding", { &style.WindowRounding, 0.0F, 12.0F } }, + { "window-border-size", { &style.WindowBorderSize, 0.0F, 1.0F } }, + { "window-min-size", { &style.WindowMinSize, 0.0F, 1000.0F } }, + { "window-title-align", { &style.WindowTitleAlign, 0.0F, 1.0F } }, + { "child-rounding", { &style.ChildRounding, 0.0F, 12.0F } }, + { "child-border-size", { &style.ChildBorderSize, 0.0F, 1.0F } }, + { "popup-rounding", { &style.PopupRounding, 0.0F, 12.0F } }, + { "popup-border-size", { &style.PopupBorderSize, 0.0F, 1.0F } }, + { "frame-padding", { &style.FramePadding, 0.0F, 20.0F } }, + { "frame-rounding", { &style.FrameRounding, 0.0F, 12.0F } }, + { "frame-border-size", { &style.FrameBorderSize, 0.0F, 1.0F } }, + { "item-spacing", { &style.ItemSpacing, 0.0F, 20.0F } }, + { "item-inner-spacing", { &style.ItemInnerSpacing, 0.0F, 20.0F } }, + { "indent-spacing", { &style.IndentSpacing, 0.0F, 30.0F } }, + { "cell-padding", { &style.CellPadding, 0.0F, 20.0F } }, + { "scrollbar-size", { &style.ScrollbarSize, 0.0F, 20.0F } }, + { "scrollbar-rounding", { &style.ScrollbarRounding, 0.0F, 12.0F } }, + { "grab-min-size", { &style.GrabMinSize, 0.0F, 20.0F } }, + { "grab-rounding", { &style.GrabRounding, 0.0F, 12.0F } }, + { "tab-rounding", { &style.TabRounding, 0.0F, 12.0F } }, + { "button-text-align", { &style.ButtonTextAlign, 0.0F, 1.0F } }, + { "selectable-text-align", { &style.SelectableTextAlign, 0.0F, 1.0F } }, + }; - auto color = api::ThemeManager::parseColorString(value); - auto colors = TextEditor::GetPalette(); + api::ThemeManager::addStyleHandler("imgui", ImGuiStyleMap); + } - if (ColorMap.contains(key) && color.has_value()) - colors[size_t(ColorMap.at(key))] = ImU32(*color); + { + auto &style = ImPlot::GetStyle(); + const static api::ThemeManager::StyleMap ImPlotStyleMap = { + { "line-weight", { &style.LineWeight, 0.0F, 5.0F } }, + { "marker-size", { &style.MarkerSize, 2.0F, 10.0F } }, + { "marker-weight", { &style.MarkerWeight, 0.0F, 5.0F } }, + { "fill-alpha", { &style.FillAlpha, 0.0F, 1.0F } }, + { "error-bar-size", { &style.ErrorBarSize, 0.0F, 10.0F } }, + { "error-bar-weight", { &style.ErrorBarWeight, 0.0F, 5.0F } }, + { "digital-bit-height", { &style.DigitalBitHeight, 0.0F, 20.0F } }, + { "digital-bit-gap", { &style.DigitalBitGap, 0.0F, 20.0F } }, + { "plot-border-size", { &style.PlotBorderSize, 0.0F, 2.0F } }, + { "minor-alpha", { &style.MinorAlpha, 0.0F, 1.0F } }, + { "major-tick-len", { &style.MajorTickLen, 0.0F, 20.0F } }, + { "minor-tick-len", { &style.MinorTickLen, 0.0F, 20.0F } }, + { "major-tick-size", { &style.MajorTickSize, 0.0F, 2.0F } }, + { "minor-tick-size", { &style.MinorTickSize, 0.0F, 2.0F } }, + { "major-grid-size", { &style.MajorGridSize, 0.0F, 2.0F } }, + { "minor-grid-size", { &style.MinorGridSize, 0.0F, 2.0F } }, + { "plot-padding", { &style.PlotPadding, 0.0F, 20.0F } }, + { "label-padding", { &style.LabelPadding, 0.0F, 20.0F } }, + { "legend-padding", { &style.LegendPadding, 0.0F, 20.0F } }, + { "legend-inner-padding", { &style.LegendInnerPadding, 0.0F, 10.0F } }, + { "legend-spacing", { &style.LegendSpacing, 0.0F, 5.0F } }, + { "mouse-pos-padding", { &style.MousePosPadding, 0.0F, 20.0F } }, + { "annotation-padding", { &style.AnnotationPadding, 0.0F, 5.0F } }, + { "fit-padding", { &style.FitPadding, 0.0F, 0.2F } }, + { "plot-default-size", { &style.PlotDefaultSize, 0.0F, 1000.0F } }, + { "plot-min-size", { &style.PlotMinSize, 0.0F, 300.0F } }, + }; - TextEditor::SetPalette(colors); + api::ThemeManager::addStyleHandler("implot", ImPlotStyleMap); + } + + { + auto &style = ImNodes::GetStyle(); + const static api::ThemeManager::StyleMap ImNodesStyleMap = { + { "grid-spacing", { &style.GridSpacing, 0.0F, 100.0F } }, + { "node-corner-rounding", { &style.NodeCornerRounding, 0.0F, 12.0F } }, + { "node-padding", { &style.NodePadding, 0.0F, 20.0F } }, + { "node-border-thickness", { &style.NodeBorderThickness, 0.0F, 1.0F } }, + { "link-thickness", { &style.LinkThickness, 0.0F, 10.0F } }, + { "link-line-segments-per-length", { &style.LinkLineSegmentsPerLength, 0.0F, 2.0F } }, + { "link-hover-distance", { &style.LinkHoverDistance, 0.0F, 20.0F } }, + { "pin-circle-radius", { &style.PinCircleRadius, 0.0F, 20.0F } }, + { "pin-quad-side-length", { &style.PinQuadSideLength, 0.0F, 20.0F } }, + { "pin-triangle-side-length", { &style.PinTriangleSideLength, 0.0F, 20.0F } }, + { "pin-line-thickness", { &style.PinLineThickness, 0.0F, 5.0F } }, + { "pin-hover-radius", { &style.PinHoverRadius, 0.0F, 20.0F } }, + { "pin-offset", { &style.PinOffset, -10.0F, 10.0F } }, + { "mini-map-padding", { &style.MiniMapPadding, 0.0F, 20.0F } }, + { "mini-map-offset", { &style.MiniMapOffset, -10.0F, 10.0F } }, + }; + + api::ThemeManager::addStyleHandler("imnodes", ImNodesStyleMap); + } }); } diff --git a/plugins/builtin/source/content/views.cpp b/plugins/builtin/source/content/views.cpp index a41903d92..564b657f2 100644 --- a/plugins/builtin/source/content/views.cpp +++ b/plugins/builtin/source/content/views.cpp @@ -18,6 +18,7 @@ #include "content/views/view_diff.hpp" #include "content/views/view_provider_settings.hpp" #include "content/views/view_find.hpp" +#include "content/views/view_theme_manager.hpp" namespace hex::plugin::builtin { @@ -42,6 +43,7 @@ namespace hex::plugin::builtin { ContentRegistry::Views::add(); ContentRegistry::Views::add(); ContentRegistry::Views::add(); + ContentRegistry::Views::add(); } } \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 9dbdc35c8..dd6005478 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -217,7 +217,8 @@ namespace hex::plugin::builtin { } void ViewPatternEditor::drawConsole(ImVec2 size, const std::vector> &console) { - ImGui::PushStyleColor(ImGuiCol_ChildBg, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Background)]); + const auto &palette = TextEditor::GetPalette(); + ImGui::PushStyleColor(ImGuiCol_ChildBg, palette[u32(TextEditor::PaletteIndex::Background)]); if (ImGui::BeginChild("##console", size, true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_HorizontalScrollbar)) { ImGuiListClipper clipper; @@ -232,16 +233,16 @@ namespace hex::plugin::builtin { using enum pl::core::LogConsole::Level; case Debug: - ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Comment)]); + ImGui::PushStyleColor(ImGuiCol_Text, palette[u32(TextEditor::PaletteIndex::Comment)]); break; case Info: - ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Default)]); + ImGui::PushStyleColor(ImGuiCol_Text, palette[u32(TextEditor::PaletteIndex::Default)]); break; case Warning: - ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Preprocessor)]); + ImGui::PushStyleColor(ImGuiCol_Text, palette[u32(TextEditor::PaletteIndex::Preprocessor)]); break; case Error: - ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::ErrorMarker)]); + ImGui::PushStyleColor(ImGuiCol_Text, palette[u32(TextEditor::PaletteIndex::ErrorMarker)]); break; default: continue; diff --git a/plugins/builtin/source/content/views/view_theme_manager.cpp b/plugins/builtin/source/content/views/view_theme_manager.cpp new file mode 100644 index 000000000..cfc6d5453 --- /dev/null +++ b/plugins/builtin/source/content/views/view_theme_manager.cpp @@ -0,0 +1,73 @@ +#include "content/views/view_theme_manager.hpp" + +#include + +#include + +namespace hex::plugin::builtin { + + ViewThemeManager::ViewThemeManager() : View("hex.builtin.view.theme_manager.name") { + ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 1200, [&, this] { + if (ImGui::MenuItem("hex.builtin.view.theme_manager.name"_lang, "")) { + this->m_viewOpen = true; + this->getWindowOpenState() = true; + } + }); + } + + void ViewThemeManager::drawContent() { + if (ImGui::Begin(View::toWindowName("hex.builtin.view.theme_manager.name").c_str(), &this->m_viewOpen, ImGuiWindowFlags_NoCollapse)) { + ImGui::Header("hex.builtin.view.theme_manager.colors"_lang, true); + + ImGui::PushID(1); + const auto &themeHandlers = api::ThemeManager::getThemeHandlers(); + for (auto &[name, handler] : themeHandlers) { + if (ImGui::CollapsingHeader(name.c_str())) { + for (auto &[colorName, colorId] : handler.colorMap) { + auto color = handler.getFunction(colorId); + if (ImGui::ColorEdit4(colorName.c_str(), (float*)&color.Value, + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf)) + { + handler.setFunction(colorId, color); + } + } + } + } + ImGui::PopID(); + + + ImGui::Header("hex.builtin.view.theme_manager.styles"_lang); + + ImGui::PushID(2); + for (auto &[name, handler] : api::ThemeManager::getStyleHandlers()) { + if (ImGui::CollapsingHeader(name.c_str())) { + for (auto &[styleName, style] : handler.styleMap) { + auto &[value, min, max] = style; + + if (auto floatValue = std::get_if(&value); floatValue != nullptr) + ImGui::SliderFloat(styleName.c_str(), *floatValue, min, max, "%.1f"); + else if (auto vecValue = std::get_if(&value); vecValue != nullptr) + ImGui::SliderFloat2(styleName.c_str(), &(*vecValue)->x, min, max, "%.1f"); + } + } + } + ImGui::PopID(); + + ImGui::Header("hex.builtin.view.theme_manager.export"_lang); + ImGui::InputTextIcon("hex.builtin.view.theme_manager.export.name"_lang, ICON_VS_SYMBOL_KEY, this->m_themeName); + if (ImGui::Button("hex.builtin.view.theme_manager.save_theme"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + fs::openFileBrowser(fs::DialogMode::Save, { { "ImHex Theme", "json" } }, [this](const std::fs::path &path){ + auto json = api::ThemeManager::exportCurrentTheme(this->m_themeName); + + fs::File outputFile(path, fs::File::Mode::Create); + outputFile.write(json.dump(4)); + }); + } + + } + ImGui::End(); + + this->getWindowOpenState() = this->m_viewOpen; + } + +} \ No newline at end of file diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index b8dedd771..0af5a139f 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -28,6 +28,7 @@ namespace hex::plugin::builtin { void registerViews(); void registerShortcuts(); void registerThemeHandlers(); + void registerStyleHandlers(); void registerThemes(); void addFooterItems(); @@ -64,6 +65,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { registerViews(); registerShortcuts(); registerThemeHandlers(); + registerStyleHandlers(); registerThemes(); addFooterItems();