/* 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" #include "gui/Events.hpp" #include "io/Memory.hpp" #include "../..//render/BasicRenderer.hpp" #include "../Window.hpp" namespace ogfx { namespace gui { ostd::BaseObject* Widget::s_dragAndDropData { nullptr }; bool Widget::s_hasDragAndDropData { false }; Widget::Widget(const Rectangle& bounds, WindowCore& window) : Rectangle(bounds), m_widgets(window, *this) { m_window = &window; } bool Widget::addWidget(Widget& child, const Vec2& position, bool __skip_callback) { if (!m_allowChildren) return false; if (position.x != 0 || position.y != 0) child.setPosition(position); child.reloadTheme(); bool result = m_widgets.addWidget(child); if (!result) return false; if (!__skip_callback) onWidgetAdded(child); if (m_layout && !__skip_callback) relayout(); return result; } bool Widget::removeWidget(Widget& child) { bool result = m_widgets.removeWidget(child); if (result && m_layout) relayout(); return result; } Vec2 Widget::getGlobalPosition(void) const { Vec2 glob = getPosition(); if (!m_rootChild && m_parent != nullptr) { glob += m_parent->getGlobalPosition(); if (!isIgnoreScrollEnabled()) { glob += m_parent->getPadding().getPosition(); glob += m_parent->getContentOffset(); glob += m_parent->getScrollOffset(); } } glob += m_margin.getPosition(); return glob; } Vec2 Widget::getGlobalContentPosition(void) const { return getGlobalPosition() + getContentBounds().getPosition(); } Rectangle Widget::getGlobalBounds(void) const { return { getGlobalPosition(), getSize() + (m_margin.getSize() * 2) }; } Rectangle Widget::getContentBounds(void) const { auto pad = getPadding(); return { Vec2 { m_contentOffset.x, m_contentOffset.y } + pad.getPosition(), (getSize() - m_contentOffset - (pad.getSize() * 2)) }; } Rectangle Widget::getPureContentBounds(void) const { return { { m_contentOffset.x, m_contentOffset.y }, (getSize() - m_contentOffset) }; } Rectangle Widget::getGlobalContentBounds(void) const { return { getGlobalContentPosition(), getContentBounds().getSize() }; } Rectangle Widget::getGlobalPureContentBounds(void) const { return { getGlobalPosition() + getContentOffset(), getPureContentBounds().getSize() }; } Rectangle Widget::getContentExtents(void) const { // If a layout owns this widget, prefer its measure() — it knows // the real desired extent (e.g. a vertical box with 50 items // reports 50*itemHeight even if only 5 are visible). if (m_layout) { Vec2 desired = m_layout->measure(*this); return { 0, 0, desired.x, desired.y }; } f32 maxX = 0, maxY = 0; for (auto* child : m_widgets.getWidgets()) { if (!child || child->isInvalid()) continue; if (child->isIgnoreScrollEnabled()) continue; if (!child->isVisible()) continue; Vec2 localPos = getPadding().getPosition() + child->getPosition() + child->getMargin().getPosition() + child->getContentBounds().getPosition(); maxX = std::max(maxX, localPos.x + child->getw() + child->getMargin().w); maxY = std::max(maxY, localPos.y + child->geth() + child->getMargin().h); } return { 0, 0, maxX, maxY }; } bool Widget::contains(Vec2 p, bool includeBounds) const { return Rectangle(getGlobalPosition(), getSize()).contains(p, includeBounds); } void Widget::enable(bool enable) { m_enabled = enable; setThemeQualifier("disabled", !enable); } void Widget::addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value) { m_themeOverrides.push_back({ fullKey, value }); } void Widget::reloadTheme(bool propagate) { if (getWindow().theme() == nullptr) return; if (!isThemingEnabled()) return; auto& theme = *getWindow().theme(); applyTheme(theme); ostd::Stylesheet& const_cast_theme = const_cast(theme); struct tBackup { const ostd::Stylesheet::TypeVariant* ptr; ostd::Stylesheet::TypeVariant val; String key; }; stdvec backup; for (auto& rule : m_themeOverrides) { auto currentValuePtr = theme.getFull(rule.fullKey); ostd::Stylesheet::TypeVariant currentValue; if (currentValuePtr) currentValue = *currentValuePtr; backup.push_back({ currentValuePtr, currentValue, rule.fullKey }); const_cast_theme.setFull(rule.fullKey, rule.value); } __applyTheme(const_cast_theme, propagate); for (auto&[ptr, val, key] : backup) { if (ptr == nullptr) const_cast_theme.removeRule(key); else const_cast_theme.setFull(key, val); } } void Widget::setThemeQualifier(const String& qualifier, bool value) { for (auto& [name, state] : m_qualifierList) { if (name == qualifier) { if (state == value) return; state = value; reloadTheme(); return; } } } bool Widget::getThemeQualifier(const String& qualifier) const { for (auto& [name, state] : m_qualifierList) { if (name == qualifier) return state; } return false; } bool Widget::addThemeID(const String& id) { if (STDVEC_CONTAINS(m_themeIDList, id)) return false; m_themeIDList.push_back(id); return true; } bool Widget::removeThemeID(const String& id) { if (!STDVEC_CONTAINS(m_themeIDList, id)) return false; STDVEC_REMOVE(m_themeIDList, id); return true; } void Widget::setVisible(bool v) { m_visible = v; } void Widget::setLayout(std::unique_ptr layout) { m_layout = std::move(layout); if (m_layout) relayout(); } void Widget::relayout(void) { if (!m_layout) return; m_layout->arrange(*this); // After our children's bounds changed, give *their* layouts a chance // to re-run (a horizontal layout containing a vertical layout, etc.). for (auto* c : m_widgets.getWidgets()) { if (!c || c->isInvalid()) continue; if (c->hasLayout()) c->relayout(); } } void Widget::setCallback(eCallback type, EventCallback callback) { switch (type) { case eCallback::MousePressed: callback_onMousePressed = std::move(callback); break; case eCallback::MouseReleased: callback_onMouseReleased = std::move(callback); break; case eCallback::MouseMoved: callback_onMouseMoved = std::move(callback); break; case eCallback::MouseScrolled: callback_onMouseScrolled = std::move(callback); break; case eCallback::DragAndDrop: callback_onDragAndDrop = std::move(callback); break; case eCallback::MouseEntered: callback_onMouseEntered = std::move(callback); break; case eCallback::MouseExited: callback_onMouseExited = std::move(callback); break; case eCallback::MouseDragged: callback_onMouseDragged = std::move(callback); break; case eCallback::KeyPressed: callback_onKeyPressed = std::move(callback); break; case eCallback::KeyReleased: callback_onKeyReleased = std::move(callback); break; case eCallback::TextEntered: callback_onTextEntered = std::move(callback); break; case eCallback::WindowClosed: callback_onWindowClosed = std::move(callback); break; case eCallback::WindowResized: callback_onWindowResized = std::move(callback); break; case eCallback::WindowFocused: callback_onWindowFocused = std::move(callback); break; case eCallback::WindowFocusLost: callback_onWindowFocusLost = std::move(callback); break; case eCallback::ActionPerformed: callback_onActionPerformed = std::move(callback); break; default: break; } } void Widget::__draw(ogfx::BasicRenderer2D& gfx) { if (!isVisible()) return; beforeDraw(gfx); if (m_useBackgroundGradient) gfx.fillGradientRect({ getGlobalPosition(), getSize() }, m_backgroundGradient); else if (m_showBackground) gfx.fillRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderRadius); onDraw(gfx); // gfx.fillRect(getGlobalPureContentBounds(), { 0, 255, 0, 120 }); const bool needsContentClip = !m_rootChild; if (needsContentClip) gfx.pushClippingRect(getGlobalPureContentBounds(), true); if (hasChildren()) m_widgets.draw(gfx); if (needsContentClip) gfx.popClippingRect(); if (m_showBorder) gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); afterDraw(gfx); } void Widget::__update(void) { onUpdate(); if (hasChildren()) m_widgets.update(); } void Widget::__onMousePressed(const Event& event) { if (event.isHandled() || !isMousePressedEventEnabled()) return; setThemeQualifier("pressed"); 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) { if (event.isHandled() || !isMouseReleasedEventEnabled()) return; setThemeQualifier("pressed", false); 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::__onDragAndDrop(const Event& event) { if (hasChildren()) m_widgets.onMouseReleased(event); if (!event.isHandled()) { if (callback_onDragAndDrop) callback_onDragAndDrop(event); onDragAndDrop(event); } } void Widget::__onMouseMoved(const Event& event) { if (event.isHandled() || !isMouseMovedEventEnabled()) return; if (hasChildren()) m_widgets.onMouseMoved(event); if (!event.isHandled()) { if (callback_onMouseMoved) callback_onMouseMoved(event); onMouseMoved(event); } if (isTooltipEnabled() && isMouseInside()) getWindow().restartTooltipTimer(); } void Widget::__onMouseScrolled(const Event& event) { if (event.isHandled() || !isMouseScrolledEventEnabled()) return; if (hasChildren()) m_widgets.onMouseScrolled(event); if (!event.isHandled()) { if (callback_onMouseScrolled) callback_onMouseScrolled(event); onMouseScrolled(event); } } void Widget::__onMouseEntered(const Event& event) { if (event.isHandled() || !isMouseEnteredEventEnabled()) return; setThemeQualifier("hover"); if (callback_onMouseEntered) callback_onMouseEntered(event); onMouseEntered(event); if (isTooltipEnabled()) getWindow().startTooltipTimer(getTooltipText()); } void Widget::__onMouseExited(const Event& event) { if (event.isHandled() || !isMouseExitedEventEnabled()) return; setThemeQualifier("hover", false); if (callback_onMouseExited) callback_onMouseExited(event); onMouseExited(event); if (isTooltipEnabled()) getWindow().stopTooltipTimer(); } void Widget::__onMouseDragged(const Event& event) { if (event.isHandled() || !isMouseDraggedEventEnabled()) return; if (hasChildren()) m_widgets.onMouseDragged(event); if (!event.isHandled()) { if (callback_onMouseDragged) callback_onMouseDragged(event); onMouseDragged(event); } if (isTooltipEnabled() && isMouseInside()) getWindow().restartTooltipTimer(); } void Widget::__onKeyPressed(const Event& event) { if (event.isHandled() || !isKeyPressedEventEnabled()) return; if (hasChildren()) m_widgets.onKeyPressed(event); if (!event.isHandled()) { if (callback_onKeyPressed) callback_onKeyPressed(event); onKeyPressed(event); } } void Widget::__onKeyReleased(const Event& event) { if (event.isHandled() || !isKeyReleasedEventEnabled()) return; 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 (event.isHandled() || !isTextEnteredEventEnabled()) return; 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); } if (m_layout) relayout(); } 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); apply_common_theme_values(theme); applyTheme(theme); } void Widget::apply_common_theme_values(const ostd::Stylesheet& theme) { setTextColor(getThemeValue(theme, "textColor", m_textColor)); setBackgroundColor(getThemeValue(theme, "backgroundColor", m_backgroundColor)); setFontSize(getThemeValue(theme, "fontSize", m_fontSize)); setBorderRadius(getThemeValue(theme, "borderRadius", m_borderRadius)); setBorderWidth(getThemeValue(theme, "borderWidth", m_borderWidth)); enableBorder(getThemeValue(theme, "showBorder", m_showBorder)); setBorderColor(getThemeValue(theme, "borderColor", m_borderColor)); enableBackground(getThemeValue(theme, "showBackground", m_showBackground)); setPadding(getThemeValue(theme, "padding", m_padding)); setMargin(getThemeValue(theme, "margin", m_margin)); enableBackgroundGradient(getThemeValue(theme, "useBackgroundGradient", m_useBackgroundGradient)); setBackgroundGradient(getThemeValue(theme, "backgroundGradient", m_backgroundGradient)); if (isBackgroundGradientEnabled() && isBackgroundEnabled()) enableBackground(false); } } }