From eb2d15471ecbd32247dca738f09a16056db974e2 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Wed, 10 Jan 2024 23:00:53 +0100 Subject: [PATCH] Added replace function to Theme to copy one theme into another one while also changing all widgets connected to the theme --- changelog.md | 3 +- include/TGUI/Loading/Theme.hpp | 13 +++++++ src/Loading/Theme.cpp | 67 ++++++++++++++++++++++++++++++++++ tests/Loading/Theme.cpp | 34 +++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 36404d1da..c417cdc9f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,8 @@ TGUI 1.2 (TBD) --------------- -- Added changeItem function to TreeView +- Added Theme::replace function +- Added TreeView::changeItem function - Added ignoreMouseEvents function to canvas widgets - Replaced getWidgetAtPosition with getWidgetAtPos - getWidgetBelowMouseCursor was given a parameter for recursive search diff --git a/include/TGUI/Loading/Theme.hpp b/include/TGUI/Loading/Theme.hpp index d320efc00..7af4de8be 100644 --- a/include/TGUI/Loading/Theme.hpp +++ b/include/TGUI/Loading/Theme.hpp @@ -103,6 +103,19 @@ TGUI_MODULE_EXPORT namespace tgui void load(const String& primary); + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Replaced this theme with another one, while updating all connected widgets to the new renderers + /// + /// @param otherTheme The theme to copy + /// + /// The renderers are copied, meaning that all widgets connected to the other theme will remain connected to it. + /// Any widgets connected to this theme will however be updated with new renderers when the same name is encountered. + /// + /// @since TGUI 1.2 + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void replace(const Theme& otherTheme); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Gets data for the renderers /// diff --git a/src/Loading/Theme.cpp b/src/Loading/Theme.cpp index 9af041e93..93e7f8d76 100644 --- a/src/Loading/Theme.cpp +++ b/src/Loading/Theme.cpp @@ -661,6 +661,71 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void Theme::replace(const Theme& otherTheme) + { + m_primary = otherTheme.m_primary; + m_globalProperties = otherTheme.m_globalProperties; + + // Replace the existing renderers + auto existingRendererIt = m_renderers.begin(); + while (existingRendererIt != m_renderers.end()) + { + const auto& id = existingRendererIt->first; + auto& existingRenderer = existingRendererIt->second; + + // If the renderer doesn't exist in the other theme then disconnect the old renderer from this theme. + // Widgets using those old renderers will remain unmodified, but they will no longer be connected to this theme. + auto rendererToCopyIt = otherTheme.m_renderers.find(id); + if ((rendererToCopyIt == otherTheme.m_renderers.end()) && !m_themeLoader->canLoad(m_primary, id)) + { + if (existingRenderer->connectedTheme == this) + existingRenderer->connectedTheme = nullptr; + + existingRendererIt = m_renderers.erase(existingRendererIt); + continue; + } + + auto newRenderer = RendererData::create(); + newRenderer->connectedTheme = this; + newRenderer->observers = std::move(existingRenderer->observers); + + if (rendererToCopyIt != otherTheme.m_renderers.end()) + newRenderer->propertyValuePairs = rendererToCopyIt->second->propertyValuePairs; + else + { + const auto& properties = m_themeLoader->load(m_primary, id); + for (const auto& property : properties) + newRenderer->propertyValuePairs[property.first] = ObjectConverter(property.second); + } + + existingRenderer = newRenderer; + + // Update the existing widgets that were using the previous renderer from this theme + for (auto& observer : newRenderer->observers) + observer->setRenderer(newRenderer); + + ++existingRendererIt; + } + + // Add the renderers that only existed in the other renderers (e.g. added via the addRenderer function) + for (const auto& otherRendererPair : otherTheme.m_renderers) + { + const auto& id = otherRendererPair.first; + const auto& otherRenderer = otherRendererPair.second; + + auto rendererIt = m_renderers.find(id); + if (rendererIt != m_renderers.end()) + continue; // We already have the renderer, it would have been handled by the earlier loop + + auto newRenderer = RendererData::create(); + newRenderer->connectedTheme = this; + newRenderer->propertyValuePairs = otherRenderer->propertyValuePairs; + m_renderers[id] = newRenderer; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::shared_ptr Theme::getRenderer(const String& id) { // If we already have this renderer in cache then just return it @@ -717,6 +782,8 @@ namespace tgui if (!renderer) renderer = RendererData::create(); + // If a renderer with the same id already existed then disconnect the old renderer from this theme. + // Widgets using the old renderer would thus remain unmodified. auto existingRendererIt = m_renderers.find(id); if (existingRendererIt != m_renderers.end()) { diff --git a/tests/Loading/Theme.cpp b/tests/Loading/Theme.cpp index fb0613705..628de3f3c 100644 --- a/tests/Loading/Theme.cpp +++ b/tests/Loading/Theme.cpp @@ -85,6 +85,40 @@ TEST_CASE("[Theme]") REQUIRE(!theme.removeRenderer("nonexistent")); } + SECTION("Replace") + { + auto data1 = std::make_shared(); + data1->propertyValuePairs["TextColor"] = {tgui::Color(255, 0, 0)}; + + auto data2 = std::make_shared(); + data2->propertyValuePairs["TextColor"] = {tgui::Color(0, 255, 0)}; + + auto data3 = std::make_shared(); + data3->propertyValuePairs["TextColor"] = {tgui::Color(0, 0, 255)}; + + auto data4 = std::make_shared(); + data4->propertyValuePairs["TextColor"] = {tgui::Color(255, 255, 0)}; + + tgui::Theme theme1; + theme1.addRenderer("A", data1); + theme1.addRenderer("B", data2); + + tgui::Theme theme2; + theme2.addRenderer("B", data3); + theme2.addRenderer("C", data4); + + REQUIRE(theme1.getRenderer("A")->propertyValuePairs["TextColor"].getColor() == tgui::Color(255, 0, 0)); + REQUIRE(theme1.getRenderer("B")->propertyValuePairs["TextColor"].getColor() == tgui::Color(0, 255, 0)); + REQUIRE(theme1.getRenderer("C")->propertyValuePairs.empty()); + + theme1.replace(theme2); + REQUIRE(theme1.getRenderer("B")->propertyValuePairs["TextColor"].getColor() == tgui::Color(0, 0, 255)); + REQUIRE(theme1.getRenderer("C")->propertyValuePairs["TextColor"].getColor() == tgui::Color(255, 255, 0)); + + REQUIRE(theme1.getRenderer("B")->connectedTheme == &theme1); + REQUIRE(theme2.getRenderer("B")->connectedTheme == &theme2); + } + SECTION("Renderers are shared") { tgui::Theme theme{"resources/Black.txt"};