diff --git a/dependencies/PhyMacParser b/dependencies/PhyMacParser index 75e06d0e..e292ffd6 160000 --- a/dependencies/PhyMacParser +++ b/dependencies/PhyMacParser @@ -1 +1 @@ -Subproject commit 75e06d0e4baee22408a2293a5f3fe608f1dbe54b +Subproject commit e292ffd6bdf9b80ef56ad25c537a1b780c348c51 diff --git a/sutk/include/sutk/ThemeManager.hpp b/sutk/include/sutk/ThemeManager.hpp index 9fc6cace..312f61b9 100644 --- a/sutk/include/sutk/ThemeManager.hpp +++ b/sutk/include/sutk/ThemeManager.hpp @@ -8,6 +8,7 @@ #include // for std::string_view #include // for std::is_same_v +#include // for std::optional<> typedef struct v3d_generic_node_t v3d_generic_node_t; @@ -44,14 +45,19 @@ namespace SUTK template void define(const KeyViewType& key) noexcept; void undefine(const KeyViewType& key) noexcept; - Type getType(const KeyViewType& key) noexcept; + std::optional getKeyType(const KeyViewType& key) noexcept; template - EventHandlerID bind(const KeyViewType& key, ValueChangeCallback callback) noexcept; - void unbind(const KeyViewType& key, EventHandlerID id) noexcept; + BindID bind(const KeyViewType& key, ValueChangeCallback callback) noexcept; + void unbind(const KeyViewType& key, BindID id) noexcept; template const ValueType& getValue(Type type, const KeyViewType& key) noexcept; void applyTheme(Theme* theme) noexcept; Theme* getCurrentTheme() noexcept { return m_currentTheme; } + com::Bool verifyKey(const KeyViewType& key, Type type) noexcept + { + auto value = try_find_value(m_defs, key); + return com::Bool { value && (value->first == type) }; + } // Published after publishing events for every key OnThemeChangeEvent& getOnThemeChangeEvent() noexcept { return m_onThemeChange; } @@ -64,60 +70,83 @@ namespace SUTK { public: using ThemeInterfaceType = ThemeInterface; + using ThemeInterfaceList = std::vector; private: com::unordered_map m_colors; com::unordered_map m_images; com::unordered_map m_fonts; com::unordered_map m_floats; com::unordered_map m_strings; - ThemeInterfaceType* m_interface; + ThemeInterfaceList m_interfaces; public: - Theme(UIDriver& driver, ThemeInterfaceType* interface) noexcept : UIDriverObject(driver), m_interface(interface) { } + Theme(UIDriver& driver, std::initializer_list interfaces) noexcept : UIDriverObject(driver), m_interfaces(interfaces) { } + Theme(UIDriver& driver, std::vector interfaces) noexcept : UIDriverObject(driver), m_interfaces(interfaces) { } + Theme(UIDriver& driver, ThemeInterfaceType* interface) noexcept : Theme(driver, { interface }) { } + + ThemeInterfaceList& getInterfaces() noexcept { return m_interfaces; } + + void apply() noexcept + { + for(auto& interface : m_interfaces) + interface->applyTheme(this); + } - ThemeInterfaceType* getInterface() noexcept { return m_interface; } + com::Bool verifyKey(const KeyViewType& key, ThemeInterfaceType::Type type) noexcept + { + for(auto& interface : m_interfaces) + if(interface->verifyKey(key, type)) + return com::True; + DEBUG_LOG_ERROR("Either the key doesn't exist or type mismatch has been occurred"); + return com::False; + } + + std::optional getKeyType(const KeyViewType& key) noexcept + { + for(auto& interface : m_interfaces) + if(auto value = interface->getKeyType(key)) + return value; + return { }; + } template - void add(const KeyViewType& key, ValueType&& value) noexcept + constexpr ThemeInterfaceType::Type getTypeEnum() noexcept { if constexpr(std::is_same_v) + return ThemeInterfaceType::Type::Color; + else if constexpr(std::is_same_v) + return ThemeInterfaceType::Type::Image; + else if constexpr(std::is_same_v) + return ThemeInterfaceType::Type::Font; + else if constexpr(std::is_same_v) + return ThemeInterfaceType::Type::Float; + else if constexpr(std::is_same_v) + return ThemeInterfaceType::Type::String; + else + static_assert(false, "Type not supported"); + } + + template + void add(const KeyViewType& key, ValueType&& value) noexcept + { + if(!verifyKey(key, getTypeEnum())) { - if(m_interface->getType(key) != ThemeInterfaceType::Type::Color) - DEBUG_LOG_ERROR("Type mismatch"); - else - m_colors.insert({ KeyType { key }, std::forward(value) }); + DEBUG_LOG_ERROR("Failed to add value into the theme"); + return; } + if constexpr(std::is_same_v) + m_colors.insert({ KeyType { key }, std::forward(value) }); else if constexpr(std::is_same_v) - { - if(m_interface->getType(key) != ThemeInterfaceType::Type::Image) - DEBUG_LOG_ERROR("Type mismatch"); - else m_images.insert({ KeyType { key }, std::forward(value) }); - } else if constexpr(std::is_same_v) - { - if(m_interface->getType(key) != ThemeInterfaceType::Type::Font) - DEBUG_LOG_ERROR("Type mismatch"); - else m_fonts.insert({ KeyType { key }, std::forward(value) }); - } else if constexpr(std::is_same_v) - { - if(m_interface->getType(key) != ThemeInterfaceType::Type::Float) - DEBUG_LOG_ERROR("Type mismatch"); - else m_floats.insert({ KeyType { key }, std::forward(value) }); - } else if constexpr(std::is_same_v) - { - if(m_interface->getType(key) != ThemeInterfaceType::Type::String) - DEBUG_LOG_ERROR("Type mismatch"); - else m_strings.insert({ KeyType { key }, std::forward(value) }); - } else static_assert(false, "Type not supported"); } - template requires(std::same_as) + template requires(com::SameAsAny) void add(const KeyViewType& key, std::string_view view) noexcept { if(view.ends_with(".png") || view.ends_with(".jpg") || view.ends_with(".bmp")) @@ -195,30 +224,41 @@ namespace SUTK } template KeyViewType> - ThemeInterface::Type ThemeInterface::getType(const KeyViewType& key) noexcept + std::optional::Type> ThemeInterface::getKeyType(const KeyViewType& key) noexcept { - return com::find_value(m_defs, key).first; + auto value = com::try_find_value(m_defs, key); + if(value) + return { value->first }; + return { }; } template KeyViewType> template - ThemeInterface::EventHandlerID ThemeInterface::bind(const KeyViewType& key, ValueChangeCallback callback) noexcept + ThemeInterface::BindID ThemeInterface::bind(const KeyViewType& key, ValueChangeCallback callback) noexcept { - auto& value = com::find_value(m_defs, key); - auto fn = [_callback = std::move(callback), key, this]() + auto value = com::try_find_value(m_defs, key); + if(value) { - _callback(this->m_currentTheme->template getValue(key)); - }; - if(m_currentTheme) - fn(); - return value.second.subscribe(std::move(fn)); + auto fn = [_callback = std::move(callback), key, this]() + { + _callback(this->m_currentTheme->template getValue(key)); + }; + if(m_currentTheme) + fn(); + return value->second.subscribe(std::move(fn)); + } + else DEBUG_LOG_ERROR("Failed to bind, no such key exists"); + return ThemeInterface::InvalidBindID; } template KeyViewType> - void ThemeInterface::unbind(const KeyViewType& key, EventHandlerID id) noexcept + void ThemeInterface::unbind(const KeyViewType& key, BindID id) noexcept { - auto& value = com::find_value(m_defs, key); - value.second.unsubscribe(id); + auto value = com::try_find_value(m_defs, key); + if(value) + value->second.unsubscribe(id); + else + DEBUG_LOG_ERROR("Failed to unbind, no such key exists"); } template KeyViewType> @@ -328,9 +368,14 @@ namespace SUTK } ThemeType* createTheme(const KeyViewType& key, ThemeInterfaceType* interface) noexcept + { + return createTheme(key, std::initializer_list { interface }); + } + template requires(com::SameAsAny, std::vector>) + ThemeType* createTheme(const KeyViewType& key, InitVectorType& interfaces) noexcept { _com_assert(!m_themes.contains(key)); - ThemeType* theme = new ThemeType(getUIDriver(), interface); + ThemeType* theme = new ThemeType(getUIDriver(), std::forward(interfaces)); m_themes.insert({ KeyType { key }, theme }); return theme; } diff --git a/sutk/include/sutk/tests/MultipleThemeModelTest.hpp b/sutk/include/sutk/tests/MultipleThemeModelTest.hpp new file mode 100644 index 00000000..ca2c97fa --- /dev/null +++ b/sutk/include/sutk/tests/MultipleThemeModelTest.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +#include + +namespace SUTK +{ + class IGfxDriver; + + class MultipleThemeModelTest : public ITest + { + private: + UIDriver* m_uiDriver; + IGfxDriver* m_gfxDriver; + IInputDriver* m_inputDriver; + RenderImage* m_renderRect; + RenderableContainer* m_renderRectContainer; + + public: + MultipleThemeModelTest() : m_uiDriver(NULL), m_gfxDriver(NULL), m_inputDriver(NULL) { } + + TestInitializationData getInitializationData() override; + + void initialize(SGE::Driver& driver) override; + + void terminate(SGE::Driver& driver) override; + + void render(SGE::Driver& driver) override; + + void update(SGE::Driver& driver, float deltaTime) override; + }; +} diff --git a/sutk/source/ITest.cpp b/sutk/source/ITest.cpp index 32651dbe..ab45a4c6 100644 --- a/sutk/source/ITest.cpp +++ b/sutk/source/ITest.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace SUTK { @@ -53,7 +54,8 @@ namespace SUTK { "CONTAINER", [] () { return std::unique_ptr(new ContainerTest()); }}, { "FONT", [] () { return std::unique_ptr(new FontTest()); }}, { "NOTEBOOK", [] () { return std::unique_ptr(new NotebookTest()); }}, - { "LUNASVG", [] () { return std::unique_ptr(new LunaSVGTest()); }} + { "LUNASVG", [] () { return std::unique_ptr(new LunaSVGTest()); }}, + { "MULTIPLE_THEME_MODEL", [] () { return std::unique_ptr(new MultipleThemeModelTest()); }} }; std::unique_ptr ITest::Create(const std::string& testName) diff --git a/sutk/source/ThemeManager.cpp b/sutk/source/ThemeManager.cpp index eefecbce..87ac1885 100644 --- a/sutk/source/ThemeManager.cpp +++ b/sutk/source/ThemeManager.cpp @@ -336,7 +336,7 @@ namespace SUTK ppsr_v3d_generic_parse_result_t result = ppsr_v3d_generic_parse(NULL, str, buf_get_element_count(text) - 1); ThemeType* theme = NULL; std::string_view strView { }; - v3d_generic_node_t* themeModelNode = NULL; + v3d_generic_node_t* themeNode = NULL; if(result.result != PPSR_SUCCESS) { DEBUG_LOG_ERROR("Unable to parse the file at %.*s", filePath.length(), filePath.data()); @@ -348,15 +348,15 @@ namespace SUTK goto HANDLE_FAILURE; } - themeModelNode = result.root->childs[0]; - if(themeModelNode->qualifier_count == 0) + themeNode = result.root->childs[0]; + if(themeNode->qualifier_count == 0) { - DEBUG_LOG_ERROR("Anonymous root block is not allowed, it must be named \"ThemeModel\""); + DEBUG_LOG_ERROR("Anonymous root block is not allowed, it must be named \"Theme\""); goto HANDLE_FAILURE; } { - u32_pair_t name = themeModelNode->qualifiers[0]; + u32_pair_t name = themeNode->qualifiers[0]; if(com_safe_strncmp(name.start + str, "Theme", U32_PAIR_DIFF(name))) { DEBUG_LOG_ERROR("Expected \"Theme\" but given \"%*.s\"", U32_PAIR_DIFF(name), name.start + str); @@ -365,41 +365,48 @@ namespace SUTK } { - v3d_generic_attribute_t* nameAttr = node_find_attribute(themeModelNode, str, "Name"); + v3d_generic_attribute_t* nameAttr = node_find_attribute(themeNode, str, "Name"); if(!nameAttr) { DEBUG_LOG_ERROR("[Name] attribute is expected on \"Theme\" block, but not given"); goto HANDLE_FAILURE; } - v3d_generic_attribute_t* modelAttr = node_find_attribute(themeModelNode, str, "Model"); - if(!modelAttr) + std::vector interfaces; + using UserData = struct { std::vector* interfaces; ThemeManager* thisPtr; }; + UserData userData = { &interfaces, this }; + node_foreach_attribute_name(themeNode, str, "Model", [](v3d_generic_attribute_t* modelAttr, const char* start, void* user_data) noexcept -> bool { - DEBUG_LOG_ERROR("[Model] attribute is expected on \"Theme\" block, but not given"); - goto HANDLE_FAILURE; - } - ThemeInterfaceType* interface = NULL; - if(modelAttr->argument_count == 1) - { - auto themeInterfaceName = std::string_view { modelAttr->arguments[0].start + str, U32_PAIR_DIFF(modelAttr->arguments[0]) }; - if(!containsThemeInterface(themeInterfaceName)) + auto* ud = static_cast(user_data); + if(modelAttr->argument_count == 1) { - DEBUG_LOG_ERROR("No Theme interface/model with name \"%.*s\" is found", themeInterfaceName.length(), themeInterfaceName.data()); - goto HANDLE_FAILURE; + auto themeInterfaceName = std::string_view { modelAttr->arguments[0].start + start, U32_PAIR_DIFF(modelAttr->arguments[0]) }; + if(!ud->thisPtr->containsThemeInterface(themeInterfaceName)) + { + DEBUG_LOG_ERROR("No Theme interface/model with name \"%.*s\" is found", themeInterfaceName.length(), themeInterfaceName.data()); + return false; + } + ud->interfaces->push_back(ud->thisPtr->getThemeInterface(themeInterfaceName)); + return true; } - interface = getThemeInterface(themeInterfaceName); - } - else + else + { + DEBUG_LOG_ERROR("Arguments mismatch, \"Model\" attribute accepts one argument (string), but either not given or more provided"); + return false; + } + return true; + }, &userData); + if(!interfaces.size()) { - DEBUG_LOG_ERROR("Arguments mismatch, \"Model\" attribute accepts one argument (string), but either not given or more provided"); + DEBUG_LOG_ERROR("[Model] attribute is expected on \"Theme\" block, but not given"); goto HANDLE_FAILURE; } if(nameAttr->argument_count == 1) { strView = std::string_view { nameAttr->arguments[0].start + str, U32_PAIR_DIFF(nameAttr->arguments[0]) }; - theme = createTheme(strView, interface); - for(u32 i = 0; i < themeModelNode->child_count; ++i) + theme = createTheme(strView, interfaces); + for(u32 i = 0; i < themeNode->child_count; ++i) { - v3d_generic_node_t* child = themeModelNode->childs[i]; + v3d_generic_node_t* child = themeNode->childs[i]; if(!child->qualifier_count) { DEBUG_LOG_ERROR("Anonymous (empty names) aren't allowed"); @@ -412,42 +419,43 @@ namespace SUTK DEBUG_LOG_ERROR("No value has been assigned to \"%.*s\"", nameSV.length(), nameSV.data()); goto HANDLE_FAILURE; } - ThemeInterfaceType::Type type = interface->getType(nameSV); - switch(type) + std::optional type = theme->getKeyType(nameSV); + if(!type) + DEBUG_LOG_WARNING("Couldn't find a key into any of the theme models which the theme implements, skipping \"%.*s\"", nameSV.length(), nameSV.data()); + else { - case ThemeInterfaceType::Type::Color: - { - Color4 color = deriveValue(child->value, str); - theme->add(nameSV, std::move(color)); - break; - } - case ThemeInterfaceType::Type::Image: - { - UIDriver::ImageReference image = deriveValue(child->value, str); - theme->add(nameSV, std::move(image)); - break; - } - case ThemeInterfaceType::Type::Font: - { - UIDriver::FontReference font = deriveValue(child->value, str); - theme->add(nameSV, std::move(font)); - break; - } - case ThemeInterfaceType::Type::Float: - { - f32 flt = deriveValue(child->value, str); - theme->add(nameSV, std::move(flt)); - break; - } - case ThemeInterfaceType::Type::String: - { - std::string stdstr = deriveValue(child->value, str); - theme->add(nameSV, std::move(stdstr)); - break; - } - default: + switch(*type) { - DEBUG_LOG_WARNING("Type either isn't recognized or not given, skipping \"%.*s\"", nameSV.length(), nameSV.data()); + case ThemeInterfaceType::Type::Color: + { + Color4 color = deriveValue(child->value, str); + theme->add(nameSV, std::move(color)); + break; + } + case ThemeInterfaceType::Type::Image: + { + UIDriver::ImageReference image = deriveValue(child->value, str); + theme->add(nameSV, std::move(image)); + break; + } + case ThemeInterfaceType::Type::Font: + { + UIDriver::FontReference font = deriveValue(child->value, str); + theme->add(nameSV, std::move(font)); + break; + } + case ThemeInterfaceType::Type::Float: + { + f32 flt = deriveValue(child->value, str); + theme->add(nameSV, std::move(flt)); + break; + } + case ThemeInterfaceType::Type::String: + { + std::string stdstr = deriveValue(child->value, str); + theme->add(nameSV, std::move(stdstr)); + break; + } } } } diff --git a/sutk/source/tests/MultipleThemeModelTest.cpp b/sutk/source/tests/MultipleThemeModelTest.cpp new file mode 100644 index 00000000..e41ce271 --- /dev/null +++ b/sutk/source/tests/MultipleThemeModelTest.cpp @@ -0,0 +1,151 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SUTK +{ + ITest::TestInitializationData MultipleThemeModelTest::getInitializationData() + { + auto data = ITest::getInitializationData(); + data.driverInitializationData.title = "Multiple Theme Load Test (press B to toggle themes)"; + return data; + } + + void MultipleThemeModelTest::initialize(SGE::Driver& driver) + { + m_gfxDriver = new SGEGfxDriver(driver); + m_inputDriver = new SGEInputDriver(driver); + m_uiDriver = new UIDriver(*m_gfxDriver, *m_inputDriver); + + ThemeManager* themeManager = new ThemeManager(*m_uiDriver); + ThemeInterface* themeInt = themeManager->loadThemeInterface("themes/multiple_theme_model_test/model1.tmdl"); + if(themeInt) + themeInt->dump(); + else + DEBUG_LOG_FETAL_ERROR("Failed to load or parse the themes/multiple_theme_model_test/model1.tmdl file"); + ThemeInterface* themeInt2 = themeManager->loadThemeInterface("themes/multiple_theme_model_test/model2.tmdl"); + if(themeInt2) + themeInt2->dump(); + else + DEBUG_LOG_FETAL_ERROR("Failed to load or parse the themes/multiple_theme_model_test/model2.tmdl file"); + Theme* theme1 = themeManager->loadTheme("themes/multiple_theme_model_test/theme1.theme"); + Theme* theme2 = themeManager->loadTheme("themes/multiple_theme_model_test/theme2.theme"); + Theme* theme3 = themeManager->loadTheme("themes/multiple_theme_model_test/theme3.theme"); + + FullWindowContainer* rootContainer = m_uiDriver->createContainer(com::null_pointer()); + Container* emptyContainer = m_uiDriver->createContainer(rootContainer); + emptyContainer->setRect({ 1.0f, 1.0f, 7.0f, 7.0f }); + AnchorRect* anchor = emptyContainer->getAnchorRect(); + anchor->setTopLeft({ 0, 0 }); + anchor->setBottomRight({ 1, 1 }); + m_renderRectContainer = m_uiDriver->createContainer(emptyContainer); + m_renderRectContainer->setRect({ 1.0f, 1.0f, 5, 5 }); + anchor = m_renderRectContainer->getAnchorRect(); + anchor->setTopLeft({ 1, 1 }); + anchor->setBottomRight({ 1, 1 }); + emptyContainer->setRect({ 1.0f, 1.0f, 9.0f, 7.0f }); + emptyContainer->setRect({ 1.0f, 1.0f, 11.0f, 7.0f }); + m_renderRect = m_uiDriver->createRenderable(m_renderRectContainer); + themeInt->bind("Folder.Open", std::bind(&RenderImage::setImage, m_renderRect, std::placeholders::_1)); + + Container* emptyContainer1 = m_uiDriver->createContainer(rootContainer); + emptyContainer1->setRect({ 1.0f, 1.0f, 7.0f, 7.0f }); + AnchorRect* anchor1 = emptyContainer1->getAnchorRect(); + anchor1->setTopLeft({ 0, 0 }); + anchor1->setBottomRight({ 1, 1 }); + RenderableContainer* renderRectContainer1 = m_uiDriver->createContainer(emptyContainer1); + renderRectContainer1->setRect({ 7.0f, 1.0f, 5, 5 }); + anchor1 = renderRectContainer1->getAnchorRect(); + anchor1->setTopLeft({ 1, 1 }); + anchor1->setBottomRight({ 1, 1 }); + emptyContainer1->setRect({ 1.0f, 1.0f, 9.0f, 7.0f }); + emptyContainer1->setRect({ 1.0f, 1.0f, 11.0f, 7.0f }); + RenderImage* renderImage = m_uiDriver->createRenderable(renderRectContainer1); + themeInt->bind("Folder.Open.VG", std::bind(&RenderImage::setImage, renderImage, std::placeholders::_1)); + renderImage->setColor(Color4::white()); + + RenderableContainer* renderRectContainer2 = m_uiDriver->createContainer(rootContainer); + RenderRectFill* renderRect2 = m_uiDriver->createRenderable(renderRectContainer2); + renderRectContainer2->setRect({ 5.0f, 10.0f, 5.0f, 5.0f }); + AnchorRect* anchor2 = renderRectContainer2->getAnchorRect(); + anchor2->setTopLeft({ 0.0f, 0.0f }); + anchor2->setBottomRight({ 1.0f, 1.0f }); + themeInt->bind("LargeButton.HoverColor", std::bind(&RenderRectFill::setColor, renderRect2, std::placeholders::_1)); + renderRect2->setColor(Color4::red()); + + RenderableContainer* renderRectContainer3 = m_uiDriver->createContainer(rootContainer); + RenderRectFill* renderRect3 = m_uiDriver->createRenderable(renderRectContainer3); + renderRectContainer3->setRect({ 11.0f, 10.0f, 5.0f, 5.0f }); + AnchorRect* anchor3 = renderRectContainer3->getAnchorRect(); + anchor3->setTopLeft({ 0.0f, 0.0f }); + anchor3->setBottomRight({ 1.0f, 1.0f }); + themeInt2->bind("AnotherButton.AlphaValue", [renderRect3](f32 value) noexcept + { + auto color = renderRect3->getColor(); + std::cout << "Float value: " << value << std::endl; + color.alpha(value * 255); + renderRect3->setColor(color); + }); + renderRect3->setColor(Color4::yellow()); + + Label* label = m_uiDriver->createContainer