From d4b4df1e51e93f4d69f6023d6fbbff2ae839ee1f Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Mon, 6 Apr 2026 14:39:51 +0200 Subject: [PATCH] Added drag and drop functionality for Widgets --- .clangd | 3 +- CMakeLists.txt | 2 + extra/testTheme.txt | 5 - other/TODO.txt | 1 - other/build.nr | 2 +- src/ogfx/gui/Events.hpp | 20 +- src/ogfx/gui/WidgetManager.cpp | 317 ++++++++++++++++++++++++++++ src/ogfx/gui/WidgetManager.hpp | 76 +++++++ src/ogfx/gui/Window.cpp | 44 +++- src/ogfx/gui/Window.hpp | 1 + src/ogfx/gui/widgets/RootWidget.cpp | 55 +++++ src/ogfx/gui/widgets/RootWidget.hpp | 44 ++++ src/ogfx/gui/widgets/Widget.cpp | 312 ++------------------------- src/ogfx/gui/widgets/Widget.hpp | 78 ++----- src/ostd/utils/Logic.cpp | 20 ++ src/ostd/utils/Signals.hpp | 2 + src/test/GuiTest.cpp | 17 +- 17 files changed, 630 insertions(+), 369 deletions(-) create mode 100644 src/ogfx/gui/WidgetManager.cpp create mode 100644 src/ogfx/gui/WidgetManager.hpp create mode 100644 src/ogfx/gui/widgets/RootWidget.cpp create mode 100644 src/ogfx/gui/widgets/RootWidget.hpp diff --git a/.clangd b/.clangd index 32169b9..122ab09 100644 --- a/.clangd +++ b/.clangd @@ -1,3 +1,2 @@ CompileFlags: - CompilationDatabase: bin - Add: ["--target=x86_64-w64-mingw32"] \ No newline at end of file + CompilationDatabase: bin \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d5075b2..585e2c7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,11 +89,13 @@ 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/WidgetManager.cpp # gui/widgets ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Containers.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Label.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/RootWidget.cpp # render ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/BasicRenderer.cpp diff --git a/extra/testTheme.txt b/extra/testTheme.txt index a5f6f74..9b0c7be 100644 --- a/extra/testTheme.txt +++ b/extra/testTheme.txt @@ -42,11 +42,6 @@ window.backgroundColor = Color(rgba(120, 120, 120, 255)) borderColor = Color(#208080FF) } - -% (@testLabel2 label:pressed [mouseButton=left or mouseButton=right]) { -% ... - - (@testLabel2 label:pressed) { showBackground = true } diff --git a/other/TODO.txt b/other/TODO.txt index 17d8377..3bf1c68 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -8,7 +8,6 @@ Add theme caching Implement mouse scroll callback Implement a way to determine on what widget (in the hierarchy chain) and event must stop Implement margin for widgets -Implement conditional statements for stylesheet rules Implement following widgets: diff --git a/other/build.nr b/other/build.nr index a9a6e2f..6310783 100644 --- a/other/build.nr +++ b/other/build.nr @@ -1 +1 @@ -2049 +2050 diff --git a/src/ogfx/gui/Events.hpp b/src/ogfx/gui/Events.hpp index ce6b7d0..a55a53f 100644 --- a/src/ogfx/gui/Events.hpp +++ b/src/ogfx/gui/Events.hpp @@ -78,12 +78,28 @@ namespace ogfx eKeyEvent eventType; WindowCore& parentWindow; }; + class DropEventData : public ostd::BaseObject + { + public: enum class eDropType { None = 0, File, Text, InApp }; + public: + inline DropEventData(WindowCore& parent, eDropType type) : parentWindow(parent), dropType(type) + { + setTypeName("ogfx::DropEventData"); + validate(); + } + + public: + eDropType dropType; + WindowCore& parentWindow; + ostd::String textOrFilePath { "" }; + ostd::BaseObject* userObject { nullptr }; + }; namespace gui { class Event { public: - inline Event(WindowCore& _window) : window(_window) { } + inline Event(WindowCore& _window) : window(_window), drop(window, DropEventData::eDropType::None) { } inline void handle(void) { m_handled = true; } inline bool isHandled(void) const { return m_handled; } @@ -92,6 +108,8 @@ namespace ogfx WindowResizedData* windowResized { nullptr }; MouseEventData* mouse { nullptr }; KeyEventData* keyboard { nullptr }; + DropEventData drop; + uint32_t __original_signal_id { 0 }; private: bool m_handled { false }; diff --git a/src/ogfx/gui/WidgetManager.cpp b/src/ogfx/gui/WidgetManager.cpp new file mode 100644 index 0000000..f224167 --- /dev/null +++ b/src/ogfx/gui/WidgetManager.cpp @@ -0,0 +1,317 @@ +/* + 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 "WidgetManager.hpp" +#include "widgets/Widget.hpp" +#include "../render/BasicRenderer.hpp" +#include "../utils/Keycodes.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)); + w->setThemeQualifier("focused", false); + } + else + w->m_focused = false; + } + widget.m_focused = true; + widget.onFocusGained(Event(m_window)); + widget.setThemeQualifier("focused", true); + 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; + gfx.pushClippingRect({ w->getGlobalPosition(), w->getSize() }, true); + w->__draw(gfx); + gfx.popClippingRect(); + } + } + + 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() || w->m_stopEvents) + break; + } + } + + void WidgetManager::onMouseReleased(const Event& event) + { + if (m_mousePressedOnWidget != nullptr) + { + event.mouse->mousePressedOnWidget = m_mousePressedOnWidget; + m_mousePressedOnWidget->__onMouseReleased(event); + // processDragAndDrop(m_mousePressedOnWidget, 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 == m_mousePressedOnWidget) continue; + event.mouse->mousePressedOnWidget = m_mousePressedOnWidget; + w->__onMouseReleased(event); + processDragAndDrop(w, event); + // requestFocus(*w); + if (w->isMouseInside() && w->m_stopEvents) + break; + } + m_mousePressedOnWidget = nullptr; + } + + 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() || w->m_stopEvents) + { + bool mouseOut = false; + for (int32_t j = i - 1; j >= 0; j--) + { + Widget* ww = m_widgetList[j]; + if (ww->m_mouseInside) + { + ww->m_mouseInside = false; + ww->__onMouseExited(event); + } + } + 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; + } + } + + void WidgetManager::processDragAndDrop(Widget* widget, const Event& event) + { + if (widget == nullptr) return; + if (!widget->isDragAndDropEnabled()) return; + if (!widget->isMouseInside()) return; + if (Widget::s_dragAndDropData != nullptr) + { + widget->__onDragAndDrop(event); + Widget::clearDragAndDropData(); + } + + auto& const_cast_event = const_cast(event); + // if (event.__original_signal_id == ostd::BuiltinSignals::TextDragAndDropped) + } + } +} diff --git a/src/ogfx/gui/WidgetManager.hpp b/src/ogfx/gui/WidgetManager.hpp new file mode 100644 index 0000000..1e841f1 --- /dev/null +++ b/src/ogfx/gui/WidgetManager.hpp @@ -0,0 +1,76 @@ +/* + 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 + +namespace ostd +{ + class Stylesheet; +} +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 ostd::Stylesheet& 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: + void processDragAndDrop(Widget* widget, const Event& event); + + private: + WindowCore& m_window; + Widget& m_owner; + std::vector m_widgetList; + Widget* m_focused { nullptr }; + bool m_tabNavigationEnabled { true }; + Widget* m_mousePressedOnWidget { nullptr }; + }; + } +} diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index a07dca5..e65c8c6 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -21,6 +21,7 @@ #include "Window.hpp" #include "../../ostd/utils/Time.hpp" #include "utils/Keycodes.hpp" +#include namespace ogfx { @@ -336,6 +337,18 @@ namespace ogfx { close(); } + else if (event.type == SDL_EVENT_DROP_FILE) + { + DropEventData ded(*this, DropEventData::eDropType::File); + ded.textOrFilePath = event.drop.data; + ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::FileDragAndDropped, ostd::Signal::Priority::RealTime, ded); + } + else if (event.type == SDL_EVENT_DROP_TEXT) + { + DropEventData ded(*this, DropEventData::eDropType::Text); + ded.textOrFilePath = event.drop.data; + ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::FileDragAndDropped, ostd::Signal::Priority::RealTime, ded); + } else if (event.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::WindowFocused, ostd::Signal::Priority::RealTime, *this); @@ -497,46 +510,74 @@ namespace ogfx void Window::__on_signal(ostd::Signal& signal) { Event evt(*this); + evt.__original_signal_id = ostd::BuiltinSignals::NoSignal; if (signal.ID == ostd::BuiltinSignals::WindowClosed) { + evt.__original_signal_id = ostd::BuiltinSignals::WindowClosed; m_rootWidget.__onWindowClosed(evt); } else if (signal.ID == ostd::BuiltinSignals::WindowFocused) { - m_rootWidget.__onWIndowFocused(evt); + evt.__original_signal_id = ostd::BuiltinSignals::WindowFocused; + m_rootWidget.__onWindowFocused(evt); } else if (signal.ID == ostd::BuiltinSignals::WindowLostFocus) { + evt.__original_signal_id = ostd::BuiltinSignals::WindowLostFocus; m_rootWidget.__onWindowFocusLost(evt); } + else if (signal.ID == ostd::BuiltinSignals::FileDragAndDropped) + { + auto& ud = (ogfx::DropEventData&)signal.userData; + evt.drop.dropType = ud.dropType; + evt.drop.userObject = ud.userObject; + evt.drop.textOrFilePath = ud.textOrFilePath; + evt.__original_signal_id = ostd::BuiltinSignals::FileDragAndDropped; + m_rootWidget.__onDragAndDrop(evt); + } + else if (signal.ID == ostd::BuiltinSignals::TextDragAndDropped) + { + auto& ud = (ogfx::DropEventData&)signal.userData; + evt.drop.dropType = ud.dropType; + evt.drop.userObject = ud.userObject; + evt.drop.textOrFilePath = ud.textOrFilePath; + evt.__original_signal_id = ostd::BuiltinSignals::TextDragAndDropped; + m_rootWidget.__onDragAndDrop(evt); + } else if (signal.ID == ostd::BuiltinSignals::WindowResized) { evt.windowResized = &(ogfx::WindowResizedData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::WindowResized; m_rootWidget.__onWindowResized(evt); } else if (signal.ID == ostd::BuiltinSignals::MouseMoved) { evt.mouse = &(ogfx::MouseEventData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::MouseMoved; m_rootWidget.__onMouseMoved(evt); } else if (signal.ID == ostd::BuiltinSignals::MousePressed) { evt.mouse = &(ogfx::MouseEventData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::MousePressed; m_rootWidget.__onMousePressed(evt); } else if (signal.ID == ostd::BuiltinSignals::MouseReleased) { evt.mouse = &(ogfx::MouseEventData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::MouseReleased; m_rootWidget.__onMouseReleased(evt); } else if (signal.ID == ostd::BuiltinSignals::KeyPressed) { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::KeyPressed; m_rootWidget.__onKeyPressed(evt); } else if (signal.ID == ostd::BuiltinSignals::KeyReleased) { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::KeyReleased; m_rootWidget.__onKeyReleased(evt); if (evt.keyboard->keyCode == KeyCode::Escape) close(); @@ -544,6 +585,7 @@ namespace ogfx else if (signal.ID == ostd::BuiltinSignals::TextEntered) { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; + evt.__original_signal_id = ostd::BuiltinSignals::TextEntered; m_rootWidget.__onTextEntered(evt); } onSignal(signal); diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 24212dd..049ed2b 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/src/ogfx/gui/widgets/RootWidget.cpp b/src/ogfx/gui/widgets/RootWidget.cpp new file mode 100644 index 0000000..098ca12 --- /dev/null +++ b/src/ogfx/gui/widgets/RootWidget.cpp @@ -0,0 +1,55 @@ +/* + 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 "RootWidget.hpp" +#include "../../render/BasicRenderer.hpp" +#include "../Window.hpp" + +namespace ogfx +{ + namespace gui + { + 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 = getThemeValue(theme, "window.backgroundColor", getWindow().getClearColor()); + } + + void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx) + { + gfx.fillRect({ 0, 0, static_cast(getWindow().getWindowWidth()), static_cast(getWindow().getWindowHeight()) }, m_color); + } + } + } +} diff --git a/src/ogfx/gui/widgets/RootWidget.hpp b/src/ogfx/gui/widgets/RootWidget.hpp new file mode 100644 index 0000000..06a91b0 --- /dev/null +++ b/src/ogfx/gui/widgets/RootWidget.hpp @@ -0,0 +1,44 @@ +/* + 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 + +namespace ogfx +{ + namespace gui + { + namespace widgets + { + class RootWidget : public Widget + { + public: + RootWidget(WindowCore& window); + void onWindowResized(const Event& event) override; + void applyTheme(const ostd::Stylesheet& theme) override; + void onDraw(ogfx::BasicRenderer2D& gfx) override; + + private: + ostd::Color m_color { ostd::Colors::Transparent }; + }; + } + } +} diff --git a/src/ogfx/gui/widgets/Widget.cpp b/src/ogfx/gui/widgets/Widget.cpp index 1a4c790..4c4ec3c 100644 --- a/src/ogfx/gui/widgets/Widget.cpp +++ b/src/ogfx/gui/widgets/Widget.cpp @@ -21,7 +21,6 @@ #include "Widget.hpp" #include "gui/Events.hpp" #include "io/Memory.hpp" -#include "utils/Keycodes.hpp" #include "../..//render/BasicRenderer.hpp" #include "../Window.hpp" @@ -29,273 +28,6 @@ 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)); - w->setThemeQualifier("focused", true); - } - else - w->m_focused = false; - } - widget.m_focused = true; - widget.onFocusGained(Event(m_window)); - widget.setThemeQualifier("focused", true); - 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; - gfx.pushClippingRect({ w->getGlobalPosition(), w->getSize() }, true); - w->__draw(gfx); - gfx.popClippingRect(); - } - } - - 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() || w->m_stopEvents) - 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() || w->m_stopEvents) - { - bool mouseOut = false; - for (int32_t j = i - 1; j >= 0; j--) - { - Widget* ww = m_widgetList[j]; - if (ww->m_mouseInside) - { - ww->m_mouseInside = false; - ww->__onMouseExited(event); - } - } - 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; @@ -403,7 +135,7 @@ namespace ogfx } } - bool Widget::getThemeQualifier(const ostd::String& qualifier) + bool Widget::getThemeQualifier(const ostd::String& qualifier) const { for (auto& [name, state] : m_qualifierList) { @@ -473,6 +205,18 @@ namespace ogfx } } + 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 (hasChildren()) @@ -574,7 +318,7 @@ namespace ogfx } } - void Widget::__onWIndowFocused(const Event& event) + void Widget::__onWindowFocused(const Event& event) { if (hasChildren()) m_widgets.onWindowFocused(event); @@ -604,33 +348,5 @@ namespace ogfx 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 = getThemeValue(theme, "window.backgroundColor", getWindow().getClearColor()); - } - - void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx) - { - gfx.fillRect({ 0, 0, static_cast(getWindow().getWindowWidth()), static_cast(getWindow().getWindowHeight()) }, m_color); - } - } } } diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index 1454752..b18a9a3 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -21,56 +21,16 @@ #pragma once #include +#include #include #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 ostd::Stylesheet& 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 { private: struct ThemeOverride @@ -95,7 +55,7 @@ namespace ogfx void addThemeOverride(const ostd::String& fullKey, ostd::Stylesheet::TypeVariant value, bool propagate = true); void reloadTheme(void); void setThemeQualifier(const ostd::String& qualifier, bool value = true); - bool getThemeQualifier(const ostd::String& qualifier); + bool getThemeQualifier(const ostd::String& qualifier) const; bool addThemeID(const ostd::String& id); bool removeThemeID(const ostd::String& id); inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; } @@ -106,6 +66,7 @@ namespace ogfx inline virtual void onMousePressed(const Event& event) { } inline virtual void onMouseReleased(const Event& event) { } inline virtual void onMouseMoved(const Event& event) { } + inline virtual void onDragAndDrop(const Event& event) { } inline virtual void onMouseEntered(const Event& event) { } inline virtual void onMouseExited(const Event& event) { } inline virtual void onMouseDragged(const Event& event) { } @@ -123,6 +84,7 @@ namespace ogfx void __update(void); void __onMousePressed(const Event& event); void __onMouseReleased(const Event& event); + void __onDragAndDrop(const Event& event); void __onMouseMoved(const Event& event); void __onMouseEntered(const Event& event); void __onMouseExited(const Event& event); @@ -132,13 +94,14 @@ namespace ogfx void __onTextEntered(const Event& event); void __onWindowClosed(const Event& event); void __onWindowResized(const Event& event); - void __onWIndowFocused(const Event& event); + void __onWindowFocused(const Event& event); void __onWindowFocusLost(const Event& event); void __applyTheme(const ostd::Stylesheet& 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 setDragAndDropCallback(EventCallback callback) { callback_onDragAndDrop = callback; } inline virtual void setMouseEnteredCallback(EventCallback callback) { callback_onMouseEntered = callback; } inline virtual void setMouseExitedCallback(EventCallback callback) { callback_onMouseExited = callback; } inline virtual void setMouseDraggedCallback(EventCallback callback) { callback_onMouseDragged = callback; } @@ -147,7 +110,7 @@ namespace ogfx 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 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; } @@ -168,8 +131,11 @@ namespace ogfx inline bool isFocusEnabled(void) const { return m_allowFocus; } inline void enableFocus(bool enable = true) { m_allowFocus = enable; } inline void disableFocus(void) { enableFocus(false); } - inline void disabble(void) { enable(false); } + inline void disable(void) { enable(false); } inline void enableStopEvents(bool enable = true) { m_stopEvents = enable; } + inline bool isDragAndDropEnabled(void) const { return m_acceptDragAndDrop; } + inline void enableDragAndDrop(bool enable = true) { m_acceptDragAndDrop = enable; } + inline void disableDragAndDrop(void) { enableDragAndDrop(false); } template inline T getThemeValue(const ostd::Stylesheet &theme, const ostd::String& key, const T& fallback) @@ -177,6 +143,10 @@ namespace ogfx return theme.get(key, fallback, getThemeIDList(), getThemeQualifierList()); } + inline static void setDragAndDropData(ostd::BaseObject& data) { s_dragAndDropData = &data; } + inline static void clearDragAndDropData(void) { s_dragAndDropData = nullptr; } + inline static ostd::BaseObject* getDragAndDropData(void) { return s_dragAndDropData; } + protected: inline void disableChildren(void) { m_allowChildren = false; } inline void disableDrawBox(void) { m_drawBox = false; } @@ -194,6 +164,7 @@ namespace ogfx EventCallback callback_onMousePressed { nullptr }; EventCallback callback_onMouseReleased { nullptr }; + EventCallback callback_onDragAndDrop { nullptr }; EventCallback callback_onMouseMoved { nullptr }; EventCallback callback_onMouseEntered { nullptr }; EventCallback callback_onMouseExited { nullptr }; @@ -219,6 +190,7 @@ namespace ogfx bool m_enabled { true }; bool m_stopEvents { true }; bool m_clipContents { true }; + bool m_acceptDragAndDrop { false }; MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None }; std::vector m_themeIDList; @@ -236,21 +208,9 @@ namespace ogfx ostd::Rectangle m_padding { 0, 0, 0, 0 }; + inline static ostd::BaseObject* s_dragAndDropData { nullptr }; + friend class WidgetManager; }; - namespace widgets - { - class RootWidget : public Widget - { - public: - RootWidget(WindowCore& window); - void onWindowResized(const Event& event) override; - void applyTheme(const ostd::Stylesheet& theme) override; - void onDraw(ogfx::BasicRenderer2D& gfx) override; - - private: - ostd::Color m_color { ostd::Colors::Transparent }; - }; - } } } diff --git a/src/ostd/utils/Logic.cpp b/src/ostd/utils/Logic.cpp index a2980da..a95a783 100755 --- a/src/ostd/utils/Logic.cpp +++ b/src/ostd/utils/Logic.cpp @@ -1,3 +1,23 @@ +/* + 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 "Logic.hpp" #include "../string/String.hpp" #ifndef __APPLE__ diff --git a/src/ostd/utils/Signals.hpp b/src/ostd/utils/Signals.hpp index b640792..9455875 100755 --- a/src/ostd/utils/Signals.hpp +++ b/src/ostd/utils/Signals.hpp @@ -41,6 +41,8 @@ namespace ostd inline static constexpr uint32_t TextEntered = 0x0007; inline static constexpr uint32_t OnGuiEvent = 0x2001; + inline static constexpr uint32_t FileDragAndDropped = 0x2002; + inline static constexpr uint32_t TextDragAndDropped = 0x2003; inline static constexpr uint32_t BeforeSDLShutdown = 0x3001; diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 4e9fd5a..6652919 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -30,7 +30,7 @@ class Window : public ogfx::gui::Window inline void onInitialize(void) override { m_panel1.setSize(300, 140); - m_panel1.setPosition(200, 150); + m_panel1.setPosition(350, 50); m_panel1.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void { pos = { event.mouse->position_x, event.mouse->position_y }; }); @@ -43,6 +43,13 @@ class Window : public ogfx::gui::Window m_panel2.setSize(300, 140); m_panel2.setPosition(130, 200); + m_panel2.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void { + ogfx::gui::Widget::setDragAndDropData(m_label3); + setCursor(eCursor::Move); + }); + m_panel2.setMouseReleasedCallback([&](const ogfx::gui::Event& event) -> void { + setCursor(eCursor::Default); + }); m_label1.setPosition(100, 200); m_label1.setText("Hello World!"); @@ -57,6 +64,14 @@ class Window : public ogfx::gui::Window m_label2.setText("Ciccia Bella!"); m_label2.addThemeID("testLabel"); m_label2.addThemeID("testLabel2"); + m_label2.enableDragAndDrop(); + m_label2.setDragAndDropCallback([&](const ogfx::gui::Event& event) -> void { + std::cout << "DROP\n"; + if (auto data = ogfx::gui::Widget::getDragAndDropData()) + { + std::cout << static_cast(*data).getText() << "!\n"; + } + }); m_panel1.addChild(m_label2); m_label1.addThemeOverride("@:pressed.label.textColor", ostd::Colors::Crimson);