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)