From 99886e8c62c745681adfd476cb404c741c188cee Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Fri, 24 Apr 2026 06:43:26 +0200 Subject: [PATCH] Started work on Context Menu --- CMakeLists.txt | 1 + src/ogfx/gui/ContextMenu.cpp | 131 ++++++++++++++++++++++++++++++ src/ogfx/gui/ContextMenu.hpp | 78 ++++++++++++++++++ src/ogfx/gui/Window.cpp | 21 +++-- src/ogfx/gui/Window.hpp | 4 + src/ogfx/gui/widgets/ListView.cpp | 2 + src/ogfx/gui/widgets/Slider.cpp | 3 +- src/ostd/math/Geometry.hpp | 4 +- src/test/GuiTest.cpp | 21 +++++ 9 files changed, 251 insertions(+), 14 deletions(-) create mode 100644 src/ogfx/gui/ContextMenu.cpp create mode 100644 src/ogfx/gui/ContextMenu.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a60b9e8..16cdb5e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ list(APPEND OGFX_SOURCE_FILES ${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 + ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/ContextMenu.cpp # gui/widgets ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp diff --git a/src/ogfx/gui/ContextMenu.cpp b/src/ogfx/gui/ContextMenu.cpp new file mode 100644 index 0000000..793f8c8 --- /dev/null +++ b/src/ogfx/gui/ContextMenu.cpp @@ -0,0 +1,131 @@ +/* + 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 "ContextMenu.hpp" +#include "../render/BasicRenderer.hpp" +#include "Window.hpp" + +namespace ogfx +{ + namespace gui + { + void ContextMenu::Entry::update_size(BasicRenderer2D& gfx, i32 fontSize) + { + size = { 0, 0 }; + for (auto& entry : submenus) + { + auto s = gfx.getStringDimensions(entry.text, fontSize); + if (s.x > size.x) + size.x = s.x; + size.y += s.y; + entry.update_size(gfx, fontSize); + } + } + + ContextMenu& ContextMenu::create(void) + { + setSize(130, 300); + return *this; + } + + void ContextMenu::draw(BasicRenderer2D& gfx) + { + gfx.outlinedRect(*this, Colors::Crimson, Colors::Black, 2); + f32 y = 0; + for (auto& entry : m_data.entries) + { + Vec2 entryPos = getPosition() + Vec2 { m_padding.x, y + m_padding.y }; + Rectangle rect = { entryPos - Vec2 { m_padding.x, 0 }, getw(), m_entryHeight }; + if (rect.contains(m_mousePos, true)) + gfx.outlinedRect(rect, Colors::Chocolate, Colors::White, 1); + gfx.drawString(entry.text, entryPos, Colors::Black, getFontSize()); + y += m_entryHeight; + } + } + + void ContextMenu::onMouseReleased(const Event& event) + { + Vec2 mousePos { event.mouse->position_x, event.mouse->position_y }; + if (contains(mousePos, true)) + { + + } + else + { + hide(); + } + event.handle(); + } + + void ContextMenu::onMouseMoved(const Event& event) + { + Vec2 mousePos { event.mouse->position_x, event.mouse->position_y }; + if (contains(mousePos, true)) + { + m_mousePos = mousePos; + } + event.handle(); + } + + void ContextMenu::show(void) + { + m_visible = true; + } + + void ContextMenu::show(const Vec2& pos) + { + setPosition(pos); + m_visible = true; + } + + void ContextMenu::hide(void) + { + m_visible = false; + } + + void ContextMenu::setInstance(const Instance& instance) + { + m_data = instance; + update_size(); + } + + void ContextMenu::setFontSize(i32 size) + { + m_fontSize = size; + update_size(); + } + + void ContextMenu::update_size(void) + { + setSize(0, 0); + auto& gfx = m_window.getGFX(); + for (auto& entry : m_data.entries) + { + auto s = gfx.getStringDimensions(entry.text, getFontSize()); + if (s.x > getw()) + setw(s.x); + addh(s.y); + m_entryHeight = s.y; + entry.update_size(gfx, getFontSize()); + } + addSize({ m_padding.x + m_padding.w, m_padding.y + m_padding.h }); + } + } +} diff --git a/src/ogfx/gui/ContextMenu.hpp b/src/ogfx/gui/ContextMenu.hpp new file mode 100644 index 0000000..9505846 --- /dev/null +++ b/src/ogfx/gui/ContextMenu.hpp @@ -0,0 +1,78 @@ +/* + 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 + { + class ContextMenu : public Rectangle + { + public: struct Entry + { + inline Entry(const String& t, const stdvec& sub = {}) { text = t; } + String text { "" }; + stdvec submenus; + + private: + Vec2 size; + void update_size(BasicRenderer2D& gfx, i32 fontSize); + + friend class ContextMenu; + }; + public: struct Instance + { + stdvec entries; + }; + public: + inline ContextMenu(WindowCore& window) : m_window(window) { create(); } + ContextMenu& create(void); + void draw(BasicRenderer2D& gfx); + void onMouseReleased(const Event& event); + void onMouseMoved(const Event& event); + void show(void); + void show(const Vec2& pos); + void hide(void); + void setInstance(const Instance& instance); + void setFontSize(i32 size); + + inline bool isVisible(void) const { return m_visible; } + inline i32 getFontSize(void) const { return m_fontSize; } + + private: + void update_size(void); + + private: + WindowCore& m_window; + bool m_visible { false }; + Instance m_data; + f32 m_entryHeight { 0 }; + Rectangle m_padding { 10, 10, 10, 10 }; + Vec2 m_mousePos; + + i32 m_fontSize { 18 }; + + friend class Instance; + }; + } +} diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 68a672d..90f0966 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -495,11 +495,11 @@ namespace ogfx break; } MouseEventData::eButton button = MouseEventData::eButton::None; - switch (btn) + switch (event.button.button) { - case SDL_BUTTON_MASK(1): button = MouseEventData::eButton::Left; break; - case SDL_BUTTON_MASK(2): button = MouseEventData::eButton::Middle; break; - case SDL_BUTTON_MASK(3): button = MouseEventData::eButton::Right; break; + case SDL_BUTTON_LEFT: button = MouseEventData::eButton::Left; break; + case SDL_BUTTON_MIDDLE: button = MouseEventData::eButton::Middle; break; + case SDL_BUTTON_RIGHT: button = MouseEventData::eButton::Right; break; default: button = MouseEventData::eButton::None; break; } MouseEventData mmd(*this, mx, my, button); @@ -629,13 +629,6 @@ namespace ogfx else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) { MouseEventData mmd = get_mouse_state(event); - switch (event.button.button) - { - case SDL_BUTTON_MASK(1): mmd.button = MouseEventData::eButton::Left; break; - case SDL_BUTTON_MASK(2): mmd.button = MouseEventData::eButton::Middle; break; - case SDL_BUTTON_MASK(3): mmd.button = MouseEventData::eButton::Right; break; - default: mmd.button = MouseEventData::eButton::None; break; - } ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::MouseReleased, ostd::Signal::Priority::RealTime, mmd); } else if (event.type == SDL_EVENT_KEY_DOWN) @@ -877,6 +870,8 @@ namespace ogfx m_rootWidget.__update(); m_rootWidget.__draw(m_gfx); onRedraw(m_gfx); + if (m_cmenu.isVisible()) + m_cmenu.draw(m_gfx); if (isTooltipShown()) { @@ -938,6 +933,8 @@ namespace ogfx { evt.mouse = &(ogfx::MouseEventData&)signal.userData; evt.__original_signal_id = ostd::BuiltinSignals::MouseMoved; + if (m_cmenu.isVisible()) + m_cmenu.onMouseMoved(evt); m_rootWidget.__onMouseMoved(evt); } else if (signal.ID == ostd::BuiltinSignals::MouseScrolled) @@ -956,6 +953,8 @@ namespace ogfx { evt.mouse = &(ogfx::MouseEventData&)signal.userData; evt.__original_signal_id = ostd::BuiltinSignals::MouseReleased; + if (m_cmenu.isVisible()) + m_cmenu.onMouseReleased(evt); m_rootWidget.__onMouseReleased(evt); } else if (signal.ID == ostd::BuiltinSignals::KeyPressed) diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index a3a83ea..d223d57 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace ogfx { @@ -269,6 +270,8 @@ namespace ogfx inline Window(i32 width, i32 height, const String& title) { initialize(width, height, title); } void addWidget(Widget& widget, const Vec2& position = { 0, 0 }); void setTheme(const ostd::Stylesheet& theme) override; + inline void showContextMenu(const Vec2& pos) { m_cmenu.show(pos); } + inline void setContextMenu(const ContextMenu::Instance& instance) { m_cmenu.setInstance(instance); } inline virtual void onInitialize(void) { } inline virtual void onDestroy(void) { } @@ -293,6 +296,7 @@ namespace ogfx widgets::RootWidget m_rootWidget { *this }; ostd::StepTimer m_fixedUpdateTimer; ostd::StepTimer::TimePoint m_lastFrameTime; + ContextMenu m_cmenu { *this }; }; } } diff --git a/src/ogfx/gui/widgets/ListView.cpp b/src/ogfx/gui/widgets/ListView.cpp index 2542d9c..7cef358 100644 --- a/src/ogfx/gui/widgets/ListView.cpp +++ b/src/ogfx/gui/widgets/ListView.cpp @@ -158,6 +158,8 @@ namespace ogfx void ListView::onMouseReleased(const Event& event) { + if (!isMouseInside()) + return; if (event.mouse->button != ogfx::MouseEventData::eButton::Left) return; if (isMouseInsideAnyScrollbar()) diff --git a/src/ogfx/gui/widgets/Slider.cpp b/src/ogfx/gui/widgets/Slider.cpp index 89145b9..25b5a54 100644 --- a/src/ogfx/gui/widgets/Slider.cpp +++ b/src/ogfx/gui/widgets/Slider.cpp @@ -71,7 +71,8 @@ namespace ogfx { if (!isMouseInside()) return; - set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y })); + if (event.mouse->button == MouseEventData::eButton::Left) + set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y })); } void Slider::onMouseScrolled(const Event& event) diff --git a/src/ostd/math/Geometry.hpp b/src/ostd/math/Geometry.hpp index 737998f..7babe4d 100755 --- a/src/ostd/math/Geometry.hpp +++ b/src/ostd/math/Geometry.hpp @@ -340,8 +340,8 @@ namespace ostd inline virtual Vec2 addPos(Vec2 pos) { return addPos(pos.x, pos.y); } inline virtual f32 addw(f32 ww) { setw(getw() + ww); return getw(); } inline virtual f32 addh(f32 hh) { seth(geth() + hh); return geth(); } - inline virtual Vec2 addSize(f32 ww, f32 hh) { return Vec2(addw(ww), addh(hh)); } - inline virtual Vec2 addSize(Vec2 size) { return addPos(size.x, size.y); } + inline virtual Vec2 addSize(f32 ww, f32 hh) { addw(ww), addh(hh); return getSize(); } + inline virtual Vec2 addSize(Vec2 size) { addSize(size.x, size.y); return getSize(); } inline virtual f32 subx(f32 xx) { setx(getx() - xx); return getx(); } inline virtual f32 suby(f32 yy) { sety(gety() - yy); return gety(); } diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index f25b93c..091fb1f 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -182,6 +182,8 @@ class Window : public ogfx::gui::Window } return true; }); + + setContextMenu(m_menu); } inline void onSignal(ostd::Signal& signal) override @@ -192,6 +194,14 @@ class Window : public ogfx::gui::Window if (evtData.keyCode == ogfx::KeyCode::Escape) close(); } + else if (signal.ID == ostd::BuiltinSignals::MouseReleased) + { + auto& mmd = cast(signal.userData); + if (mmd.button == ogfx::MouseEventData::eButton::Right) + { + showContextMenu({ mmd.position_x, mmd.position_y }); + } + } } void onRedraw(ogfx::BasicRenderer2D& gfx) override @@ -224,6 +234,17 @@ class Window : public ogfx::gui::Window ListView m_list { *this }; Label m_drawCallsLbl { *this }; + ogfx::gui::ContextMenu::Instance m_menu { + { + { "Update" }, + { "File" }, + { "Open RAW" }, + { "Mouse Settings" }, + { "Terror" }, + { "Properties" } + } + }; + ostd::StepTimer m_timer; ostd::AsyncJob m_progressJob;