diff --git a/extra/testTheme.txt b/extra/testTheme.txt new file mode 100644 index 0000000..f5c3c0e --- /dev/null +++ b/extra/testTheme.txt @@ -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) diff --git a/other/TODO.txt b/other/TODO.txt index 8144f94..2ec5c6d 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 +***Add MouseEntered/MouseExited callbacks +***Fix mouse detection currently using local coordinates implement JSON theme loading -Implemenmt MouseDragged callback +***Implemenmt MouseDragged callback diff --git a/src/ogfx/gui/Themes.hpp b/src/ogfx/gui/Themes.hpp index c4a9b96..dacc336 100644 --- a/src/ogfx/gui/Themes.hpp +++ b/src/ogfx/gui/Themes.hpp @@ -20,6 +20,9 @@ #pragma once +#include "io/FileSystem.hpp" +#include "io/Logger.hpp" +#include "string/String.hpp" #include #include #include @@ -31,21 +34,22 @@ namespace ogfx { struct Theme { - public: using ThemeValue = std::variant; + 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::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 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(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 30dc1d4..f2a209e 100644 --- a/src/ogfx/gui/Widgets.cpp +++ b/src/ogfx/gui/Widgets.cpp @@ -19,6 +19,7 @@ */ #include "Widgets.hpp" +#include "gui/Events.hpp" #include "utils/Keycodes.hpp" #include #include @@ -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; - w->__onMouseMoved(event); + } + 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(window.getWindowWidth()), static_cast(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("window.backgroundColor", getWindow().getClearColor())); + m_color = theme.get("window.backgroundColor", getWindow().getClearColor()); + } + + void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx) + { + gfx.fillRect({ 0, 0, static_cast(getWindow().getWindowWidth()), static_cast(getWindow().getWindowHeight()) }, m_color); } diff --git a/src/ogfx/gui/Widgets.hpp b/src/ogfx/gui/Widgets.hpp index f5f567f..0c1e521 100644 --- a/src/ogfx/gui/Widgets.hpp +++ b/src/ogfx/gui/Widgets.hpp @@ -20,6 +20,7 @@ #pragma once +#include "gui/Events.hpp" #include #include #include @@ -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 { diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 4b9d4d7..620ed39 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -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(width), static_cast(height)); } void Window::__on_window_close(void) diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 0bb4cd3..129a37e 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -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 }; diff --git a/src/ostd/string/String.cpp b/src/ostd/string/String.cpp index af0e588..7381b6e 100644 --- a/src/ostd/string/String.cpp +++ b/src/ostd/string/String.cpp @@ -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 diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 3bb5bcb..1c692ec 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -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); }