Started work on gui Widget system

This commit is contained in:
OmniaX-Dev 2026-04-02 06:19:20 +02:00
parent f0f644102b
commit 924da0dba6
15 changed files with 1082 additions and 75 deletions

View file

@ -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

View file

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

View file

@ -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 };
};
}
}

90
src/ogfx/gui/Themes.hpp Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/Color.hpp>
#include <ostd/math/Geometry.hpp>
#include <variant>
#include <unordered_map>
namespace ogfx
{
namespace gui
{
struct Theme
{
public: using ThemeValue = std::variant<int, float, bool, ostd::String, ostd::Color, ostd::Rectangle, ostd::Vec2>;
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<typename T>
inline T get(const ostd::String& key, const T& fallback) const
{
if (auto v = __get(key))
{
if (auto p = std::get_if<T>(v))
return *p;
}
return fallback;
}
private:
std::unordered_map<ostd::String, ThemeValue> m_values;
};
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "Widget.hpp"
namespace ogfx
{
namespace gui
{
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/BaseObject.hpp>
#include <ostd/math/Geometry.hpp>
namespace ogfx
{
namespace gui
{
class Widget : public ostd::BaseObject, public ostd::Rectangle
{
public:
private:
};
}
}

526
src/ogfx/gui/Widgets.cpp Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "Widgets.hpp"
#include <ogfx/render/BasicRenderer.hpp>
#include <ogfx/gui/Window.hpp>
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<int32_t>::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<float>(window.getWindowWidth()), static_cast<float>(window.getWindowHeight()));
enableDrawBox();
setDrawBoxColor(window.getClearColor());
setTypeName("ogfx::gui::widgets::RootWidget");
}
void RootWidget::onWindowResized(const Event& event)
{
setSize(static_cast<float>(event.windowResized->new_width), static_cast<float>(event.windowResized->new_height));
}
void RootWidget::applyTheme(const Theme& theme)
{
setDrawBoxColor(theme.get<ostd::Color>("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<ostd::Color>("label.textColor", ostd::Colors::White));
setBackGroundColor(theme.get<ostd::Color>("label.backgroundColor", ostd::Colors::Transparent));
setFontSize(theme.get<int32_t>("label.fontSize", 20));
m_borderRadius = theme.get<int32_t>("label.borderRadius", 10);
m_borderWidth = theme.get<int32_t>("label.borderWidth", 2);
m_showBorder = theme.get<bool>("label.showBorder", false);
m_borderColor = theme.get<ostd::Color>("label.borderColor", ostd::Colors::White);
enableBackground(theme.get<bool>("label.showBackground", false));
setPadding(theme.get<ostd::Rectangle>("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<float>(size.x), static_cast<float>(size.y) });
m_textChanged = false;
}
}
}
}

226
src/ogfx/gui/Widgets.hpp Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/BaseObject.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/data/Color.hpp>
#include <ogfx/gui/Themes.hpp>
#include <functional>
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<Widget*> 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<void(const Event&)>;
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:
};
}
}
}

View file

@ -20,6 +20,7 @@
#include "Window.hpp"
#include "../../ostd/utils/Time.hpp"
#include <SDL3/SDL_events.h>
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);
}

View file

@ -25,8 +25,10 @@
#include <ostd/utils/Time.hpp>
#include <ostd/io/IOHandlers.hpp>
#include <ogfx/gui/Events.hpp>
#include <ogfx/gui/Widgets.hpp>
#include <ogfx/render/BasicRenderer.hpp>
#include <ogfx/gui/WindowOutputHandler.hpp>
#include <ogfx/gui/Themes.hpp>
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;
};
}
}

View file

@ -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 };
};
}

View file

@ -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 }); }

View file

@ -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)

View file

@ -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<tSignalObjPair> m_textEnteredRecievers;
inline static std::vector<tSignalObjPair> m_windowResizedRecievers;
inline static std::vector<tSignalObjPair> m_windowClosedRecievers;
inline static std::vector<tSignalObjPair> m_windowFocusedRecievers;
inline static std::vector<tSignalObjPair> m_windowLostFocusRecievers;
inline static std::vector<tSignalObjPair> m_onGuiEventRecievers;
inline static std::vector<tSignalObjPair> m_beforeSDLShutdownRecievers;
/************************************/

View file

@ -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)