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;