diff --git a/.clangd b/.clangd index 9526b48..3261667 100644 --- a/.clangd +++ b/.clangd @@ -2,7 +2,6 @@ CompileFlags: CompilationDatabase: bin Add: [ - "--target=x86_64-w64-mingw32", "-IC:/msys64/ucrt64/include", "-IC:/msys64/ucrt64/include/ucrt", "-IC:/msys64/ucrt64/include/crt", diff --git a/CMakeLists.txt b/CMakeLists.txt index d891d83..35c8e12 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ list(APPEND OSTD_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/OutputHandlers.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Serial.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Memory.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Stylesheet.cpp # math ${CMAKE_CURRENT_LIST_DIR}/src/ostd/math/Geometry.cpp diff --git a/extra/testTheme.txt b/extra/testTheme.txt index f5c3c0e..abfffa4 100644 --- a/extra/testTheme.txt +++ b/extra/testTheme.txt @@ -1,13 +1,28 @@ +% ================== testTheme.txt ================== + +$accentColor = Color(rgba(255, 0, 255, 255)) + % Window window.backgroundColor = Color(rgba(40, 0, 0, 255)) % Label -label.textColor = Color(#FFFFFFFF) -label.backgroundColor = Color(#FFFF80FF) -label.borderColor = Color(#FFFFFFFF) +label.textColor = $accentColor +label.backgroundColor = Color(#000000FF) +label.borderColor = $accentColor label.fontSize = 30 label.borderRadius = 20 label.borderWidth = 2 -label.showBackground = false -label.showBorder = false -label.padding = Rect(5, 5, 5, 5) +label.showBackground = true +label.showBorder = true +label.padding = Rect(15, 15, 15, 15) + +% Format: +% @ID:QUALIFIER PROPERTY = VALUE + + +@testLabel label.textColor = Color(#004000FF) +@testLabel:hover label.textColor = Color(#000040FF) +@testLabel:hover label.backgroundColor = Color(#400040FF) +@testWindow window.backgroundColor = Color(#004000FF) + +% =================================================== diff --git a/other/TODO.txt b/other/TODO.txt index 2ec5c6d..f6c4ea1 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -1,5 +1,5 @@ ***Add Color constants to ostd::Color ***Add MouseEntered/MouseExited callbacks ***Fix mouse detection currently using local coordinates -implement JSON theme loading +***implement file theme loading ***Implemenmt MouseDragged callback diff --git a/src/ogfx/gui/Themes.hpp b/src/ogfx/gui/Themes.hpp deleted file mode 100644 index dacc336..0000000 --- a/src/ogfx/gui/Themes.hpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - OmniaFramework - A collection of useful functionality - Copyright (C) 2025 OmniaX-Dev - - This file is part of OmniaFramework. - - OmniaFramework is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OmniaFramework is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OmniaFramework. If not, see . -*/ - -#pragma once - -#include "io/FileSystem.hpp" -#include "io/Logger.hpp" -#include "string/String.hpp" -#include -#include -#include -#include - -namespace ogfx -{ - namespace gui - { - struct Theme - { - public: using ThemeValue = std::variant; - public: - inline Theme(bool blank = false) - { - if (blank) return; - - // Loading default theme - set("window.backgroundColor", ostd::Colors::Black); - - set("label.textColor", ostd::Colors::White); - set("label.backgroundColor", ostd::Colors::Red); - set("label.borderColor", ostd::Colors::White); - set("label.fontSize", 20); - set("label.borderRadius", 20); - set("label.borderWidth", 2); - set("label.showBackground", true); - set("label.showBorder", false); - set("label.padding", ostd::Rectangle { 5, 5, 5, 5 }); - } - - inline Theme& blank(void) - { - m_values.clear(); - return *this; - } - - inline Theme& loadFromFile(const ostd::String& filePath) - { - std::vector lines; - ostd::FileSystem::readTextFile(filePath, lines); - uint32_t lineNumber = 0; - ostd::String lineCopy = ""; - for (auto& line : lines) - { - lineNumber++; - lineCopy = line; - line.trim(); - if (line.startsWith("%")) - continue; - if (line.contains("%")) - line.substr(0, line.indexOf("%")).trim(); - if (line == "") - continue; - if (!parseThemeFileLine(line)) - OX_WARN("Invalid theme line in file <%s:%d>:\n\t%s", filePath.c_str(), lineNumber, lineCopy.c_str()); - } - return *this; - } - - inline void set(const std::string& key, ThemeValue value) - { - m_values[key] = std::move(value); - } - - inline const ThemeValue* __get(const ostd::String& key) const - { - auto it = m_values.find(key); - return it != m_values.end() ? &it->second : nullptr; - } - - template - inline T get(const ostd::String& key, const T& fallback) const - { - if (auto v = __get(key)) - { - if (auto p = std::get_if(v)) - return *p; - } - return fallback; - } - - private: - inline bool parseThemeFileLine(const ostd::String& line) - { - if (!line.contains("=")) - return false; - ostd::String key = line.new_substr(0, line.indexOf("=")).trim(); - ostd::String value = line.new_substr(line.indexOf("=") + 1).trim().toLower(); - if (key == "") - return false; - if (value.isInt()) - set(key, static_cast(value.toInt())); - else if (value.isNumeric(true)) - set(key, value.toFloat()); - else if (value == "true" || value == "false") - set(key, value == "true"); - else if (value.startsWith("\"") && value.endsWith("\"")) - set(key, value); - else if (value.startsWith("color(") && value.endsWith(")")) - { - value.substr(6, value.len() - 1).trim(); - set(key, ostd::Color(value)); - } - else if (value.startsWith("vec2(") && value.endsWith(")")) - { - value.substr(4, value.len() - 1).trim(); - auto tokens = value.tokenize(","); - if (tokens.count() != 2) - return false; - std::vector vec; - for (const auto& tok : tokens) - { - if (!tok.isNumeric(true)) - return false; - vec.push_back(tok.toFloat()); - } - set(key, ostd::Vec2(vec[0], vec[1])); - } - else if (value.startsWith("rect(") && value.endsWith(")")) - { - value.substr(5, value.len() - 1).trim(); - auto tokens = value.tokenize(","); - if (tokens.count() != 4) - return false; - std::vector vec; - for (const auto& tok : tokens) - { - if (!tok.isNumeric(true)) - return false; - vec.push_back(tok.toFloat()); - } - set(key, ostd::Rectangle(vec[0], vec[1], vec[2], vec[3])); - } - else - return false; - return true; - } - - private: - std::unordered_map m_values; - }; - } -} diff --git a/src/ogfx/gui/Widgets.cpp b/src/ogfx/gui/Widgets.cpp index f2a209e..4b065c6 100644 --- a/src/ogfx/gui/Widgets.cpp +++ b/src/ogfx/gui/Widgets.cpp @@ -118,7 +118,7 @@ namespace ogfx } } - void WidgetManager::onThemeApplied(const Theme& theme) + void WidgetManager::onThemeApplied(const ostd::Stylesheet& theme) { for (auto& w : m_widgetList) { @@ -304,13 +304,33 @@ namespace ogfx return ostd::Rectangle(getGlobalPosition(), getSize()).contains(p, includeBounds); } - void Widget::applyThemeValue(Theme& theme, const ostd::String& key, Theme::ThemeValue value, bool propagate) + void Widget::addThemeOverride(const ostd::String& key, ostd::Stylesheet::TypeVariant value, const ostd::String& themeID, const ostd::String& qualifier, bool propagate) { - auto currentValue = theme.__get(key); - if (currentValue == nullptr) return; - theme.set(key, value); - __applyTheme(theme, propagate); - theme.set(key, *currentValue); + ostd::String fullKey = "@" + themeID; + if (qualifier.new_trim() != "") + fullKey += ":" + qualifier; + fullKey += "." + key; + m_themeOverrides.push_back({ fullKey, value, propagate }); + } + + void Widget::reloadTheme(void) + { + if (getWindow().theme() == nullptr) + return; + + auto& theme = *getWindow().theme(); + applyTheme(theme); + ostd::Stylesheet& const_cast_theme = const_cast(theme); + + for (auto& rule : m_themeOverrides) + { + auto currentValue = theme.getFull(rule.fullKey); + if (currentValue == nullptr) + return; + const_cast_theme.setFull(rule.fullKey, rule.value); + __applyTheme(const_cast_theme, rule.propagate); + const_cast_theme.setFull(rule.fullKey, *currentValue); + } } void Widget::__draw(ogfx::BasicRenderer2D& gfx) @@ -369,6 +389,8 @@ namespace ogfx void Widget::__onMouseEntered(const Event& event) { + m_qualifier = "hover"; + reloadTheme(); if (callback_onMouseEntered) callback_onMouseEntered(event); onMouseEntered(event); @@ -376,6 +398,8 @@ namespace ogfx void Widget::__onMouseExited(const Event& event) { + m_qualifier = ""; + reloadTheme(); if (callback_onMouseExited) callback_onMouseExited(event); onMouseExited(event); @@ -478,7 +502,7 @@ namespace ogfx } } - void Widget::__applyTheme(const Theme& theme, bool propagate) + void Widget::__applyTheme(const ostd::Stylesheet& theme, bool propagate) { if (propagate && hasChildren()) m_widgets.onThemeApplied(theme); @@ -502,9 +526,9 @@ namespace ogfx setSize(static_cast(event.windowResized->new_width), static_cast(event.windowResized->new_height)); } - void RootWidget::applyTheme(const Theme& theme) + void RootWidget::applyTheme(const ostd::Stylesheet& theme) { - m_color = theme.get("window.backgroundColor", getWindow().getClearColor()); + m_color = theme.get("window.backgroundColor", getWindow().getClearColor(), getThemeID(), getThemeQualifier()); } void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx) @@ -526,17 +550,17 @@ namespace ogfx return *this; } - void Label::applyTheme(const Theme& theme) + void Label::applyTheme(const ostd::Stylesheet& theme) { - setColor(theme.get("label.textColor", ostd::Colors::White)); - setBackGroundColor(theme.get("label.backgroundColor", ostd::Colors::Transparent)); - setFontSize(theme.get("label.fontSize", 20)); - m_borderRadius = theme.get("label.borderRadius", 10); - m_borderWidth = theme.get("label.borderWidth", 2); - m_showBorder = theme.get("label.showBorder", false); - m_borderColor = theme.get("label.borderColor", ostd::Colors::White); - enableBackground(theme.get("label.showBackground", false)); - setPadding(theme.get("label.padding", { 5, 5, 5, 5 })); + setColor(theme.get("label.textColor", ostd::Colors::White, getThemeID(), getThemeQualifier())); + setBackGroundColor(theme.get("label.backgroundColor", ostd::Colors::Transparent, getThemeID(), getThemeQualifier())); + setFontSize(theme.get("label.fontSize", 20, getThemeID(), getThemeQualifier())); + m_borderRadius = theme.get("label.borderRadius", 10, getThemeID(), getThemeQualifier()); + m_borderWidth = theme.get("label.borderWidth", 2, getThemeID(), getThemeQualifier()); + m_showBorder = theme.get("label.showBorder", false, getThemeID(), getThemeQualifier()); + m_borderColor = theme.get("label.borderColor", ostd::Colors::White, getThemeID(), getThemeQualifier()); + enableBackground(theme.get("label.showBackground", false, getThemeID(), getThemeQualifier())); + setPadding(theme.get("label.padding", { 5, 5, 5, 5 }, getThemeID(), getThemeQualifier())); } void Label::onDraw(ogfx::BasicRenderer2D& gfx) diff --git a/src/ogfx/gui/Widgets.hpp b/src/ogfx/gui/Widgets.hpp index 0c1e521..d997449 100644 --- a/src/ogfx/gui/Widgets.hpp +++ b/src/ogfx/gui/Widgets.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include namespace ogfx @@ -46,7 +46,7 @@ namespace ogfx void draw(ogfx::BasicRenderer2D& gfx); void update(void); - void onThemeApplied(const Theme& theme); + void onThemeApplied(const ostd::Stylesheet& theme); void onMousePressed(const Event& event); void onMouseReleased(const Event& event); void onMouseMoved(const Event& event); @@ -72,6 +72,12 @@ namespace ogfx }; class Widget : public ostd::BaseObject, public ostd::Rectangle { + private: struct ThemeOverride + { + ostd::String fullKey; + ostd::Stylesheet::TypeVariant value; + bool propagate; + }; public: using EventCallback = std::function; public: Widget(const ostd::Rectangle& bounds, WindowCore& window); @@ -79,8 +85,9 @@ namespace ogfx ostd::Vec2 getGlobalPosition(void) const; using ostd::Rectangle::contains; bool contains(ostd::Vec2 p, bool includeBounds = false) const override; - virtual void applyTheme(const Theme& theme) = 0; - void applyThemeValue(Theme& theme, const ostd::String& key, Theme::ThemeValue value, bool propagate); + virtual void applyTheme(const ostd::Stylesheet& theme) = 0; + void addThemeOverride(const ostd::String& key, ostd::Stylesheet::TypeVariant value, const ostd::String& themeID = "", const ostd::String& qualifier = "", bool propagate = true); + void reloadTheme(void); inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { } inline virtual void onUpdate(void) { } @@ -116,7 +123,7 @@ namespace ogfx void __onWindowResized(const Event& event); void __onWIndowFocused(const Event& event); void __onWindowFocusLost(const Event& event); - void __applyTheme(const Theme& theme, bool propagate); + void __applyTheme(const ostd::Stylesheet& theme, bool propagate); inline virtual void setMousePressedCallback(EventCallback callback) { callback_onMousePressed = callback; } inline virtual void setMouseReleasedCallback(EventCallback callback) { callback_onMouseReleased = callback; } @@ -145,6 +152,9 @@ namespace ogfx inline Rectangle getPadding(void) { return m_padding; } inline bool isMouseInside(void) const { return m_mouseInside; } inline ogfx::MouseEventData::eButton getPressedMouseButton(void) const { return m_pressedButton; } + inline ostd::String getThemeID(void) const { return m_themeID; } + inline void setThemeID(const ostd::String& id) { m_themeID = id; } + inline ostd::String getThemeQualifier(void) const { return m_qualifier; } protected: inline void disableChildren(void) { m_allowChildren = false; } @@ -180,7 +190,11 @@ namespace ogfx WidgetManager m_widgets; bool m_allowChildren { true }; bool m_mouseInside { false }; - ogfx::MouseEventData::eButton m_pressedButton { ogfx::MouseEventData::eButton::None }; + MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None }; + + ostd::String m_themeID { "" }; + ostd::String m_qualifier { "" }; + std::vector m_themeOverrides; bool m_drawBox { true }; ostd::Color m_drawBoxColor { 255, 0, 0 }; @@ -196,7 +210,7 @@ namespace ogfx public: RootWidget(WindowCore& window); void onWindowResized(const Event& event) override; - void applyTheme(const Theme& theme) override; + void applyTheme(const ostd::Stylesheet& theme) override; void onDraw(ogfx::BasicRenderer2D& gfx) override; private: @@ -208,7 +222,7 @@ namespace ogfx inline Label(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); } inline Label(WindowCore& window, const ostd::String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); } Label& create(const ostd::String& text); - void applyTheme(const Theme& theme) override; + void applyTheme(const ostd::Stylesheet& theme) override; void onDraw(ogfx::BasicRenderer2D& gfx) override; void setText(const ostd::String& text); inline ostd::String getText(void) const { return m_text; } diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 620ed39..a07dca5 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -26,6 +26,18 @@ namespace ogfx { const Uint32 REDRAW_EVENT = SDL_RegisterEvents(1); + inline static const ostd::String DefaultThemeStyle = \ + "window.backgroundColor = Color(#000000FF)\n" \ + "label.textColor = Color(#FFFFFFFF)\n" \ + "label.backgroundColor = Color(#00000000)\n" \ + "label.borderColor = Color(#FFFFFFFF)\n" \ + "label.fontSize = 20\n" \ + "label.borderRadius = 20\n" \ + "label.borderWidth = 2\n" \ + "label.showBackground = false\n" \ + "label.showBorder = false\n" \ + "label.padding = Rect(5, 5, 5, 5)\n"; + WindowCore::~WindowCore(void) { __on_window_destroy(); @@ -60,6 +72,9 @@ namespace ogfx { if (m_initialized) return; SDLSysten::acquire(); + + DefaultTheme.loadFromString(DefaultThemeStyle); + m_windowWidth = width; m_windowHeight = height; m_title = title; @@ -445,7 +460,7 @@ namespace ogfx setTheme(*m_guiTheme); } - void Window::setTheme(const gui::Theme& theme) + void Window::setTheme(const ostd::Stylesheet& theme) { m_guiTheme = &theme; m_rootWidget.__applyTheme(theme, true); diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 129a37e..b01524f 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -28,7 +28,6 @@ #include #include #include -#include namespace ogfx { @@ -75,6 +74,10 @@ namespace ogfx void requestRedraw(void); void handleSignal(ostd::Signal& signal) override; + inline const ostd::Stylesheet* theme(void) const { return m_guiTheme; } + inline void loadDefaultTHeme(void) { setTheme(DefaultTheme); } + inline virtual void setTheme(const ostd::Stylesheet& theme) { } + inline bool isInitialized(void) const { return m_initialized; } inline bool isRunning(void) const { return m_running; } inline bool isVisible(void) const { return m_visible; } @@ -112,6 +115,7 @@ namespace ogfx SDL_Renderer* m_renderer { nullptr }; ostd::ConsoleOutputHandler m_out; GraphicsWindowOutputHandler m_wout; + const ostd::Stylesheet* m_guiTheme { nullptr }; private: ostd::Color m_clearColor { 10, 10, 10, 255 }; @@ -152,7 +156,9 @@ namespace ogfx public: inline static constexpr int32_t MaxBlockingEventsFPS { 240 }; inline static constexpr int32_t DefaultBlockingEventsFPS { 30 }; - inline static const gui::Theme DefaultTheme; + + private: + inline static ostd::Stylesheet DefaultTheme; }; class GraphicsWindow : public WindowCore { @@ -196,6 +202,8 @@ namespace ogfx inline Window(int32_t width, int32_t height, const ostd::String& title) { initialize(width, height, title); } void addWidget(Widget& widget); + void setTheme(const ostd::Stylesheet& theme) override; + inline virtual void onInitialize(void) { } inline virtual void onDestroy(void) { } inline virtual void onClose(void) { } @@ -203,10 +211,6 @@ namespace ogfx inline virtual void onRedraw(BasicRenderer2D& gfx) { } inline virtual void onSignal(ostd::Signal& signal) { } - inline const gui::Theme& theme(void) const { return *m_guiTheme; } - void setTheme(const gui::Theme& theme); - inline void loadDefaultTHeme(void) { setTheme(DefaultTheme); } - protected: void __on_window_init(int32_t width, int32_t height, const ostd::String& title) override; void __on_event(SDL_Event& event) override; @@ -218,9 +222,6 @@ namespace ogfx protected: BasicRenderer2D m_gfx; widgets::RootWidget m_rootWidget { *this }; - const gui::Theme* m_guiTheme { nullptr }; - - inline static const gui::Theme DefaultTheme; }; } } diff --git a/src/ostd/io/Stylesheet.cpp b/src/ostd/io/Stylesheet.cpp new file mode 100644 index 0000000..e30b964 --- /dev/null +++ b/src/ostd/io/Stylesheet.cpp @@ -0,0 +1,194 @@ +/* + OmniaFramework - A collection of useful functionality + Copyright (C) 2025 OmniaX-Dev + + This file is part of OmniaFramework. + + OmniaFramework is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OmniaFramework is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OmniaFramework. If not, see . +*/ + +#include "Stylesheet.hpp" +#include "FileSystem.hpp" +#include "Logger.hpp" + +namespace ostd +{ + Stylesheet::Stylesheet(void) + { + } + + Stylesheet& Stylesheet::clear(void) + { + m_values.clear(); + return *this; + } + + Stylesheet& Stylesheet::loadFromFile(const ostd::String& filePath) + { + ostd::String outContent = ""; + ostd::FileSystem::readTextFileRaw(filePath, outContent); + return loadFromString(outContent, filePath); + } + + Stylesheet& Stylesheet::loadFromString(const ostd::String& content, const ostd::String& filePath) + { + std::vector lines = content.tokenize("\n", false, true).getRawData(); + uint32_t lineNumber = 0; + ostd::String lineCopy = ""; + std::unordered_map variables; + for (auto& line : lines) + { + lineNumber++; + lineCopy = line; + line.trim(); + if (line.startsWith("%")) + continue; + if (line.contains("%")) + line.substr(0, line.indexOf("%")).trim(); + if (line == "") + continue; + if (line.startsWith("$")) + { + line.substr(1); + if (line.count("=") != 1 || line.endsWith("=")) + { + OX_WARN("Invalid variable in theme line. File: <%s:%d>:\n\t%s", filePath.c_str(), lineNumber, lineCopy.c_str()); + continue; + } + ostd::String varName = line.new_substr(0, line.indexOf("=")).trim(); + ostd::String varValue = line.new_substr(line.indexOf("=") + 1).trim(); + if (!varName.regexMatches(m_validNameRegex)) + { + OX_WARN("Invalid variable name in theme line. File: <%s:%d>:\n\t%s", filePath.c_str(), lineNumber, lineCopy.c_str()); + continue; + } + variables["$" + varName] = varValue; + continue; + } + for (const auto&[var, val] : variables) + line.replaceAll(var, val); + if (!parseThemeFileLine(line)) + OX_WARN("Invalid theme line: File <%s:%d>:\n\t%s", filePath.c_str(), lineNumber, lineCopy.c_str()); + } + return *this; + } + + void Stylesheet::set(const std::string& key, TypeVariant value, const ostd::String& themeID) + { + ostd::String fullKey = "@" + themeID + "." + key; + // std::cout << "SET: " << fullKey << "\n"; + setFull(fullKey, value); + } + + void Stylesheet::setFull(const ostd::String& fullKey, TypeVariant value) + { + m_values[fullKey] = std::move(value); + } + + const Stylesheet::TypeVariant* Stylesheet::getVariant(const ostd::String& key, const ostd::String& themeID, const ostd::String& qualifier) const + { + if (auto v = getFull("@" + themeID + ":" + qualifier + "." + key)) + { + return v; + } + else if (auto v = getFull("@:" + qualifier + "." + key)) + { + return v; + } + else if (auto v = getFull("@" + themeID + "." + key)) + { + return v; + } + else if (auto v = getFull("@." + key)) + { + return v; + } + return nullptr; + } + + const Stylesheet::TypeVariant* Stylesheet::getFull(const ostd::String& fullKey) const + { + // std::cout << " GET: " << key << "\n"; + auto it = m_values.find(fullKey); + return it != m_values.end() ? &it->second : nullptr; + } + + bool Stylesheet::parseThemeFileLine(const ostd::String& line) + { + if (!line.contains("=")) + return false; + ostd::String key = line.new_substr(0, line.indexOf("=")).trim(); + ostd::String valuePreserveCase = line.new_substr(line.indexOf("=") + 1).trim(); + ostd::String value = line.new_substr(line.indexOf("=") + 1).trim().toLower(); + if (key == "") + return false; + ostd::String themeID = ""; + if (key.startsWith("@")) + { + if (key.indexOf(" ") < 2) + return false; + themeID = key.new_substr(1, key.indexOf(" ")).trim(); + key.substr(key.indexOf(" ") + 1).trim(); + } + if (value.isInt()) + set(key, static_cast(value.toInt()), themeID); + else if (value.isNumeric(true)) + set(key, value.toFloat(), themeID); + else if (value == "true" || value == "false") + set(key, value == "true", themeID); + else if (value.startsWith("\"") && value.endsWith("\"")) + { + valuePreserveCase.substr(1, value.len() - 1); + set(key, valuePreserveCase, themeID); + } + else if (value.startsWith("color(") && value.endsWith(")")) + { + value.substr(6, value.len() - 1).trim(); + set(key, ostd::Color(value), themeID); + } + else if (value.startsWith("vec2(") && value.endsWith(")")) + { + value.substr(4, value.len() - 1).trim(); + auto tokens = value.tokenize(","); + if (tokens.count() != 2) + return false; + std::vector vec; + for (const auto& tok : tokens) + { + if (!tok.isNumeric(true)) + return false; + vec.push_back(tok.toFloat()); + } + set(key, ostd::Vec2(vec[0], vec[1]), themeID); + } + else if (value.startsWith("rect(") && value.endsWith(")")) + { + value.substr(5, value.len() - 1).trim(); + auto tokens = value.tokenize(","); + if (tokens.count() != 4) + return false; + std::vector vec; + for (const auto& tok : tokens) + { + if (!tok.isNumeric(true)) + return false; + vec.push_back(tok.toFloat()); + } + set(key, ostd::Rectangle(vec[0], vec[1], vec[2], vec[3]), themeID); + } + else + return false; + return true; + } +} diff --git a/src/ostd/io/Stylesheet.hpp b/src/ostd/io/Stylesheet.hpp new file mode 100644 index 0000000..485006f --- /dev/null +++ b/src/ostd/io/Stylesheet.hpp @@ -0,0 +1,61 @@ +/* + OmniaFramework - A collection of useful functionality + Copyright (C) 2025 OmniaX-Dev + + This file is part of OmniaFramework. + + OmniaFramework is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OmniaFramework is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OmniaFramework. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +namespace ostd +{ + class Stylesheet + { + public: using TypeVariant = std::variant; + public: + Stylesheet(void); + Stylesheet& clear(void); + Stylesheet& loadFromFile(const ostd::String& filePath); + Stylesheet& loadFromString(const ostd::String& content, const ostd::String& filePath = "memory://"); + void set(const std::string& key, TypeVariant value, const ostd::String& themeID); + void setFull(const ostd::String& fullKey, TypeVariant value); + const TypeVariant* getVariant(const ostd::String& key, const ostd::String& themeID, const ostd::String& qualifier) const; + const TypeVariant* getFull(const ostd::String& fullKey) const; + + template + inline T get(const ostd::String& key, const T& fallback, const ostd::String& themeID, const ostd::String& qualifier) const + { + if (auto v = getVariant(key, themeID, qualifier)) + { + if (auto p = std::get_if(v)) + return *p; + } + return fallback; + } + + private: + bool parseThemeFileLine(const ostd::String& line); + + private: + std::unordered_map m_values; + const ostd::String m_validNameRegex { "^[A-Za-z_][A-Za-z0-9_]*$" }; + }; +} diff --git a/src/ostd/string/String.cpp b/src/ostd/string/String.cpp index 7381b6e..7996be2 100644 --- a/src/ostd/string/String.cpp +++ b/src/ostd/string/String.cpp @@ -511,6 +511,20 @@ namespace ostd return m_data.ends_with(str); } + bool String::regexMatches(const String& regex_pattern, bool case_insensitive) const + { + try + { + boost::regex rgx(regex_pattern.cpp_str(), case_insensitive ? boost::regex_constants::icase : boost::regex_constants::normal); + return boost::regex_search(m_data, rgx); + } + catch (const boost::regex_error& err) + { + std::cerr << err.what() << '\n'; //TODO: Better error handling + return false; + } + } + uint32_t String::count(const String& str) const { Tokens tok = tokenize(str, false, true); diff --git a/src/ostd/string/String.hpp b/src/ostd/string/String.hpp index d5d8733..fd58cd9 100644 --- a/src/ostd/string/String.hpp +++ b/src/ostd/string/String.hpp @@ -162,6 +162,7 @@ namespace ostd bool contains(const String& str) const; bool startsWith(const String& str) const; bool endsWith(const String& str) const; + bool regexMatches(const String& regex_pattern, bool case_insensitive = false) const; uint32_t count(const String& str) const; int32_t indexOf(char c, uint32_t start = 0) const; int32_t indexOf(const String& str, uint32_t start = 0) const; diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 1c692ec..28c63c1 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -51,21 +51,22 @@ class Window : public ogfx::gui::Window { m_label1.setPosition(100, 200); m_label1.setText("Hello World!"); - m_label1.setMouseEnteredCallback([&](const ogfx::gui::Event& event) -> void { - m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkBlue, false); - this->setCursor(eCursor::Move); - }); - m_label1.setMouseExitedCallback([&](const ogfx::gui::Event& event) -> void { - m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkRed, false); - this->setCursor(eCursor::Default); - }); - m_label1.setMouseDraggedCallback([&](const ogfx::gui::Event& event) -> void { - m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkGreen, false); - }); + // m_label1.setMouseEnteredCallback([&](const ogfx::gui::Event& event) -> void { + // m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkBlue, false); + // this->setCursor(eCursor::Move); + // }); + // m_label1.setMouseExitedCallback([&](const ogfx::gui::Event& event) -> void { + // m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkRed, false); + // this->setCursor(eCursor::Default); + // }); + // m_label1.setMouseDraggedCallback([&](const ogfx::gui::Event& event) -> void { + // m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkGreen, false); + // }); addWidget(m_label1); m_label2.setPosition(100, 400); m_label2.setText("Ciccia Bella!"); + m_label2.setThemeID("testLabel"); addWidget(m_label2); m_theme.loadFromFile("./testTheme.txt"); @@ -90,7 +91,7 @@ class Window : public ogfx::gui::Window private: ogfx::gui::widgets::Label m_label1 { *this }; ogfx::gui::widgets::Label m_label2 { *this }; - ogfx::gui::Theme m_theme = ogfx::gui::Theme(); + ostd::Stylesheet m_theme; }; int main(int argc, char** argv)