From 924da0dba69fb80f664802d237b2f3a9535adc38 Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Thu, 2 Apr 2026 06:19:20 +0200 Subject: [PATCH] Started work on gui Widget system --- CMakeLists.txt | 2 +- other/TODO.txt | 6 +- src/ogfx/gui/Events.hpp | 24 ++ src/ogfx/gui/Themes.hpp | 90 +++++++ src/ogfx/gui/Widget.cpp | 29 -- src/ogfx/gui/Widget.hpp | 36 --- src/ogfx/gui/Widgets.cpp | 526 +++++++++++++++++++++++++++++++++++++ src/ogfx/gui/Widgets.hpp | 226 ++++++++++++++++ src/ogfx/gui/Window.cpp | 95 ++++++- src/ogfx/gui/Window.hpp | 18 ++ src/ostd/data/Color.hpp | 57 ++++ src/ostd/math/Geometry.hpp | 9 +- src/ostd/utils/Signals.cpp | 12 + src/ostd/utils/Signals.hpp | 4 + src/test/GuiTest.cpp | 23 +- 15 files changed, 1082 insertions(+), 75 deletions(-) create mode 100644 src/ogfx/gui/Themes.hpp delete mode 100644 src/ogfx/gui/Widget.cpp delete mode 100644 src/ogfx/gui/Widget.hpp create mode 100644 src/ogfx/gui/Widgets.cpp create mode 100644 src/ogfx/gui/Widgets.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bf793f7..a33ba72 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ list(APPEND OGFX_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/RawTextInput.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/Window.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/WindowOutputHandler.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/Widget.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/Widgets.cpp # render ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/BasicRenderer.cpp diff --git a/other/TODO.txt b/other/TODO.txt index 37be370..8144f94 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -1 +1,5 @@ -Add Color constants to ostd::Color \ No newline at end of file +***Add Color constants to ostd::Color +Add MouseEntered/MouseExited callbacks +Fix mouse detection currently using local coordinates +implement JSON theme loading +Implemenmt MouseDragged callback diff --git a/src/ogfx/gui/Events.hpp b/src/ogfx/gui/Events.hpp index b2040d9..ce6b7d0 100644 --- a/src/ogfx/gui/Events.hpp +++ b/src/ogfx/gui/Events.hpp @@ -25,6 +25,10 @@ namespace ogfx { class WindowCore; + namespace gui + { + class Widget; + } class WindowResizedData : public ostd::BaseObject { public: @@ -55,6 +59,7 @@ namespace ogfx float position_x; float position_y; eButton button; + gui::Widget* mousePressedOnWidget { nullptr }; WindowCore& parentWindow; }; class KeyEventData : public ostd::BaseObject @@ -73,4 +78,23 @@ namespace ogfx eKeyEvent eventType; WindowCore& parentWindow; }; + namespace gui + { + class Event + { + public: + inline Event(WindowCore& _window) : window(_window) { } + inline void handle(void) { m_handled = true; } + inline bool isHandled(void) const { return m_handled; } + + public: + WindowCore& window; + WindowResizedData* windowResized { nullptr }; + MouseEventData* mouse { nullptr }; + KeyEventData* keyboard { nullptr }; + + private: + bool m_handled { false }; + }; + } } diff --git a/src/ogfx/gui/Themes.hpp b/src/ogfx/gui/Themes.hpp new file mode 100644 index 0000000..c4a9b96 --- /dev/null +++ b/src/ogfx/gui/Themes.hpp @@ -0,0 +1,90 @@ +/* + 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 ogfx +{ + namespace gui + { + struct Theme + { + public: using ThemeValue = std::variant; + public: + inline Theme(bool blank = false) + { + if (blank) return; + + set("window.backgroundColor", ostd::Colors::Black); + + set("label.textColor", ostd::Colors::White); + set("label.backgroundColor", ostd::Colors::Transparent); + set("label.borderColor", ostd::Colors::White); + set("label.fontSize", 20); + set("label.borderRadius", 20); + set("label.borderWidth", 2); + set("label.showBackground", false); + set("label.showBorder", false); + set("label.padding", ostd::Rectangle { 5, 5, 5, 5 }); + } + + inline Theme& blank(void) + { + m_values.clear(); + return *this; + } + + inline Theme& loadFromJsonFile(const ostd::String& jsonFilePath) + { + return *this; //TODO: Implement + } + + 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: + std::unordered_map m_values; + }; + } +} diff --git a/src/ogfx/gui/Widget.cpp b/src/ogfx/gui/Widget.cpp deleted file mode 100644 index f6ae4e3..0000000 --- a/src/ogfx/gui/Widget.cpp +++ /dev/null @@ -1,29 +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 . -*/ - -#include "Widget.hpp" - -namespace ogfx -{ - namespace gui - { - - } -} diff --git a/src/ogfx/gui/Widget.hpp b/src/ogfx/gui/Widget.hpp deleted file mode 100644 index 38b8d75..0000000 --- a/src/ogfx/gui/Widget.hpp +++ /dev/null @@ -1,36 +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 -#include - -namespace ogfx -{ - namespace gui - { - class Widget : public ostd::BaseObject, public ostd::Rectangle - { - public: - private: - }; - } -} diff --git a/src/ogfx/gui/Widgets.cpp b/src/ogfx/gui/Widgets.cpp new file mode 100644 index 0000000..812f01b --- /dev/null +++ b/src/ogfx/gui/Widgets.cpp @@ -0,0 +1,526 @@ +/* + 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 "Widgets.hpp" +#include +#include + +namespace ogfx +{ + namespace gui + { + WidgetManager::WidgetManager(WindowCore& window, Widget& owner) : m_window(window), m_owner(owner) + { + // + } + + bool WidgetManager::hasWidget(Widget& widget) + { + bool found = std::ranges::find(m_widgetList, &widget) != m_widgetList.end(); + return found && widget.isValid(); + } + + bool WidgetManager::requestFocus(Widget& widget) + { + if (!hasWidget(widget)) return false; + if (&widget == m_focused) return true; + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + if (w->m_focused) + { + w->m_focused = false; + w->onFocusLost(Event(m_window)); + } + else + w->m_focused = false; + } + widget.m_focused = true; + widget.onFocusGained(Event(m_window)); + m_focused = &widget; + return true; + } + + bool WidgetManager::addWidget(Widget& widget) + { + if (hasWidget(widget)) return false; + widget.m_parent = &m_owner; + m_widgetList.push_back(&widget); + std::ranges::sort(m_widgetList, {}, [](Widget* w) { + return w->m_zIndex; + }); + return true; + } + + Widget* WidgetManager::focusNext(void) + { + if (m_widgetList.empty()) + return nullptr; + + Widget* next = nullptr; + Widget* smallest = nullptr; + + int32_t currentTabIndex = (m_focused != nullptr ? m_focused->m_tabIndex : std::numeric_limits::max()); + for (Widget* w : m_widgetList) + { + int tab_i = w->m_tabIndex; + if (tab_i < 0) continue; + if (!smallest || tab_i < smallest->m_tabIndex) + smallest = w; + if (tab_i > currentTabIndex) + { + if (!next || tab_i < next->m_tabIndex) + next = w; + } + } + Widget* w = next ? next : smallest; + requestFocus(*w); + return w; + } + + void WidgetManager::draw(ogfx::BasicRenderer2D& gfx) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__draw(gfx); + } + } + + void WidgetManager::update(void) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__update(); + } + } + + void WidgetManager::onThemeApplied(const Theme& theme) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__applyTheme(theme, true); + } + } + + void WidgetManager::onMousePressed(const Event& event) + { + for (int32_t i = m_widgetList.size() - 1; i >= 0; i--) + { + Widget* w = m_widgetList[i]; + if (w == nullptr) continue; + if (w->isInvalid()) continue; + if (!w->contains(event.mouse->position_x, event.mouse->position_y, true)) + continue; + w->__onMousePressed(event); + m_mousePressedOnWidget = w; + if (event.isHandled()) + break; + } + } + + void WidgetManager::onMouseReleased(const Event& event) + { + for (int32_t i = m_widgetList.size() - 1; i >= 0; i--) + { + Widget* w = m_widgetList[i]; + if (w == nullptr) continue; + if (w->isInvalid()) continue; + event.mouse->mousePressedOnWidget = m_mousePressedOnWidget; + w->__onMouseReleased(event); + requestFocus(*w); + m_mousePressedOnWidget = nullptr; + if (event.isHandled()) + break; + } + } + + void WidgetManager::onMouseMoved(const Event& event) + { + for (int32_t i = m_widgetList.size() - 1; i >= 0; i--) + { + Widget* w = m_widgetList[i]; + if (w == nullptr) continue; + if (w->isInvalid()) continue; + if (!w->contains(event.mouse->position_x, event.mouse->position_y, true)) + continue; + w->__onMouseMoved(event); + if (event.isHandled()) + break; + } + } + + void WidgetManager::onMouseDragged(const Event& event) + { + for (int32_t i = m_widgetList.size() - 1; i >= 0; i--) + { + Widget* w = m_widgetList[i]; + if (w == nullptr) continue; + if (w->isInvalid()) continue; + if (!w->contains(event.mouse->position_x, event.mouse->position_y, true)) + continue; + w->__onMouseDragged(event); + if (event.isHandled()) + break; + } + } + + void WidgetManager::onKeyPressed(const Event& event) + { + if (!m_focused) return; + m_focused->__onKeyPressed(event); + } + + void WidgetManager::onKeyReleased(const Event& event) + { + if (!m_focused) return; + m_focused->__onKeyReleased(event); + if (m_tabNavigationEnabled && event.keyboard->keyCode == SDLK_TAB) + focusNext(); + } + + void WidgetManager::onTextEntered(const Event& event) + { + if (!m_focused) return; + m_focused->__onTextEntered(event); + } + + void WidgetManager::onWindowClosed(const Event& event) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__onWindowClosed(event); + if (event.isHandled()) + break; + } + } + + void WidgetManager::onWindowResized(const Event& event) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__onWindowResized(event); + if (event.isHandled()) + break; + } + } + + void WidgetManager::onWindowFocused(const Event& event) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__onWIndowFocused(event); + if (event.isHandled()) + break; + } + } + + void WidgetManager::onWindowFocusLost(const Event& event) + { + for (auto& w : m_widgetList) + { + if (w == nullptr) continue; + if (w->isInvalid()) continue; + w->__onWindowFocusLost(event); + if (event.isHandled()) + break; + } + } + + + + + Widget::Widget(const ostd::Rectangle& bounds, WindowCore& window) : Rectangle(bounds), m_widgets(window, *this) + { + m_window = &window; + } + + bool Widget::addChild(Widget& child) + { + if (!m_allowChildren) + return false; + return m_widgets.addWidget(child); + } + + ostd::Vec2 Widget::getGlobalPosition(void) const + { + ostd::Vec2 glob = getPosition(); + if (!m_rootChild && m_parent != nullptr) + glob += m_parent->getGlobalPosition(); + return glob; + } + + void Widget::applyThemeValue(Theme& theme, const ostd::String& key, Theme::ThemeValue value, bool propagate) + { + auto currentValue = theme.__get(key); + if (currentValue == nullptr) return; + theme.set(key, value); + __applyTheme(theme, propagate); + theme.set(key, *currentValue); + } + + void Widget::__draw(ogfx::BasicRenderer2D& gfx) + { + if (isDrawBoxEnabled()) + gfx.fillRect({ getGlobalPosition(), getSize() }, getDrawBoxColor()); + onDraw(gfx); + if (hasChildren()) + m_widgets.draw(gfx); + } + + void Widget::__update(void) + { + onUpdate(); + if (hasChildren()) + m_widgets.update(); + } + + void Widget::__onMousePressed(const Event& event) + { + if (hasChildren()) + m_widgets.onMousePressed(event); + if (!event.isHandled()) + { + if (callback_onMousePressed) + callback_onMousePressed(event); + onMousePressed(event); + } + } + + void Widget::__onMouseReleased(const Event& event) + { + if (hasChildren()) + m_widgets.onMouseReleased(event); + if (!event.isHandled()) + { + if (callback_onMouseReleased) + callback_onMouseReleased(event); + onMouseReleased(event); + } + } + + void Widget::__onMouseMoved(const Event& event) + { + if (hasChildren()) + m_widgets.onMouseMoved(event); + if (!event.isHandled()) + { + if (callback_onMouseMoved) + callback_onMouseMoved(event); + onMouseMoved(event); + } + } + + void Widget::__onMouseDragged(const Event& event) + { + if (hasChildren()) + m_widgets.onMouseDragged(event); + if (!event.isHandled()) + { + if (callback_onMouseDragged) + callback_onMouseDragged(event); + onMouseDragged(event); + } + } + + void Widget::__onKeyPressed(const Event& event) + { + if (hasChildren()) + m_widgets.onKeyPressed(event); + if (!event.isHandled()) + { + if (callback_onKeyPressed) + callback_onKeyPressed(event); + onKeyPressed(event); + } + } + + void Widget::__onKeyReleased(const Event& event) + { + if (hasChildren()) + m_widgets.onKeyReleased(event); + if (!event.isHandled()) + { + if (callback_onKeyReleased) + callback_onKeyReleased(event); + onKeyReleased(event); + } + } + + void Widget::__onTextEntered(const Event& event) + { + if (hasChildren()) + m_widgets.onTextEntered(event); + if (!event.isHandled()) + { + if (callback_onTextEntered) + callback_onTextEntered(event); + onTextEntered(event); + } + } + + void Widget::__onWindowClosed(const Event& event) + { + if (hasChildren()) + m_widgets.onWindowClosed(event); + if (!event.isHandled()) + { + if (callback_onWindowClosed) + callback_onWindowClosed(event); + onWindowClosed(event); + } + } + + void Widget::__onWindowResized(const Event& event) + { + if (hasChildren()) + m_widgets.onWindowResized(event); + if (!event.isHandled()) + { + if (callback_onWindowResized) + callback_onWindowResized(event); + onWindowResized(event); + } + } + + void Widget::__onWIndowFocused(const Event& event) + { + if (hasChildren()) + m_widgets.onWindowFocused(event); + if (!event.isHandled()) + { + if (callback_onWindowFocused) + callback_onWindowFocused(event); + onWindowFocused(event); + } + } + + void Widget::__onWindowFocusLost(const Event& event) + { + if (hasChildren()) + m_widgets.onWindowFocusLost(event); + if (!event.isHandled()) + { + if (callback_onWindowFocusLost) + callback_onWindowFocusLost(event); + onWindowFocusLost(event); + } + } + + void Widget::__applyTheme(const Theme& theme, bool propagate) + { + if (propagate && hasChildren()) + m_widgets.onThemeApplied(theme); + applyTheme(theme); + } + + + + namespace widgets + { + RootWidget::RootWidget(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) + { + m_rootChild = true; + setSize(static_cast(window.getWindowWidth()), static_cast(window.getWindowHeight())); + enableDrawBox(); + setDrawBoxColor(window.getClearColor()); + setTypeName("ogfx::gui::widgets::RootWidget"); + } + + void RootWidget::onWindowResized(const Event& event) + { + setSize(static_cast(event.windowResized->new_width), static_cast(event.windowResized->new_height)); + } + + void RootWidget::applyTheme(const Theme& theme) + { + setDrawBoxColor(theme.get("window.backgroundColor", getWindow().getClearColor())); + } + + + + + Label& Label::create(const ostd::String& text) + { + setText(text); + setPadding({ 5, 5, 5, 5 }); + setTypeName("ogfx::gui::widgets::Label"); + disableDrawBox(); + enableBackground(false); + validate(); + return *this; + } + + void Label::applyTheme(const Theme& 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 })); + } + + void Label::onDraw(ogfx::BasicRenderer2D& gfx) + { + if (m_textChanged) + __update_size(gfx); + if (m_showBackground) + gfx.fillRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderRadius); + if (m_showBorder) + gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); + gfx.drawString(getText(), getGlobalPosition() + ostd::Vec2 { getPadding().left(), getPadding().top() }, getColor()); + } + + void Label::setText(const ostd::String& text) + { + m_text = text; + m_textChanged = true; + } + + void Label::__update_size(ogfx::BasicRenderer2D& gfx) + { + auto size = gfx.getStringSize(getText(), getFontSize()); + size.x += getPadding().left(); + size.x += getPadding().right(); + size.y += getPadding().top(); + size.y += getPadding().bottom(); + setSize({ static_cast(size.x), static_cast(size.y) }); + m_textChanged = false; + } + + } + } +} diff --git a/src/ogfx/gui/Widgets.hpp b/src/ogfx/gui/Widgets.hpp new file mode 100644 index 0000000..a476a41 --- /dev/null +++ b/src/ogfx/gui/Widgets.hpp @@ -0,0 +1,226 @@ +/* + 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 +#include + +namespace ogfx +{ + class BasicRenderer2D; + class WindowCore; + namespace gui + { + class Event; + class Widget; + class WidgetManager + { + public: + WidgetManager(WindowCore& window, Widget& owner); + bool hasWidget(Widget& widget); + bool requestFocus(Widget& widget); + bool addWidget(Widget& widget); + Widget* focusNext(void); + + void draw(ogfx::BasicRenderer2D& gfx); + void update(void); + void onThemeApplied(const Theme& theme); + void onMousePressed(const Event& event); + void onMouseReleased(const Event& event); + void onMouseMoved(const Event& event); + void onMouseDragged(const Event& event); + void onKeyPressed(const Event& event); + void onKeyReleased(const Event& event); + void onTextEntered(const Event& event); + void onWindowClosed(const Event& event); + void onWindowResized(const Event& event); + void onWindowFocused(const Event& event); + void onWindowFocusLost(const Event& event); + + inline int32_t widgetCount(void) const { return m_widgetList.size(); } + inline WindowCore& getWindow(void) { return m_window; } + + private: + WindowCore& m_window; + Widget& m_owner; + std::vector m_widgetList; + Widget* m_focused { nullptr }; + bool m_tabNavigationEnabled { true }; + Widget* m_mousePressedOnWidget { nullptr }; + }; + class Widget : public ostd::BaseObject, public ostd::Rectangle + { + public: using EventCallback = std::function; + public: + Widget(const ostd::Rectangle& bounds, WindowCore& window); + bool addChild(Widget& child); + ostd::Vec2 getGlobalPosition(void) const; + virtual void applyTheme(const Theme& theme) = 0; + void applyThemeValue(Theme& theme, const ostd::String& key, Theme::ThemeValue value, bool propagate); + + inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { } + inline virtual void onUpdate(void) { } + + inline virtual void onMousePressed(const Event& event) { } + inline virtual void onMouseReleased(const Event& event) { } + inline virtual void onMouseMoved(const Event& event) { } + inline virtual void onMouseDragged(const Event& event) { } + inline virtual void onKeyPressed(const Event& event) { } + inline virtual void onKeyReleased(const Event& event) { } + inline virtual void onTextEntered(const Event& event) { } + inline virtual void onFocusGained(const Event& event) { } + inline virtual void onFocusLost(const Event& event) { } + inline virtual void onWindowClosed(const Event& event) { } + inline virtual void onWindowResized(const Event& event) { } + inline virtual void onWindowFocused(const Event& event) { } + inline virtual void onWindowFocusLost(const Event& event) { } + + void __draw(ogfx::BasicRenderer2D& gfx); + void __update(void); + void __onMousePressed(const Event& event); + void __onMouseReleased(const Event& event); + void __onMouseMoved(const Event& event); + void __onMouseDragged(const Event& event); + void __onKeyPressed(const Event& event); + void __onKeyReleased(const Event& event); + void __onTextEntered(const Event& event); + void __onWindowClosed(const Event& event); + void __onWindowResized(const Event& event); + void __onWIndowFocused(const Event& event); + void __onWindowFocusLost(const Event& event); + void __applyTheme(const Theme& theme, bool propagate); + + 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 setMouseDraggedCallback(EventCallback callback) { callback_onMouseDragged = callback; } + inline virtual void setKeyPressedCallback(EventCallback callback) { callback_onKeyPressed = callback; } + inline virtual void setKeyReleasedCallback(EventCallback callback) { callback_onKeyReleased = callback; } + inline virtual void setTextEnteredCallback(EventCallback callback) { callback_onTextEntered = callback; } + inline virtual void setWindowClosedCallback(EventCallback callback) { callback_onWindowClosed = callback; } + inline virtual void setWindowResizedCallback(EventCallback callback) { callback_onWindowResized = callback; } + inline virtual void setWIndowFocusedCallback(EventCallback callback) { callback_onWindowFocused = callback; } + inline virtual void setWindowFocusLostCallback(EventCallback callback) { callback_onWindowFocusLost = callback; } + + inline bool hasChildren(void) const { return m_allowChildren && m_widgets.widgetCount() > 0; } + inline virtual bool isInvalid(void) const override { return ostd::BaseObject::isInvalid() || (m_parent == nullptr && !m_rootChild); } + inline void setTabIndex(int32_t tabIndex) { m_tabIndex = tabIndex; } + inline int32_t getTabIndex(void) const { return m_tabIndex; } + inline bool isFocused(void) const { return m_focused; } + inline WindowCore& getWindow(void) { return *m_window; } + inline Widget* getParent(void) { return m_parent; } + inline int32_t getZIndex(void) const { return m_zIndex; } + 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; } + + protected: + inline void disableChildren(void) { m_allowChildren = false; } + inline void disableDrawBox(void) { m_drawBox = false; } + inline void enableDrawBox(void) { m_drawBox = true; } + inline bool isDrawBoxEnabled(void) const { return m_drawBox; } + inline void setDrawBoxColor(const ostd::Color& color) { m_drawBoxColor = color; } + inline ostd::Color getDrawBoxColor(void) { return m_drawBoxColor; } + + protected: + bool m_rootChild { false }; + + EventCallback callback_onMousePressed { nullptr }; + EventCallback callback_onMouseReleased { nullptr }; + EventCallback callback_onMouseMoved { nullptr }; + EventCallback callback_onMouseDragged { nullptr }; + EventCallback callback_onKeyPressed { nullptr }; + EventCallback callback_onKeyReleased { nullptr }; + EventCallback callback_onTextEntered { nullptr }; + EventCallback callback_onWindowClosed { nullptr }; + EventCallback callback_onWindowResized { nullptr }; + EventCallback callback_onWindowFocused { nullptr }; + EventCallback callback_onWindowFocusLost { nullptr }; + + private: + WindowCore* m_window { nullptr }; + Widget* m_parent { nullptr }; + bool m_focused { false }; + int32_t m_tabIndex { -1 }; + int32_t m_zIndex { -1 }; + WidgetManager m_widgets; + bool m_allowChildren { true }; + + bool m_drawBox { true }; + ostd::Color m_drawBoxColor { 255, 0, 0 }; + + ostd::Rectangle m_padding { 0, 0, 0, 0 }; + + friend class WidgetManager; + }; + namespace widgets + { + class RootWidget : public Widget + { + public: + RootWidget(WindowCore& window); + void onWindowResized(const Event& event) override; + void applyTheme(const Theme& theme) override; + + private: + }; + class Label : public Widget + { + public: + 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 onDraw(ogfx::BasicRenderer2D& gfx) override; + void setText(const ostd::String& text); + inline ostd::String getText(void) const { return m_text; } + inline ostd::Color getColor(void) const { return m_color; } + inline void setColor(const ostd::Color& color) { m_color = color; } + inline int32_t getFontSize(void) const { return m_fontSize; } + inline void setFontSize(int32_t fontSize) { m_fontSize = fontSize; } + inline void setBackGroundColor(const ostd::Color& color) { m_backgroundColor = color; } + inline ostd::Color getBackgroundColor(const ostd::Color& color) { return m_backgroundColor; } + inline void enableBackground(bool enable = true) { m_showBackground = enable; } + inline bool isBackgoundEnabled(void) const { return m_showBackground; } + + private: + void __update_size(ogfx::BasicRenderer2D& gfx); + + private: + ostd::String m_text { "" }; + bool m_textChanged { false }; + int32_t m_fontSize { 20 }; + ostd::Color m_color { 255, 255, 255 }; + int32_t m_borderRadius { 10 }; + int32_t m_borderWidth { 2 }; + bool m_showBorder { false }; + bool m_showBackground { false }; + ostd::Color m_backgroundColor { ostd::Colors::Transparent }; + ostd::Color m_borderColor { 255, 255, 255 }; + + private: + }; + } + } +} diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 933fa1e..334175f 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -20,6 +20,7 @@ #include "Window.hpp" #include "../../ostd/utils/Time.hpp" +#include namespace ogfx { @@ -74,9 +75,12 @@ namespace ogfx connectSignal(ostd::tBuiltinSignals::MousePressed); connectSignal(ostd::tBuiltinSignals::MouseReleased); connectSignal(ostd::tBuiltinSignals::MouseMoved); + connectSignal(ostd::tBuiltinSignals::MouseMoved); connectSignal(ostd::tBuiltinSignals::OnGuiEvent); connectSignal(ostd::tBuiltinSignals::WindowClosed); connectSignal(ostd::tBuiltinSignals::WindowResized); + connectSignal(ostd::tBuiltinSignals::WindowFocused); + connectSignal(ostd::tBuiltinSignals::WindowLostFocus); __on_window_init(width, height, title); } @@ -160,6 +164,11 @@ namespace ogfx SDL_PushEvent(&e); } + void WindowCore::handleSignal(ostd::tSignal& signal) + { + __on_signal(signal); + } + MouseEventData WindowCore::get_mouse_state(void) { float mx = 0, my = 0; @@ -216,6 +225,14 @@ namespace ogfx { close(); } + else if (event.type == SDL_EVENT_WINDOW_FOCUS_GAINED) + { + ostd::SignalHandler::emitSignal(ostd::tBuiltinSignals::WindowFocused, ostd::tSignalPriority::RealTime, *this); + } + else if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST) + { + ostd::SignalHandler::emitSignal(ostd::tBuiltinSignals::WindowLostFocus, ostd::tSignalPriority::RealTime, *this); + } else if (event.type == SDL_EVENT_WINDOW_RESIZED) { WindowResizedData wrd(*this, m_windowWidth, m_windowHeight, 0, 0); @@ -267,6 +284,7 @@ namespace ogfx + void GraphicsWindow::__on_window_init(int32_t width, int32_t height, const ostd::String& title) { SDL_SetWindowResizable(m_window, false); @@ -305,6 +323,11 @@ namespace ogfx m_fpsUpdateTimer.update(); } + void GraphicsWindow::__on_signal(ostd::tSignal& signal) + { + onSignal(signal); + } + void GraphicsWindow::__on_event(SDL_Event& event) { onSDLEvent(event); @@ -320,11 +343,24 @@ namespace ogfx namespace gui { + void Window::addWidget(Widget& widget) + { + m_rootWidget.addChild(widget); + setTheme(*m_guiTheme); + } + + void Window::setTheme(const gui::Theme& theme) + { + m_guiTheme = &theme; + m_rootWidget.__applyTheme(theme, true); + } + void Window::__on_window_init(int32_t width, int32_t height, const ostd::String& title) { enableBlockingEvents(); setTypeName("ogfx::gui::Window"); m_gfx.init(*this); + loadDefaultTHeme(); onInitialize(); } @@ -339,19 +375,70 @@ namespace ogfx { handle_events(); before_render(); + m_rootWidget.__update(); + m_rootWidget.__draw(m_gfx); onRedraw(m_gfx); - m_gfx.drawString("Hello World", { 100, 100 }, { 255, 0, 0 }); after_render(); } } - void Window::__on_event(SDL_Event& event) + void Window::__on_signal(ostd::tSignal& signal) { - if (event.type == SDL_EVENT_KEY_UP) + Event evt(*this); + if (signal.ID == ostd::tBuiltinSignals::WindowClosed) { - if (event.key.key == SDLK_ESCAPE) + m_rootWidget.__onWindowClosed(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::WindowFocused) + { + m_rootWidget.__onWIndowFocused(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::WindowLostFocus) + { + m_rootWidget.__onWindowFocusLost(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::WindowResized) + { + evt.windowResized = &(ogfx::WindowResizedData&)signal.userData; + m_rootWidget.__onWindowResized(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::MouseMoved) + { + evt.mouse = &(ogfx::MouseEventData&)signal.userData; + m_rootWidget.__onMouseMoved(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::MousePressed) + { + evt.mouse = &(ogfx::MouseEventData&)signal.userData; + m_rootWidget.__onMousePressed(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::MouseReleased) + { + evt.mouse = &(ogfx::MouseEventData&)signal.userData; + m_rootWidget.__onMouseReleased(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::KeyPressed) + { + evt.keyboard = &(ogfx::KeyEventData&)signal.userData; + m_rootWidget.__onKeyPressed(evt); + } + else if (signal.ID == ostd::tBuiltinSignals::KeyReleased) + { + evt.keyboard = &(ogfx::KeyEventData&)signal.userData; + m_rootWidget.__onKeyReleased(evt); + if (evt.keyboard->keyCode == SDLK_ESCAPE) close(); } + else if (signal.ID == ostd::tBuiltinSignals::TextEntered) + { + evt.keyboard = &(ogfx::KeyEventData&)signal.userData; + m_rootWidget.__onTextEntered(evt); + } + onSignal(signal); + } + + void Window::__on_event(SDL_Event& event) + { onSDLEvent(event); } diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 5a00df2..47c1375 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -25,8 +25,10 @@ #include #include #include +#include #include #include +#include namespace ogfx { @@ -47,6 +49,7 @@ namespace ogfx void setIcon(const ostd::String& iconFilePath); void setBlockingEventsRefreshFPS(uint32_t fps); void requestRedraw(void); + void handleSignal(ostd::tSignal& signal) override; inline bool isInitialized(void) const { return m_initialized; } inline bool isRunning(void) const { return m_running; } @@ -74,6 +77,7 @@ namespace ogfx inline virtual void __on_window_init(int32_t width, int32_t height, const ostd::String& title) { } inline virtual void __on_window_destroy(void) { } inline virtual void __on_window_close(void) { } + inline virtual void __on_signal(ostd::tSignal& signal) { } inline virtual void __main_loop(void) = 0; private: @@ -106,6 +110,7 @@ namespace ogfx public: inline static constexpr int32_t MaxBlockingEventsFPS { 240 }; inline static constexpr int32_t DefaultBlockingEventsFPS { 30 }; + inline static const gui::Theme DefaultTheme; }; class GraphicsWindow : public WindowCore { @@ -121,6 +126,7 @@ namespace ogfx inline virtual void onDestroy(void) { } inline virtual void onClose(void) { } inline virtual void onSDLEvent(SDL_Event& event) { } + inline virtual void onSignal(ostd::tSignal& signal) { } inline int32_t getFPS(void) const { return m_fps; } @@ -130,6 +136,7 @@ namespace ogfx void __on_window_destroy(void) override; void __on_window_close(void) override; void __main_loop(void) override; + void __on_signal(ostd::tSignal& signal) override; private: int32_t m_fps { 0 }; @@ -147,12 +154,18 @@ namespace ogfx public: inline Window(void) { } inline Window(int32_t width, int32_t height, const ostd::String& title) { initialize(width, height, title); } + void addWidget(Widget& widget); inline virtual void onInitialize(void) { } inline virtual void onDestroy(void) { } inline virtual void onClose(void) { } inline virtual void onSDLEvent(SDL_Event& event) { } inline virtual void onRedraw(BasicRenderer2D& gfx) { } + inline virtual void onSignal(ostd::tSignal& 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; @@ -160,9 +173,14 @@ namespace ogfx void __on_window_destroy(void) override; void __on_window_close(void) override; void __main_loop(void) override; + void __on_signal(ostd::tSignal& signal) override; private: 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/data/Color.hpp b/src/ostd/data/Color.hpp index 218f767..a9c591e 100755 --- a/src/ostd/data/Color.hpp +++ b/src/ostd/data/Color.hpp @@ -78,5 +78,62 @@ namespace ostd uint8_t g; uint8_t b; uint8_t a; + + public: + }; + struct Colors + { + inline static const Color Transparent { 0, 0, 0, 0 }; + inline static const Color Black { 0, 0, 0, 255 }; + inline static const Color White { 255, 255, 255, 255 }; + inline static const Color Gray { 128, 128, 128, 255 }; + inline static const Color LightGray { 192, 192, 192, 255 }; + inline static const Color DarkGray { 64, 64, 64, 255 }; + + inline static const Color Red { 255, 0, 0, 255 }; + inline static const Color Green { 0, 255, 0, 255 }; + inline static const Color Blue { 0, 0, 255, 255 }; + + inline static const Color Yellow { 255, 255, 0, 255 }; + inline static const Color Cyan { 0, 255, 255, 255 }; + inline static const Color Magenta { 255, 0, 255, 255 }; + + inline static const Color Orange { 255, 165, 0, 255 }; + inline static const Color Purple { 128, 0, 128, 255 }; + inline static const Color Brown { 139, 69, 19, 255 }; + inline static const Color Pink { 255, 192, 203, 255 }; + inline static const Color Lime { 50, 205, 50, 255 }; + inline static const Color Olive { 128, 128, 0, 255 }; + inline static const Color Teal { 0, 128, 128, 255 }; + inline static const Color Navy { 0, 0, 128, 255 }; + inline static const Color Maroon { 128, 0, 0, 255 }; + inline static const Color Indigo { 75, 0, 130, 255 }; + inline static const Color Gold { 255, 215, 0, 255 }; + inline static const Color Silver { 192, 192, 192, 255 }; + inline static const Color Beige { 245, 245, 220, 255 }; + inline static const Color Coral { 255, 127, 80, 255 }; + inline static const Color Salmon { 250, 128, 114, 255 }; + inline static const Color Chocolate { 210, 105, 30, 255 }; + inline static const Color Khaki { 240, 230, 140, 255 }; + inline static const Color Lavender { 230, 230, 250, 255 }; + inline static const Color Mint { 189, 252, 201, 255 }; + inline static const Color SkyBlue { 135, 206, 235, 255 }; + inline static const Color RoyalBlue { 65, 105, 225, 255 }; + inline static const Color DeepSkyBlue { 0, 191, 255, 255 }; + inline static const Color Turquoise { 64, 224, 208, 255 }; + inline static const Color Aquamarine { 127, 255, 212, 255 }; + inline static const Color ForestGreen { 34, 139, 34, 255 }; + inline static const Color SeaGreen { 46, 139, 87, 255 }; + inline static const Color SpringGreen { 0, 255, 127, 255 }; + inline static const Color Firebrick { 178, 34, 34, 255 }; + inline static const Color Crimson { 220, 20, 60, 255 }; + inline static const Color Tomato { 255, 99, 71, 255 }; + inline static const Color DarkOrange { 255, 140, 0, 255 }; + inline static const Color DarkRed { 139, 0, 0, 255 }; + inline static const Color DarkBlue { 0, 0, 139, 255 }; + inline static const Color DarkGreen { 0, 100, 0, 255 }; + inline static const Color DarkCyan { 0, 139, 139, 255 }; + inline static const Color DarkMagenta { 139, 0, 139, 255 }; + inline static const Color DarkYellow { 204, 204, 0, 255 }; }; } diff --git a/src/ostd/math/Geometry.hpp b/src/ostd/math/Geometry.hpp index 7e15383..09740ea 100755 --- a/src/ostd/math/Geometry.hpp +++ b/src/ostd/math/Geometry.hpp @@ -345,6 +345,11 @@ namespace ostd inline virtual Vec2 bottomLeft(void) const { return Vec2(getx(), gety() + geth()); } inline virtual Vec2 bottomRight(void) const { return Vec2(getx() + getw(), gety() + geth()); } + inline virtual float left(void) const { return getx(); } + inline virtual float right(void) const { return getw(); } + inline virtual float top(void) const { return gety(); } + inline virtual float bottom(void) const { return geth(); } + inline virtual bool intersects(Rectangle rect, bool includeBounds = true) const { if (includeBounds) @@ -376,9 +381,9 @@ namespace ostd inline virtual bool contains(Vec2 p, bool includeBounds = false) const { if (includeBounds) - return p.x >= x && p.y >= y & p.x <= x + w && p.y <= y + h; + return p.x >= x && p.y >= y && p.x <= x + w && p.y <= y + h; else - return p.x > x && p.y > y & p.x < x + w && p.y < y + h; + return p.x > x && p.y > y && p.x < x + w && p.y < y + h; } inline virtual bool contains(float xx, float yy, bool includeBounds = false) const { return contains({ xx, yy }); } diff --git a/src/ostd/utils/Signals.cpp b/src/ostd/utils/Signals.cpp index 888a2ae..76a679c 100755 --- a/src/ostd/utils/Signals.cpp +++ b/src/ostd/utils/Signals.cpp @@ -27,6 +27,10 @@ namespace ostd SignalHandler::m_windowResizedRecievers.reserve(SignalHandler::__SIGNAL_BUFFER_START_SIZE); SignalHandler::m_windowClosedRecievers.clear(); SignalHandler::m_windowClosedRecievers.reserve(SignalHandler::__SIGNAL_BUFFER_START_SIZE); + SignalHandler::m_windowFocusedRecievers.clear(); + SignalHandler::m_windowFocusedRecievers.reserve(SignalHandler::__SIGNAL_BUFFER_START_SIZE); + SignalHandler::m_windowLostFocusRecievers.clear(); + SignalHandler::m_windowLostFocusRecievers.reserve(SignalHandler::__SIGNAL_BUFFER_START_SIZE); SignalHandler::m_delegatedSignals.clear(); SignalHandler::m_delegatedSignals.reserve(SignalHandler::__DELEGATED_SIGNALS_BUFFER_START_SIZE); SignalHandler::m_onGuiEventRecievers.clear(); @@ -71,6 +75,10 @@ namespace ostd sig_list = &m_windowResizedRecievers; else if (signal_id == tBuiltinSignals::WindowClosed) sig_list = &m_windowClosedRecievers; + else if (signal_id == tBuiltinSignals::WindowLostFocus) + sig_list = &m_windowLostFocusRecievers; + else if (signal_id == tBuiltinSignals::WindowFocused) + sig_list = &m_windowFocusedRecievers; else if (signal_id == tBuiltinSignals::OnGuiEvent) sig_list = &m_onGuiEventRecievers; else if (signal_id == tBuiltinSignals::BeforeSDLShutdown) @@ -112,6 +120,10 @@ namespace ostd m_windowResizedRecievers.push_back({ &object, signal_id }); else if (signal_id == tBuiltinSignals::WindowClosed) m_windowClosedRecievers.push_back({ &object, signal_id }); + else if (signal_id == tBuiltinSignals::WindowLostFocus) + m_windowLostFocusRecievers.push_back({ &object, signal_id }); + else if (signal_id == tBuiltinSignals::WindowFocused) + m_windowFocusedRecievers.push_back({ &object, signal_id }); else if (signal_id == tBuiltinSignals::OnGuiEvent) m_onGuiEventRecievers.push_back({ &object, signal_id }); else if (signal_id == tBuiltinSignals::BeforeSDLShutdown) diff --git a/src/ostd/utils/Signals.hpp b/src/ostd/utils/Signals.hpp index 8bc74aa..2b37b0f 100755 --- a/src/ostd/utils/Signals.hpp +++ b/src/ostd/utils/Signals.hpp @@ -49,6 +49,8 @@ namespace ostd inline static constexpr uint32_t WindowResized = 0x1001; inline static constexpr uint32_t WindowClosed = 0x1002; + inline static constexpr uint32_t WindowFocused = 0x1003; + inline static constexpr uint32_t WindowLostFocus = 0x1004; /*********************/ inline static constexpr uint32_t CustomSignalBase = 0xFF0000; @@ -107,6 +109,8 @@ namespace ostd inline static std::vector m_textEnteredRecievers; inline static std::vector m_windowResizedRecievers; inline static std::vector m_windowClosedRecievers; + inline static std::vector m_windowFocusedRecievers; + inline static std::vector m_windowLostFocusRecievers; inline static std::vector m_onGuiEventRecievers; inline static std::vector m_beforeSDLShutdownRecievers; /************************************/ diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 4087ab4..1c9f929 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -48,9 +48,25 @@ class Window : public ogfx::gui::Window inline Window(void) { } inline void onInitialize(void) override { + m_label1.setPosition(100, 200); + m_label1.setText("Hello World!"); + m_label1.setMouseMovedCallback([&](const ogfx::gui::Event& event) -> void { + m_label1.applyThemeValue(m_theme, "label.backgroundColor", ostd::Colors::DarkBlue, 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); + setTheme(m_theme); } - inline void handleSignal(ostd::tSignal& signal) override + inline void onSignal(ostd::tSignal& signal) override { if (signal.ID == ostd::tBuiltinSignals::KeyReleased) { @@ -62,10 +78,13 @@ class Window : public ogfx::gui::Window void onRedraw(ogfx::BasicRenderer2D& gfx) override { - wout().fg(ostd::Color { 255, 0, 0 }).p("Hello ").fg(ostd::Color { 0, 0, 255 }).p("World"); + // wout().fg(ostd::Color { 255, 0, 0 }).p("Hello ").fg(ostd::Color { 0, 0, 255 }).p("World"); } private: + ogfx::gui::widgets::Label m_label1 { *this }; + ogfx::gui::widgets::Label m_label2 { *this }; + ogfx::gui::Theme m_theme = ogfx::gui::Theme(); }; int main(int argc, char** argv)