Added the ability to load Gui themes from a file

This commit is contained in:
OmniaX-Dev 2026-04-04 07:16:24 +02:00
parent 45c36339ec
commit f33b6de559
9 changed files with 172 additions and 19 deletions

13
extra/testTheme.txt Normal file
View file

@ -0,0 +1,13 @@
% Window
window.backgroundColor = Color(rgba(40, 0, 0, 255))
% Label
label.textColor = Color(#FFFFFFFF)
label.backgroundColor = Color(#FFFF80FF)
label.borderColor = Color(#FFFFFFFF)
label.fontSize = 30
label.borderRadius = 20
label.borderWidth = 2
label.showBackground = false
label.showBorder = false
label.padding = Rect(5, 5, 5, 5)

View file

@ -1,5 +1,5 @@
***Add Color constants to ostd::Color
Add MouseEntered/MouseExited callbacks
Fix mouse detection currently using local coordinates
***Add MouseEntered/MouseExited callbacks
***Fix mouse detection currently using local coordinates
implement JSON theme loading
Implemenmt MouseDragged callback
***Implemenmt MouseDragged callback

View file

@ -20,6 +20,9 @@
#pragma once
#include "io/FileSystem.hpp"
#include "io/Logger.hpp"
#include "string/String.hpp"
#include <ostd/data/Color.hpp>
#include <ostd/math/Geometry.hpp>
#include <variant>
@ -31,21 +34,22 @@ namespace ogfx
{
struct Theme
{
public: using ThemeValue = std::variant<int, float, bool, ostd::String, ostd::Color, ostd::Rectangle, ostd::Vec2>;
public: using ThemeValue = std::variant<int32_t, float, bool, ostd::String, ostd::Color, ostd::Rectangle, ostd::Vec2>;
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::Transparent);
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", false);
set("label.showBackground", true);
set("label.showBorder", false);
set("label.padding", ostd::Rectangle { 5, 5, 5, 5 });
}
@ -56,9 +60,27 @@ namespace ogfx
return *this;
}
inline Theme& loadFromJsonFile(const ostd::String& jsonFilePath)
inline Theme& loadFromFile(const ostd::String& filePath)
{
return *this; //TODO: Implement
std::vector<ostd::String> 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)
@ -83,6 +105,63 @@ namespace ogfx
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<int32_t>(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<float> 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<float> 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<ostd::String, ThemeValue> m_values;
};

View file

@ -19,6 +19,7 @@
*/
#include "Widgets.hpp"
#include "gui/Events.hpp"
#include "utils/Keycodes.hpp"
#include <ogfx/render/BasicRenderer.hpp>
#include <ogfx/gui/Window.hpp>
@ -167,8 +168,26 @@ namespace ogfx
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->contains(event.mouse->position_x, event.mouse->position_y, true))
{
if (w->m_mouseInside)
{
w->m_mouseInside = false;
w->__onMouseExited(event);
}
continue;
}
if (!w->m_mouseInside)
{
w->m_mouseInside = true;
w->__onMouseEntered(event);
}
else
{
if (w->m_pressedButton != ogfx::MouseEventData::eButton::None)
w->__onMouseDragged(event);
else
w->__onMouseMoved(event);
}
if (event.isHandled())
break;
}
@ -319,11 +338,13 @@ namespace ogfx
if (callback_onMousePressed)
callback_onMousePressed(event);
onMousePressed(event);
m_pressedButton = event.mouse->button;
}
}
void Widget::__onMouseReleased(const Event& event)
{
m_pressedButton = ogfx::MouseEventData::eButton::None;
if (hasChildren())
m_widgets.onMouseReleased(event);
if (!event.isHandled())
@ -346,6 +367,20 @@ namespace ogfx
}
}
void Widget::__onMouseEntered(const Event& event)
{
if (callback_onMouseEntered)
callback_onMouseEntered(event);
onMouseEntered(event);
}
void Widget::__onMouseExited(const Event& event)
{
if (callback_onMouseExited)
callback_onMouseExited(event);
onMouseExited(event);
}
void Widget::__onMouseDragged(const Event& event)
{
if (hasChildren())
@ -372,6 +407,7 @@ namespace ogfx
void Widget::__onKeyReleased(const Event& event)
{
m_pressedButton = ogfx::MouseEventData::eButton::None;
if (hasChildren())
m_widgets.onKeyReleased(event);
if (!event.isHandled())
@ -455,10 +491,9 @@ namespace ogfx
{
RootWidget::RootWidget(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window)
{
disableDrawBox();
m_rootChild = true;
setSize(static_cast<float>(window.getWindowWidth()), static_cast<float>(window.getWindowHeight()));
enableDrawBox();
setDrawBoxColor(window.getClearColor());
setTypeName("ogfx::gui::widgets::RootWidget");
}
@ -469,7 +504,12 @@ namespace ogfx
void RootWidget::applyTheme(const Theme& theme)
{
setDrawBoxColor(theme.get<ostd::Color>("window.backgroundColor", getWindow().getClearColor()));
m_color = theme.get<ostd::Color>("window.backgroundColor", getWindow().getClearColor());
}
void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx)
{
gfx.fillRect({ 0, 0, static_cast<float>(getWindow().getWindowWidth()), static_cast<float>(getWindow().getWindowHeight()) }, m_color);
}

View file

@ -20,6 +20,7 @@
#pragma once
#include "gui/Events.hpp"
#include <ostd/data/BaseObject.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/data/Color.hpp>
@ -87,6 +88,8 @@ namespace ogfx
inline virtual void onMousePressed(const Event& event) { }
inline virtual void onMouseReleased(const Event& event) { }
inline virtual void onMouseMoved(const Event& event) { }
inline virtual void onMouseEntered(const Event& event) { }
inline virtual void onMouseExited(const Event& event) { }
inline virtual void onMouseDragged(const Event& event) { }
inline virtual void onKeyPressed(const Event& event) { }
inline virtual void onKeyReleased(const Event& event) { }
@ -103,6 +106,8 @@ namespace ogfx
void __onMousePressed(const Event& event);
void __onMouseReleased(const Event& event);
void __onMouseMoved(const Event& event);
void __onMouseEntered(const Event& event);
void __onMouseExited(const Event& event);
void __onMouseDragged(const Event& event);
void __onKeyPressed(const Event& event);
void __onKeyReleased(const Event& event);
@ -116,6 +121,8 @@ namespace ogfx
inline virtual void setMousePressedCallback(EventCallback callback) { callback_onMousePressed = callback; }
inline virtual void setMouseReleasedCallback(EventCallback callback) { callback_onMouseReleased = callback; }
inline virtual void setMouseMovedCallback(EventCallback callback) { callback_onMouseMoved = callback; }
inline virtual void setMouseEnteredCallback(EventCallback callback) { callback_onMouseEntered = callback; }
inline virtual void setMouseExitedCallback(EventCallback callback) { callback_onMouseExited = callback; }
inline virtual void setMouseDraggedCallback(EventCallback callback) { callback_onMouseDragged = callback; }
inline virtual void setKeyPressedCallback(EventCallback callback) { callback_onKeyPressed = callback; }
inline virtual void setKeyReleasedCallback(EventCallback callback) { callback_onKeyReleased = callback; }
@ -136,6 +143,8 @@ namespace ogfx
inline bool isChildrenEnabled(void) const { return m_allowChildren; }
inline void setPadding(const ostd::Rectangle& pad) { m_padding = pad; }
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; }
protected:
inline void disableChildren(void) { m_allowChildren = false; }
@ -151,6 +160,8 @@ namespace ogfx
EventCallback callback_onMousePressed { nullptr };
EventCallback callback_onMouseReleased { nullptr };
EventCallback callback_onMouseMoved { nullptr };
EventCallback callback_onMouseEntered { nullptr };
EventCallback callback_onMouseExited { nullptr };
EventCallback callback_onMouseDragged { nullptr };
EventCallback callback_onKeyPressed { nullptr };
EventCallback callback_onKeyReleased { nullptr };
@ -168,6 +179,8 @@ namespace ogfx
int32_t m_zIndex { -1 };
WidgetManager m_widgets;
bool m_allowChildren { true };
bool m_mouseInside { false };
ogfx::MouseEventData::eButton m_pressedButton { ogfx::MouseEventData::eButton::None };
bool m_drawBox { true };
ostd::Color m_drawBoxColor { 255, 0, 0 };
@ -184,8 +197,10 @@ namespace ogfx
RootWidget(WindowCore& window);
void onWindowResized(const Event& event) override;
void applyTheme(const Theme& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
private:
ostd::Color m_color { ostd::Colors::Transparent };
};
class Label : public Widget
{

View file

@ -123,6 +123,7 @@ namespace ogfx
connectSignal(ostd::BuiltinSignals::WindowLostFocus);
__on_window_init(width, height, title);
setSize(m_windowWidth, m_windowHeight);
}
void WindowCore::mainLoop(void)
@ -457,6 +458,7 @@ namespace ogfx
m_gfx.init(*this);
loadDefaultTHeme();
onInitialize();
m_rootWidget.setSize(static_cast<float>(width), static_cast<float>(height));
}
void Window::__on_window_close(void)

View file

@ -215,7 +215,7 @@ namespace ogfx
void __main_loop(void) override;
void __on_signal(ostd::Signal& signal) override;
private:
protected:
BasicRenderer2D m_gfx;
widgets::RootWidget m_rootWidget { *this };
const gui::Theme* m_guiTheme { nullptr };

View file

@ -445,7 +445,7 @@ namespace ostd
str = str.substr(2);
base = 2;
}
return strtol(str.c_str(), NULL, base);
return strtoul(str.c_str(), NULL, base);
}
float String::toFloat(void) const

View file

@ -51,20 +51,24 @@ class Window : public ogfx::gui::Window
{
m_label1.setPosition(100, 200);
m_label1.setText("Hello World!");
m_label1.setMouseMovedCallback([&](const ogfx::gui::Event& event) -> void {
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!");
addWidget(m_label2);
m_theme.set("label.textColor", ostd::Colors::White);
m_theme.set("label.backgroundColor", ostd::Colors::DarkRed);
m_theme.set("label.showBackground", true);
m_theme.set("label.borderRadius", 0);
m_theme.loadFromFile("./testTheme.txt");
setTheme(m_theme);
}