diff --git a/extra/DefaultThemeDark.oss b/extra/DefaultThemeDark.oss index b2f7624..ee29b94 100644 --- a/extra/DefaultThemeDark.oss +++ b/extra/DefaultThemeDark.oss @@ -287,6 +287,8 @@ const $backgroundColor = rgba(30, 30, 30, 255) borderColor = $borderColor useSelectionGradient = true selectionGradient = gradientV($accentColor - $accentColorDark) + animateOpen = true + animationDelay = 120 } % ========================== diff --git a/other/TODO.txt b/other/TODO.txt index eb0fa97..319478a 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -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 not clickable even when no horizontal scrollbar present 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 ***Grid ***Fill - ComboBox + ***ComboBox TreeView Text Input Text Area diff --git a/src/ogfx/gui/ContextMenu.cpp b/src/ogfx/gui/ContextMenu.cpp index 211c213..1980284 100644 --- a/src/ogfx/gui/ContextMenu.cpp +++ b/src/ogfx/gui/ContextMenu.cpp @@ -62,20 +62,52 @@ namespace ogfx setBorderColor(theme.get("context.borderColor", getBorderColor(), {}, {})); enableSelectionGradient(theme.get("context.useSelectionGradient", isSelectionGradientEnabled(), {}, {})); setSelectionGradient(theme.get("context.selectionGradient", getSelectionGradient(), {}, {})); + enableOpenAnimation(theme.get("context.animateOpen", isOpenAnimationEnabled(), {}, {})); + setAnimationDelayMS(theme.get("context.animationDelay", getAnimationDelayMS(), {}, {})); } void ContextMenu::draw(BasicRenderer2D& gfx) { if (!m_visible) return; 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); + gfx.popClippingRect(); + } } void ContextMenu::update(void) { 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]; i32 entryIdx = m_pendingOpenEntryIndex; @@ -271,6 +303,7 @@ namespace ogfx m_panels.clear(); push_panel(m_data.entries, pos, false); m_visible = true; + m_animClock.startCount(ostd::eTimeUnits::Milliseconds); } void ContextMenu::hide(void) @@ -282,6 +315,7 @@ namespace ogfx m_hoverCloseTimerActive = false; m_pendingOpenPanelDepth = -1; m_pendingOpenEntryIndex = -1; + m_animClock.endCount(); m_visible = false; } @@ -327,6 +361,7 @@ namespace ogfx if (p.position.y < 0) p.position.y = 0; if (p.position.x < 0) p.position.x = 0; + p.animProgress = m_animateOpen ? 0.0f : 1.0f; m_panels.push_back(p); } diff --git a/src/ogfx/gui/ContextMenu.hpp b/src/ogfx/gui/ContextMenu.hpp index 9a0b62c..6e14de6 100644 --- a/src/ogfx/gui/ContextMenu.hpp +++ b/src/ogfx/gui/ContextMenu.hpp @@ -110,6 +110,8 @@ namespace ogfx f32 entryHeight { 0 }; i32 hoveredIndex { -1 }; i32 openedSubmenuIndex { -1 }; + + f32 animProgress { 0 }; // 0.0 = collapsed, 1.0 = fully open }; public: inline ContextMenu(WindowCore& window) : m_window(window) { create(); } @@ -140,6 +142,8 @@ namespace ogfx OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor); OSTD_PARAM_GETSET(ColorGradient, SelectionGradient, m_selectionGradient); OSTD_BOOL_PARAM_GETSET_E(SelectionGradient, m_useSelectionGradient); + OSTD_BOOL_PARAM_GETSET_E(OpenAnimation, m_animateOpen); + OSTD_PARAM_GETSET(u64, AnimationDelayMS, m_animationDelayMS); private: void push_panel(stdvec& entries, const Vec2& anchorTopLeft, bool flipLeft = false); @@ -156,6 +160,7 @@ namespace ogfx Instance m_data; f32 m_entryHeight { 0 }; Vec2 m_mousePos { 0, 0 }; + ostd::Timer m_animClock; stdvec m_panels; @@ -178,9 +183,11 @@ namespace ogfx Color m_borderColor { "#400000FF" }; bool m_useSelectionGradient { true }; 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 OpenAnimDurationMs { 120 }; friend class Instance; }; } diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index ccda826..9cf9fa9 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -829,7 +829,7 @@ namespace ogfx setTypeName("ogfx::gui::Window"); m_gfx.init(*this); loadDefaultTHeme(); - setBlockingEventsRefreshFPS(30); + setBlockingEventsRefreshFPS(60); m_fixedUpdateTimer.create(60.0, [this](f64 dt) { __on_fixed_update();