/*
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 "gui/Events.hpp"
#include "utils/Keycodes.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 ostd::Stylesheet& 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))
{
if (w->m_mouseInside)
{
w->m_mouseInside = false;
w->__onMouseExited(event);
}
continue;
}
if (!w->m_mouseInside)
{
w->m_mouseInside = true;
w->__onMouseEntered(event);
}
else
{
if (w->m_pressedButton != ogfx::MouseEventData::eButton::None)
w->__onMouseDragged(event);
else
w->__onMouseMoved(event);
}
if (event.isHandled())
break;
}
}
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 == KeyCode::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;
}
bool Widget::contains(ostd::Vec2 p, bool includeBounds) const
{
return ostd::Rectangle(getGlobalPosition(), getSize()).contains(p, includeBounds);
}
void Widget::addThemeOverride(const ostd::String& key, ostd::Stylesheet::TypeVariant value, const ostd::String& themeID, const ostd::String& qualifier, bool propagate)
{
ostd::String fullKey = "@" + themeID;
if (qualifier.new_trim() != "")
fullKey += ":" + qualifier;
fullKey += "." + key;
m_themeOverrides.push_back({ fullKey, value, propagate });
}
void Widget::reloadTheme(void)
{
if (getWindow().theme() == nullptr)
return;
auto& theme = *getWindow().theme();
applyTheme(theme);
ostd::Stylesheet& const_cast_theme = const_cast(theme);
for (auto& rule : m_themeOverrides)
{
auto currentValue = theme.getFull(rule.fullKey);
if (currentValue == nullptr)
return;
const_cast_theme.setFull(rule.fullKey, rule.value);
__applyTheme(const_cast_theme, rule.propagate);
const_cast_theme.setFull(rule.fullKey, *currentValue);
}
}
void Widget::__draw(ogfx::BasicRenderer2D& gfx)
{
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);
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())
{
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::__onMouseEntered(const Event& event)
{
m_qualifier = "hover";
reloadTheme();
if (callback_onMouseEntered)
callback_onMouseEntered(event);
onMouseEntered(event);
}
void Widget::__onMouseExited(const Event& event)
{
m_qualifier = "";
reloadTheme();
if (callback_onMouseExited)
callback_onMouseExited(event);
onMouseExited(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)
{
m_pressedButton = ogfx::MouseEventData::eButton::None;
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 ostd::Stylesheet& theme, bool propagate)
{
if (propagate && hasChildren())
m_widgets.onThemeApplied(theme);
applyTheme(theme);
}
namespace widgets
{
RootWidget::RootWidget(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window)
{
disableDrawBox();
m_rootChild = true;
setSize(static_cast(window.getWindowWidth()), static_cast(window.getWindowHeight()));
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 ostd::Stylesheet& theme)
{
m_color = theme.get("window.backgroundColor", getWindow().getClearColor(), getThemeID(), getThemeQualifier());
}
void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx)
{
gfx.fillRect({ 0, 0, static_cast(getWindow().getWindowWidth()), static_cast(getWindow().getWindowHeight()) }, m_color);
}
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 ostd::Stylesheet& theme)
{
setColor(theme.get("label.textColor", ostd::Colors::White, getThemeID(), getThemeQualifier()));
setBackGroundColor(theme.get("label.backgroundColor", ostd::Colors::Transparent, getThemeID(), getThemeQualifier()));
setFontSize(theme.get("label.fontSize", 20, getThemeID(), getThemeQualifier()));
m_borderRadius = theme.get("label.borderRadius", 10, getThemeID(), getThemeQualifier());
m_borderWidth = theme.get("label.borderWidth", 2, getThemeID(), getThemeQualifier());
m_showBorder = theme.get("label.showBorder", false, getThemeID(), getThemeQualifier());
m_borderColor = theme.get("label.borderColor", ostd::Colors::White, getThemeID(), getThemeQualifier());
enableBackground(theme.get("label.showBackground", false, getThemeID(), getThemeQualifier()));
setPadding(theme.get("label.padding", { 5, 5, 5, 5 }, getThemeID(), getThemeQualifier()));
}
void Label::onDraw(ogfx::BasicRenderer2D& gfx)
{
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;
}
}
}
}