Added Context Menu ANimation

This commit is contained in:
OmniaX-Dev 2026-04-30 20:36:45 +02:00
parent 68bf336530
commit 2de45908a9
5 changed files with 50 additions and 4 deletions

View file

@ -287,6 +287,8 @@ const $backgroundColor = rgba(30, 30, 30, 255)
borderColor = $borderColor borderColor = $borderColor
useSelectionGradient = true useSelectionGradient = true
selectionGradient = gradientV($accentColor - $accentColorDark) selectionGradient = gradientV($accentColor - $accentColorDark)
animateOpen = true
animationDelay = 120
} }
% ========================== % ==========================

View file

@ -49,6 +49,8 @@ FIX: Button state getting hung after showing an openFileDialog
FIX: ListView last item is covered by scrollbar FIX: ListView last item is covered by scrollbar
FIX: ListView last item not clickable even when no horizontal scrollbar present FIX: ListView last item not clickable even when no horizontal scrollbar present
FIX: Refresh display text in ComboBox when font size changes and also on theme reload FIX: Refresh display text in ComboBox when font size changes and also on theme reload
Add setSelectedMenuItem() function to ComboBox
FIX: ContextMenu rendering partly outside the screen when showing it close to the right Window border
@ -80,7 +82,7 @@ Implement following widgets:
***Horizontal ***Horizontal
***Grid ***Grid
***Fill ***Fill
ComboBox ***ComboBox
TreeView TreeView
Text Input Text Input
Text Area Text Area

View file

@ -62,20 +62,52 @@ namespace ogfx
setBorderColor(theme.get<Color>("context.borderColor", getBorderColor(), {}, {})); setBorderColor(theme.get<Color>("context.borderColor", getBorderColor(), {}, {}));
enableSelectionGradient(theme.get<bool>("context.useSelectionGradient", isSelectionGradientEnabled(), {}, {})); enableSelectionGradient(theme.get<bool>("context.useSelectionGradient", isSelectionGradientEnabled(), {}, {}));
setSelectionGradient(theme.get<ColorGradient>("context.selectionGradient", getSelectionGradient(), {}, {})); setSelectionGradient(theme.get<ColorGradient>("context.selectionGradient", getSelectionGradient(), {}, {}));
enableOpenAnimation(theme.get<bool>("context.animateOpen", isOpenAnimationEnabled(), {}, {}));
setAnimationDelayMS(theme.get<i32>("context.animationDelay", getAnimationDelayMS(), {}, {}));
} }
void ContextMenu::draw(BasicRenderer2D& gfx) void ContextMenu::draw(BasicRenderer2D& gfx)
{ {
if (!m_visible) return; if (!m_visible) return;
for (const auto& panel : m_panels) for (const auto& panel : m_panels)
{
// Apply easing for a more natural feel
f32 t = panel.animProgress;
f32 eased = 1.0f - (1.0f - t) * (1.0f - t); // ease-out quadratic
f32 visibleHeight = panel.size.y * eased;
Rectangle clip {
panel.position.x,
panel.position.y,
panel.size.x,
visibleHeight
};
gfx.pushClippingRect(clip);
draw_panel(gfx, panel); draw_panel(gfx, panel);
gfx.popClippingRect();
}
} }
void ContextMenu::update(void) void ContextMenu::update(void)
{ {
if (!m_visible) return; if (!m_visible) return;
if (m_pendingOpenPanelDepth >= 0 && m_hoverOpenTimerActive && m_hoverOpenTimer.read() >= SubmenuOpenDelayMs) // Advance animation for any panel that isn't fully open yet.
f64 deltaMs = m_animClock.read(); // milliseconds since last update
m_animClock.restart(ostd::eTimeUnits::Milliseconds);
for (auto& panel : m_panels)
{
if (panel.animProgress < 1.0f)
{
panel.animProgress += (f32)(deltaMs / (f64)OpenAnimDurationMs);
if (panel.animProgress > 1.0f) panel.animProgress = 1.0f;
}
}
if (m_pendingOpenPanelDepth >= 0 && m_hoverOpenTimerActive && m_hoverOpenTimer.read() >= m_animationDelayMS)
{ {
Panel& parent = m_panels[m_pendingOpenPanelDepth]; Panel& parent = m_panels[m_pendingOpenPanelDepth];
i32 entryIdx = m_pendingOpenEntryIndex; i32 entryIdx = m_pendingOpenEntryIndex;
@ -271,6 +303,7 @@ namespace ogfx
m_panels.clear(); m_panels.clear();
push_panel(m_data.entries, pos, false); push_panel(m_data.entries, pos, false);
m_visible = true; m_visible = true;
m_animClock.startCount(ostd::eTimeUnits::Milliseconds);
} }
void ContextMenu::hide(void) void ContextMenu::hide(void)
@ -282,6 +315,7 @@ namespace ogfx
m_hoverCloseTimerActive = false; m_hoverCloseTimerActive = false;
m_pendingOpenPanelDepth = -1; m_pendingOpenPanelDepth = -1;
m_pendingOpenEntryIndex = -1; m_pendingOpenEntryIndex = -1;
m_animClock.endCount();
m_visible = false; m_visible = false;
} }
@ -327,6 +361,7 @@ namespace ogfx
if (p.position.y < 0) p.position.y = 0; if (p.position.y < 0) p.position.y = 0;
if (p.position.x < 0) p.position.x = 0; if (p.position.x < 0) p.position.x = 0;
p.animProgress = m_animateOpen ? 0.0f : 1.0f;
m_panels.push_back(p); m_panels.push_back(p);
} }

View file

@ -110,6 +110,8 @@ namespace ogfx
f32 entryHeight { 0 }; f32 entryHeight { 0 };
i32 hoveredIndex { -1 }; i32 hoveredIndex { -1 };
i32 openedSubmenuIndex { -1 }; i32 openedSubmenuIndex { -1 };
f32 animProgress { 0 }; // 0.0 = collapsed, 1.0 = fully open
}; };
public: public:
inline ContextMenu(WindowCore& window) : m_window(window) { create(); } inline ContextMenu(WindowCore& window) : m_window(window) { create(); }
@ -140,6 +142,8 @@ namespace ogfx
OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor); OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor);
OSTD_PARAM_GETSET(ColorGradient, SelectionGradient, m_selectionGradient); OSTD_PARAM_GETSET(ColorGradient, SelectionGradient, m_selectionGradient);
OSTD_BOOL_PARAM_GETSET_E(SelectionGradient, m_useSelectionGradient); OSTD_BOOL_PARAM_GETSET_E(SelectionGradient, m_useSelectionGradient);
OSTD_BOOL_PARAM_GETSET_E(OpenAnimation, m_animateOpen);
OSTD_PARAM_GETSET(u64, AnimationDelayMS, m_animationDelayMS);
private: private:
void push_panel(stdvec<Entry>& entries, const Vec2& anchorTopLeft, bool flipLeft = false); void push_panel(stdvec<Entry>& entries, const Vec2& anchorTopLeft, bool flipLeft = false);
@ -156,6 +160,7 @@ namespace ogfx
Instance m_data; Instance m_data;
f32 m_entryHeight { 0 }; f32 m_entryHeight { 0 };
Vec2 m_mousePos { 0, 0 }; Vec2 m_mousePos { 0, 0 };
ostd::Timer m_animClock;
stdvec<Panel> m_panels; stdvec<Panel> m_panels;
@ -178,9 +183,11 @@ namespace ogfx
Color m_borderColor { "#400000FF" }; Color m_borderColor { "#400000FF" };
bool m_useSelectionGradient { true }; bool m_useSelectionGradient { true };
ColorGradient m_selectionGradient { { "#C21135FF", "#820B23FF" }, { 1.0f } }; ColorGradient m_selectionGradient { { "#C21135FF", "#820B23FF" }, { 1.0f } };
bool m_animateOpen { true };
u64 m_animationDelayMS { 300 };
static constexpr u64 SubmenuOpenDelayMs { 300 };
static constexpr u64 SubmenuCloseDelayMs { 50 }; static constexpr u64 SubmenuCloseDelayMs { 50 };
static constexpr u64 OpenAnimDurationMs { 120 };
friend class Instance; friend class Instance;
}; };
} }

View file

@ -829,7 +829,7 @@ namespace ogfx
setTypeName("ogfx::gui::Window"); setTypeName("ogfx::gui::Window");
m_gfx.init(*this); m_gfx.init(*this);
loadDefaultTHeme(); loadDefaultTHeme();
setBlockingEventsRefreshFPS(30); setBlockingEventsRefreshFPS(60);
m_fixedUpdateTimer.create(60.0, [this](f64 dt) { m_fixedUpdateTimer.create(60.0, [this](f64 dt) {
__on_fixed_update(); __on_fixed_update();