Polished first stage of Context Menu

This commit is contained in:
OmniaX-Dev 2026-04-24 11:29:05 +02:00
parent 99886e8c62
commit eb27fa42d1
10 changed files with 141 additions and 20 deletions

View file

@ -1,5 +1,4 @@
CompileFlags: CompileFlags:
CompilationDatabase: bin CompilationDatabase: bin
Add: ["--target=x86_64-w64-mingw32"]
Diagnostics: Diagnostics:
MissingIncludes: None MissingIncludes: None

View file

@ -258,7 +258,7 @@ const $accentColorDark = #6B0A1DFF
% ====== Slider ====== % ====== ListView ======
(list) { (list) {
(item) { (item) {
defaultTextColor = #202020FF defaultTextColor = #202020FF
@ -273,4 +273,21 @@ const $accentColorDark = #6B0A1DFF
showSeparatorLine = true showSeparatorLine = true
} }
% =================== % ======================
% ====== Context Menu ======
(context) {
padding = rect(16, 0, 16, 0)
itemSpacing = 6.0
fontSize = 18
backgroundColor = #CCAAAAFF
selectionColor = $accentColor
selectionTextColor = #FFFFFFFF
separatorLineColor = #700000FF
textColor = $textColor
submenuIndicatorColor = $accentColorDark
borderColor = #400000FF
}
% ==========================

View file

@ -33,6 +33,7 @@ Add multi-selection to ListView
Cache getStringDimensions() call in TabPanel::draw_tabs Cache getStringDimensions() call in TabPanel::draw_tabs
FIX: Optimize ListView FIX: Optimize ListView
FIX: Padding for ListView items FIX: Padding for ListView items
Add icons to ListView

View file

@ -33,7 +33,7 @@ namespace ogfx
{ {
auto s = gfx.getStringDimensions(entry.text, fontSize); auto s = gfx.getStringDimensions(entry.text, fontSize);
if (s.x > size.x) if (s.x > size.x)
size.x = s.x; size.x = s.x + (entry.submenus.size() > 0 ? 40 : 0);
size.y += s.y; size.y += s.y;
entry.update_size(gfx, fontSize); entry.update_size(gfx, fontSize);
} }
@ -45,19 +45,53 @@ namespace ogfx
return *this; return *this;
} }
void ContextMenu::applyTheme(const ostd::Stylesheet& theme)
{
setPadding(theme.get<Rectangle>("context.padding", getPadding(), {}, {}));
setItemSpacing(theme.get<f32>("context.itemSpacing", getItemSpacing(), {}, {}));
setFontSize(theme.get<i32>("context.fontSize", getFontSize(), {}, {}));
setBackgroundColor(theme.get<Color>("context.backgroundColor", getBackgroundColor(), {}, {}));
setSelectionColor(theme.get<Color>("context.selectionColor", getSelectionColor(), {}, {}));
setSelectionTextColor(theme.get<Color>("context.selectionTextColor", getSelectionTextColor(), {}, {}));
setSeparatorLineColor(theme.get<Color>("context.separatorLineColor", getSeparatorLineColor(), {}, {}));
setTextColor(theme.get<Color>("context.textColor", getTextColor(), {}, {}));
setSubmenuIndicatorColor(theme.get<Color>("context.submenuIndicatorColor", getSubmenuIndicatorColor(), {}, {}));
setBorderColor(theme.get<Color>("context.borderColor", getBorderColor(), {}, {}));
}
void ContextMenu::draw(BasicRenderer2D& gfx) void ContextMenu::draw(BasicRenderer2D& gfx)
{ {
gfx.outlinedRect(*this, Colors::Crimson, Colors::Black, 2); gfx.fillRect(*this, getBackgroundColor());
const f32 triPad = 14;
f32 y = 0; f32 y = 0;
i32 i = 0;
for (auto& entry : m_data.entries) for (auto& entry : m_data.entries)
{ {
Vec2 entryPos = getPosition() + Vec2 { m_padding.x, y + m_padding.y }; Vec2 entryPos = getPosition() + Vec2 { m_padding.x, y + m_padding.y };
Rectangle rect = { entryPos - Vec2 { m_padding.x, 0 }, getw(), m_entryHeight }; Rectangle rect = { entryPos - Vec2 { m_padding.x, 0 }, getw(), m_entryHeight };
if (rect.contains(m_mousePos, true)) if (rect.contains(m_mousePos, true))
gfx.outlinedRect(rect, Colors::Chocolate, Colors::White, 1); {
gfx.drawString(entry.text, entryPos, Colors::Black, getFontSize()); gfx.outlinedRect(rect, getSelectionColor(), getSeparatorLineColor(), 1, false, false, i != m_data.entries.size() , false);
gfx.drawVCenteredString(entry.text, { entryPos, getw(), m_entryHeight }, getSelectionTextColor(), getFontSize());
}
else
{
gfx.drawRect(rect, getSeparatorLineColor(), 1, false, false, i != m_data.entries.size() , false);
gfx.drawVCenteredString(entry.text, { entryPos, getw(), m_entryHeight }, getTextColor(), getFontSize());
}
if (entry.submenus.size() > 0)
{
Triangle tri {
{ rect.x + rect.w - m_entryHeight + triPad, rect.y + (triPad * 0.5f) },
{ rect.x + rect.w - (triPad * 0.5f), rect.y + (rect.h * 0.5f) },
{ rect.x + rect.w - m_entryHeight + triPad, rect.y + rect.h - (triPad * 0.5f) }
};
gfx.fillTriangle(tri, getSubmenuIndicatorColor());
}
y += m_entryHeight; y += m_entryHeight;
i++;
} }
gfx.drawRect(*this, getBorderColor(), 2);
} }
void ContextMenu::onMouseReleased(const Event& event) void ContextMenu::onMouseReleased(const Event& event)
@ -84,6 +118,16 @@ namespace ogfx
event.handle(); event.handle();
} }
void ContextMenu::onMousePressed(const Event& event)
{
event.handle();
}
void ContextMenu::onMouseScrolled(const Event& event)
{
event.handle();
}
void ContextMenu::show(void) void ContextMenu::show(void)
{ {
m_visible = true; m_visible = true;
@ -119,10 +163,11 @@ namespace ogfx
for (auto& entry : m_data.entries) for (auto& entry : m_data.entries)
{ {
auto s = gfx.getStringDimensions(entry.text, getFontSize()); auto s = gfx.getStringDimensions(entry.text, getFontSize());
if (s.x > getw()) f32 extra = (entry.submenus.size() > 0 ? s.y : 0);
setw(s.x); if (s.x + extra > getw())
addh(s.y); setw(s.x + extra);
m_entryHeight = s.y; addh(s.y + m_spacing);
m_entryHeight = s.y + m_spacing;
entry.update_size(gfx, getFontSize()); entry.update_size(gfx, getFontSize());
} }
addSize({ m_padding.x + m_padding.w, m_padding.y + m_padding.h }); addSize({ m_padding.x + m_padding.w, m_padding.y + m_padding.h });

View file

@ -30,7 +30,7 @@ namespace ogfx
{ {
public: struct Entry public: struct Entry
{ {
inline Entry(const String& t, const stdvec<Entry>& sub = {}) { text = t; } inline Entry(const String& t, const stdvec<Entry>& sub = {}) { text = t; submenus = sub; }
String text { "" }; String text { "" };
stdvec<Entry> submenus; stdvec<Entry> submenus;
@ -47,9 +47,12 @@ namespace ogfx
public: public:
inline ContextMenu(WindowCore& window) : m_window(window) { create(); } inline ContextMenu(WindowCore& window) : m_window(window) { create(); }
ContextMenu& create(void); ContextMenu& create(void);
void applyTheme(const ostd::Stylesheet& theme);
void draw(BasicRenderer2D& gfx); void draw(BasicRenderer2D& gfx);
void onMouseReleased(const Event& event); void onMouseReleased(const Event& event);
void onMouseMoved(const Event& event); void onMouseMoved(const Event& event);
void onMousePressed(const Event& event);
void onMouseScrolled(const Event& event);
void show(void); void show(void);
void show(const Vec2& pos); void show(const Vec2& pos);
void hide(void); void hide(void);
@ -59,6 +62,16 @@ namespace ogfx
inline bool isVisible(void) const { return m_visible; } inline bool isVisible(void) const { return m_visible; }
inline i32 getFontSize(void) const { return m_fontSize; } inline i32 getFontSize(void) const { return m_fontSize; }
OSTD_PARAM_GETSET(Rectangle, Padding, m_padding);
OSTD_PARAM_GETSET(f32, ItemSpacing, m_spacing);
OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor);
OSTD_PARAM_GETSET(Color, SelectionColor, m_selectionColor);
OSTD_PARAM_GETSET(Color, SelectionTextColor, m_selectionTextColor);
OSTD_PARAM_GETSET(Color, SeparatorLineColor, m_separatorLineColor);
OSTD_PARAM_GETSET(Color, TextColor, m_textColor);
OSTD_PARAM_GETSET(Color, SubmenuIndicatorColor, m_submenuIndicatorColor);
OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor);
private: private:
void update_size(void); void update_size(void);
@ -67,10 +80,19 @@ namespace ogfx
bool m_visible { false }; bool m_visible { false };
Instance m_data; Instance m_data;
f32 m_entryHeight { 0 }; f32 m_entryHeight { 0 };
Rectangle m_padding { 10, 10, 10, 10 }; Vec2 m_mousePos { 0, 0 };
Vec2 m_mousePos;
Rectangle m_padding { 16, 0, 16, 0 };
f32 m_spacing { 8 };
i32 m_fontSize { 18 }; i32 m_fontSize { 18 };
Color m_backgroundColor { "#6B0A1DFF" };
Color m_selectionColor { "#DC143CFF" };
Color m_selectionTextColor { "#F16A85FF" };
Color m_separatorLineColor { "#700000FF" };
Color m_textColor { "#F16A85FF" };
Color m_submenuIndicatorColor { "#111111FF" };
Color m_borderColor { "#400000FF" };
friend class Instance; friend class Instance;
}; };

View file

@ -829,6 +829,7 @@ namespace ogfx
m_guiTheme = &theme; m_guiTheme = &theme;
m_rootWidget.__applyTheme(theme, true); m_rootWidget.__applyTheme(theme, true);
m_rootWidget.reloadTheme(true); m_rootWidget.reloadTheme(true);
m_cmenu.applyTheme(theme);
} }
void Window::__on_window_init(i32 width, i32 height, const String& title) void Window::__on_window_init(i32 width, i32 height, const String& title)
@ -871,9 +872,11 @@ namespace ogfx
m_rootWidget.__draw(m_gfx); m_rootWidget.__draw(m_gfx);
onRedraw(m_gfx); onRedraw(m_gfx);
if (m_cmenu.isVisible()) if (m_cmenu.isVisible())
{
stopTooltipTimer();
m_cmenu.draw(m_gfx); m_cmenu.draw(m_gfx);
}
if (isTooltipShown()) else if (isTooltipShown())
{ {
auto textSize = m_gfx.getStringDimensions(getTooltipText(), m_rootWidget.getTooltipFontSize()); auto textSize = m_gfx.getStringDimensions(getTooltipText(), m_rootWidget.getTooltipFontSize());
Rectangle textBounds = { getMousePosition(), textSize }; Rectangle textBounds = { getMousePosition(), textSize };
@ -902,6 +905,8 @@ namespace ogfx
} }
else if (signal.ID == ostd::BuiltinSignals::WindowLostFocus) else if (signal.ID == ostd::BuiltinSignals::WindowLostFocus)
{ {
if (m_cmenu.isVisible())
m_cmenu.hide();
evt.__original_signal_id = ostd::BuiltinSignals::WindowLostFocus; evt.__original_signal_id = ostd::BuiltinSignals::WindowLostFocus;
m_rootWidget.__onWindowFocusLost(evt); m_rootWidget.__onWindowFocusLost(evt);
} }
@ -925,6 +930,8 @@ namespace ogfx
} }
else if (signal.ID == ostd::BuiltinSignals::WindowResized) else if (signal.ID == ostd::BuiltinSignals::WindowResized)
{ {
if (m_cmenu.isVisible())
m_cmenu.hide();
evt.windowResized = &(ogfx::WindowResizedData&)signal.userData; evt.windowResized = &(ogfx::WindowResizedData&)signal.userData;
evt.__original_signal_id = ostd::BuiltinSignals::WindowResized; evt.__original_signal_id = ostd::BuiltinSignals::WindowResized;
m_rootWidget.__onWindowResized(evt); m_rootWidget.__onWindowResized(evt);
@ -941,12 +948,16 @@ namespace ogfx
{ {
evt.mouse = &(ogfx::MouseEventData&)signal.userData; evt.mouse = &(ogfx::MouseEventData&)signal.userData;
evt.__original_signal_id = ostd::BuiltinSignals::MouseScrolled; evt.__original_signal_id = ostd::BuiltinSignals::MouseScrolled;
if (m_cmenu.isVisible())
m_cmenu.onMouseScrolled(evt);
m_rootWidget.__onMouseScrolled(evt); m_rootWidget.__onMouseScrolled(evt);
} }
else if (signal.ID == ostd::BuiltinSignals::MousePressed) else if (signal.ID == ostd::BuiltinSignals::MousePressed)
{ {
evt.mouse = &(ogfx::MouseEventData&)signal.userData; evt.mouse = &(ogfx::MouseEventData&)signal.userData;
evt.__original_signal_id = ostd::BuiltinSignals::MousePressed; evt.__original_signal_id = ostd::BuiltinSignals::MousePressed;
if (m_cmenu.isVisible())
m_cmenu.onMousePressed(evt);
m_rootWidget.__onMousePressed(evt); m_rootWidget.__onMousePressed(evt);
} }
else if (signal.ID == ostd::BuiltinSignals::MouseReleased) else if (signal.ID == ostd::BuiltinSignals::MouseReleased)

View file

@ -272,6 +272,7 @@ namespace ogfx
void Widget::__onMousePressed(const Event& event) void Widget::__onMousePressed(const Event& event)
{ {
if (event.isHandled()) return;
setThemeQualifier("pressed"); setThemeQualifier("pressed");
if (hasChildren()) if (hasChildren())
m_widgets.onMousePressed(event); m_widgets.onMousePressed(event);
@ -286,6 +287,7 @@ namespace ogfx
void Widget::__onMouseReleased(const Event& event) void Widget::__onMouseReleased(const Event& event)
{ {
if (event.isHandled()) return;
setThemeQualifier("pressed", false); setThemeQualifier("pressed", false);
m_pressedButton = ogfx::MouseEventData::eButton::None; m_pressedButton = ogfx::MouseEventData::eButton::None;
if (hasChildren()) if (hasChildren())
@ -312,6 +314,7 @@ namespace ogfx
void Widget::__onMouseMoved(const Event& event) void Widget::__onMouseMoved(const Event& event)
{ {
if (event.isHandled()) return;
if (hasChildren()) if (hasChildren())
m_widgets.onMouseMoved(event); m_widgets.onMouseMoved(event);
if (!event.isHandled()) if (!event.isHandled())
@ -326,6 +329,7 @@ namespace ogfx
void Widget::__onMouseScrolled(const Event& event) void Widget::__onMouseScrolled(const Event& event)
{ {
if (event.isHandled()) return;
if (hasChildren()) if (hasChildren())
m_widgets.onMouseScrolled(event); m_widgets.onMouseScrolled(event);
if (!event.isHandled()) if (!event.isHandled())
@ -338,6 +342,7 @@ namespace ogfx
void Widget::__onMouseEntered(const Event& event) void Widget::__onMouseEntered(const Event& event)
{ {
if (event.isHandled()) return;
setThemeQualifier("hover"); setThemeQualifier("hover");
if (callback_onMouseEntered) if (callback_onMouseEntered)
callback_onMouseEntered(event); callback_onMouseEntered(event);
@ -348,6 +353,7 @@ namespace ogfx
void Widget::__onMouseExited(const Event& event) void Widget::__onMouseExited(const Event& event)
{ {
if (event.isHandled()) return;
setThemeQualifier("hover", false); setThemeQualifier("hover", false);
if (callback_onMouseExited) if (callback_onMouseExited)
callback_onMouseExited(event); callback_onMouseExited(event);
@ -358,6 +364,7 @@ namespace ogfx
void Widget::__onMouseDragged(const Event& event) void Widget::__onMouseDragged(const Event& event)
{ {
if (event.isHandled()) return;
if (hasChildren()) if (hasChildren())
m_widgets.onMouseDragged(event); m_widgets.onMouseDragged(event);
if (!event.isHandled()) if (!event.isHandled())
@ -366,10 +373,13 @@ namespace ogfx
callback_onMouseDragged(event); callback_onMouseDragged(event);
onMouseDragged(event); onMouseDragged(event);
} }
if (isTooltipEnabled() && isMouseInside())
getWindow().restartTooltipTimer();
} }
void Widget::__onKeyPressed(const Event& event) void Widget::__onKeyPressed(const Event& event)
{ {
if (event.isHandled()) return;
if (hasChildren()) if (hasChildren())
m_widgets.onKeyPressed(event); m_widgets.onKeyPressed(event);
if (!event.isHandled()) if (!event.isHandled())
@ -382,6 +392,7 @@ namespace ogfx
void Widget::__onKeyReleased(const Event& event) void Widget::__onKeyReleased(const Event& event)
{ {
if (event.isHandled()) return;
m_pressedButton = ogfx::MouseEventData::eButton::None; m_pressedButton = ogfx::MouseEventData::eButton::None;
if (hasChildren()) if (hasChildren())
m_widgets.onKeyReleased(event); m_widgets.onKeyReleased(event);
@ -395,6 +406,7 @@ namespace ogfx
void Widget::__onTextEntered(const Event& event) void Widget::__onTextEntered(const Event& event)
{ {
if (event.isHandled()) return;
if (hasChildren()) if (hasChildren())
m_widgets.onTextEntered(event); m_widgets.onTextEntered(event);
if (!event.isHandled()) if (!event.isHandled())

View file

@ -436,13 +436,25 @@ namespace ogfx
void BasicRenderer2D::drawCenteredString(const String& str, const Vec2& center, const Color& color, i32 fontSize, f32 scale) void BasicRenderer2D::drawCenteredString(const String& str, const Vec2& center, const Color& color, i32 fontSize, f32 scale)
{ {
auto dims = getStringDimensions(str, fontSize); auto dims = getStringDimensions(str, fontSize);
drawString(str, { center.x - (dims.x * scale) * 0.5f, center.y - (dims.y * scale) * 0.5f }, color, fontSize, scale); drawString(str, { center.x - dims.x * 0.5f, center.y - dims.y * 0.5f }, color, fontSize, scale);
} }
void BasicRenderer2D::drawCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize, f32 scale) void BasicRenderer2D::drawCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize, f32 scale)
{ {
drawCenteredString(str, Vec2 { bounds.x + bounds.w * 0.5f, bounds.y + bounds.h * 0.5f }, color, fontSize, scale); drawCenteredString(str, Vec2 { bounds.x + bounds.w * 0.5f, bounds.y + bounds.h * 0.5f }, color, fontSize, scale);
} }
void BasicRenderer2D::drawVCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize, f32 scale)
{
auto dims = getStringDimensions(str, fontSize);
drawString(str, { bounds.x, (bounds.y + bounds.h * 0.5f) - dims.y * 0.5f }, color, fontSize, scale);
}
void BasicRenderer2D::drawHCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize, f32 scale)
{
auto dims = getStringDimensions(str, fontSize);
drawString(str, { (bounds.x + bounds.w * 0.5f) - dims.x * 0.5f, bounds.y }, color, fontSize, scale);
}
// ===================================================== SPECIALIZED ===================================================== // ===================================================== SPECIALIZED =====================================================

View file

@ -87,6 +87,8 @@ namespace ogfx
void drawString(const String& str, const Vec2& position, const Color& color, i32 fontSize = 0, f32 scale = 1.0f); void drawString(const String& str, const Vec2& position, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawCenteredString(const String& str, const Vec2& center, const Color& color, i32 fontSize = 0, f32 scale = 1.0f); void drawCenteredString(const String& str, const Vec2& center, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f); void drawCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawVCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawHCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawLine(const FLine& line, const Color& color, i32 thickness = 1, bool rounded = true); void drawLine(const FLine& line, const Color& color, i32 thickness = 1, bool rounded = true);
void drawRect(const Rectangle& rect, const Color& color, i32 thickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true); void drawRect(const Rectangle& rect, const Color& color, i32 thickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true);

View file

@ -152,7 +152,7 @@ class Window : public ogfx::gui::Window
t1.addWidget(m_slide, { 30, 250 }); t1.addWidget(m_slide, { 30, 250 });
t1.addWidget(m_slideLbl, { 340, 240 }); t1.addWidget(m_slideLbl, { 340, 240 });
t1.addWidget(m_list, { 30, 300 }); t1.addWidget(m_list, { 30, 300 });
t2.addWidget(m_panel2, { 500, 100 }); t1.addWidget(m_panel2, { 500, 100 });
m_panel3.addWidget(m_label4); m_panel3.addWidget(m_label4);
@ -238,8 +238,8 @@ class Window : public ogfx::gui::Window
{ {
{ "Update" }, { "Update" },
{ "File" }, { "File" },
{ "Open RAW" }, { "Open RAW", { { "Image" }, { "Text File" }, { "Audio" } } },
{ "Mouse Settings" }, { "Mouse Settings", { { "Test" } } },
{ "Terror" }, { "Terror" },
{ "Properties" } { "Properties" }
} }