diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 4f42c68a..d49add0e 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -993,6 +993,9 @@ void CConfigManager::setDefaultAnimationVars() { m_animationTree.createNode("fadeLayers", "fade"); m_animationTree.createNode("fadeLayersIn", "fadeLayers"); m_animationTree.createNode("fadeLayersOut", "fadeLayers"); + m_animationTree.createNode("fadePopups", "fade"); + m_animationTree.createNode("fadePopupsIn", "fadePopups"); + m_animationTree.createNode("fadePopupsOut", "fadePopups"); // workspaces m_animationTree.createNode("workspacesIn", "workspaces"); diff --git a/src/desktop/Popup.cpp b/src/desktop/Popup.cpp index 702ebdf9..cf97cf3d 100644 --- a/src/desktop/Popup.cpp +++ b/src/desktop/Popup.cpp @@ -1,10 +1,12 @@ #include "Popup.hpp" #include "../config/ConfigValue.hpp" +#include "../config/ConfigManager.hpp" #include "../Compositor.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/XDGShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../managers/SeatManager.hpp" +#include "../managers/AnimationManager.hpp" #include "../desktop/LayerSurface.hpp" #include "../managers/input/InputManager.hpp" #include "../render/Renderer.hpp" @@ -51,6 +53,20 @@ CPopup::~CPopup() { void CPopup::initAllSignals() { + g_pAnimationManager->createAnimation(0.f, m_alpha, g_pConfigManager->getAnimationPropertyConfig("fade"), AVARDAMAGE_NONE); + m_alpha->setUpdateCallback([this](auto) { + // + g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()}); + }); + m_alpha->setCallbackOnEnd( + [this](auto) { + if (inert()) { + g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()}); + fullyDestroy(); + } + }, + false); + if (!m_resource) { if (!m_windowOwner.expired()) m_listeners.newPopup = m_windowOwner->m_xdgSurface->m_events.newPopup.listen([this](const auto& resource) { this->onNewPopup(resource); }); @@ -83,6 +99,23 @@ void CPopup::onDestroy() { if (!m_parent) return; // head node + m_subsurfaceHead.reset(); + m_children.clear(); + + if (m_fadingOut && m_alpha->isBeingAnimated()) { + Debug::log(LOG, "popup {:x}: skipping full destroy, animating", (uintptr_t)this); + return; + } + + fullyDestroy(); +} + +void CPopup::fullyDestroy() { + Debug::log(LOG, "popup {:x} fully destroying", (uintptr_t)this); + + g_pHyprRenderer->makeEGLCurrent(); + std::erase_if(g_pHyprOpenGL->m_popupFramebuffers, [&](const auto& other) { return other.first.expired() || other.first == m_self; }); + std::erase_if(m_parent->m_children, [this](const auto& other) { return other.get() == this; }); } @@ -112,6 +145,9 @@ void CPopup::onMap() { if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); + + m_alpha->setValueAndWarp(0.F); + *m_alpha = 1.F; } void CPopup::onUnmap() { @@ -124,13 +160,12 @@ void CPopup::onUnmap() { return; } - m_mapped = false; - // if the popup committed a different size right now, we also need to damage the old size. const Vector2D MAX_DAMAGE_SIZE = {std::max(m_lastSize.x, m_resource->m_surface->m_surface->m_current.size.x), std::max(m_lastSize.y, m_resource->m_surface->m_surface->m_current.size.y)}; m_lastSize = m_resource->m_surface->m_surface->m_current.size; + m_lastPos = coordsRelativeToParent(); const auto COORDS = coordsGlobal(); @@ -142,6 +177,16 @@ void CPopup::onUnmap() { box = CBox{COORDS, MAX_DAMAGE_SIZE}.expand(4); g_pHyprRenderer->damageBox(box); + m_lastSize = MAX_DAMAGE_SIZE; + + g_pHyprRenderer->makeSnapshot(m_self); + + m_fadingOut = true; + m_alpha->setValueAndWarp(1.F); + *m_alpha = 0.F; + + m_mapped = false; + m_subsurfaceHead.reset(); if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) @@ -245,7 +290,7 @@ Vector2D CPopup::coordsRelativeToParent() { Vector2D offset; if (!m_resource) - return {}; + return m_lastPos; WP current = m_self; offset -= current->m_resource->m_surface->m_current.geometry.pos(); @@ -381,3 +426,11 @@ WP CPopup::at(const Vector2D& globalCoords, bool allowsInput) { bool CPopup::inert() const { return m_inert; } + +PHLMONITOR CPopup::getMonitor() { + if (!m_windowOwner.expired()) + return m_windowOwner->m_monitor.lock(); + if (!m_layerOwner.expired()) + return m_layerOwner->m_monitor.lock(); + return nullptr; +} diff --git a/src/desktop/Popup.hpp b/src/desktop/Popup.hpp index fdbce5a5..964b36b6 100644 --- a/src/desktop/Popup.hpp +++ b/src/desktop/Popup.hpp @@ -4,6 +4,7 @@ #include "Subsurface.hpp" #include "../helpers/signal/Signal.hpp" #include "../helpers/memory/Memory.hpp" +#include "../helpers/AnimatedVariable.hpp" class CXDGPopupResource; @@ -21,6 +22,7 @@ class CPopup { SP getT1Owner(); Vector2D coordsRelativeToParent(); Vector2D coordsGlobal(); + PHLMONITOR getMonitor(); Vector2D size(); @@ -45,6 +47,10 @@ class CPopup { WP m_self; bool m_mapped = false; + // fade in-out + PHLANIMVAR m_alpha; + bool m_fadingOut = false; + private: CPopup() = default; @@ -82,6 +88,7 @@ class CPopup { void reposition(); void recheckChildrenRecursive(); void sendScale(); + void fullyDestroy(); Vector2D localToGlobal(const Vector2D& rel); Vector2D t1ParentCoords(); diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 5b3189e9..9e61106b 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -298,6 +298,7 @@ class CHyprOpenGLImpl { std::map m_windowFramebuffers; std::map m_layerFramebuffers; + std::map, CFramebuffer> m_popupFramebuffers; std::map m_monitorRenderResources; std::map m_monitorBGFBs; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 16a6b478..9e04dd38 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -472,7 +472,6 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T const auto PWORKSPACE = pWindow->m_workspace; const auto REALPOS = pWindow->m_realPosition->value() + (pWindow->m_pinned ? Vector2D{} : PWORKSPACE->m_renderOffset->value()); static auto PDIMAROUND = CConfigValue("decoration:dim_around"); - static auto PBLUR = CConfigValue("decoration:blur:enabled"); CSurfacePassElement::SRenderData renderdata = {pMonitor, time}; CBox textureBox = {REALPOS.x, REALPOS.y, std::max(pWindow->m_realSize->value().x, 5.0), std::max(pWindow->m_realSize->value().y, 5.0)}; @@ -639,10 +638,9 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.squishOversized = false; // don't squish popups renderdata.popup = true; - static CConfigValue PBLURPOPUPS = CConfigValue("decoration:blur:popups"); static CConfigValue PBLURIGNOREA = CConfigValue("decoration:blur:popups_ignorealpha"); - renderdata.blur = *PBLURPOPUPS && *PBLUR; + renderdata.blur = shouldBlur(pWindow->m_popupHead); if (renderdata.blur) { renderdata.discardMode |= DISCARD_ALPHA; @@ -656,11 +654,17 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T pWindow->m_popupHead->breadthfirst( [this, &renderdata](WP popup, void* data) { + if (popup->m_fadingOut) { + renderSnapshot(popup); + return; + } + if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) return; const auto pos = popup->coordsRelativeToParent(); const Vector2D oldPos = renderdata.pos; renderdata.pos += pos; + renderdata.alpha = popup->m_alpha->value(); popup->m_wlSurface->resource()->breadthfirst( [this, &renderdata](SP s, const Vector2D& offset, void* data) { @@ -676,6 +680,8 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.pos = oldPos; }, &renderdata); + + renderdata.alpha = 1.F; } if (decorate) { @@ -2468,6 +2474,54 @@ void CHyprRenderer::makeSnapshot(PHLLS pLayer) { m_bRenderingSnapshot = false; } +void CHyprRenderer::makeSnapshot(WP popup) { + // we trust the window is valid. + const auto PMONITOR = popup->getMonitor(); + + if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0) + return; + + if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) + return; + + CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y}; + + makeEGLCurrent(); + + const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_popupFramebuffers[popup]; + + PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + + beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, PFRAMEBUFFER); + + m_bRenderingSnapshot = true; + + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); // JIC + + CSurfacePassElement::SRenderData renderdata; + renderdata.pos = popup->coordsGlobal() - PMONITOR->m_position; + renderdata.alpha = 1.F; + renderdata.dontRound = true; // don't round popups + renderdata.pMonitor = PMONITOR; + renderdata.squishOversized = false; // don't squish popups + renderdata.popup = true; + + popup->m_wlSurface->resource()->breadthfirst( + [this, &renderdata](SP s, const Vector2D& offset, void* data) { + renderdata.localPos = offset; + renderdata.texture = s->m_current.texture; + renderdata.surface = s; + renderdata.mainSurface = false; + m_renderPass.add(makeUnique(renderdata)); + renderdata.surfaceCounter++; + }, + nullptr); + + endRender(); + + m_bRenderingSnapshot = false; +} + void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { static auto PDIMAROUND = CConfigValue("decoration:dim_around"); @@ -2570,6 +2624,41 @@ void CHyprRenderer::renderSnapshot(PHLLS pLayer) { m_renderPass.add(makeUnique(std::move(data))); } +void CHyprRenderer::renderSnapshot(WP popup) { + if (!g_pHyprOpenGL->m_popupFramebuffers.contains(popup)) + return; + + static CConfigValue PBLURIGNOREA = CConfigValue("decoration:blur:popups_ignorealpha"); + + const auto FBDATA = &g_pHyprOpenGL->m_popupFramebuffers.at(popup); + + if (!FBDATA->getTexture()) + return; + + const auto PMONITOR = popup->getMonitor(); + + if (!PMONITOR) + return; + + CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y}; + + const bool SHOULD_BLUR = shouldBlur(popup); + + CTexPassElement::SRenderData data; + data.flipEndFrame = true; + data.tex = FBDATA->getTexture(); + data.box = {{}, PMONITOR->m_transformedSize}; + data.a = popup->m_alpha->value(); + data.damage = fakeDamage; + data.blur = SHOULD_BLUR; + data.blurA = sqrt(popup->m_alpha->value()); // sqrt makes the blur fadeout more realistic. + if (SHOULD_BLUR) + data.ignoreAlpha = std::max(*PBLURIGNOREA, 0.01F); /* ignore the alpha 0 regions */ + ; + + m_renderPass.add(makeUnique(std::move(data))); +} + bool CHyprRenderer::shouldBlur(PHLLS ls) { if (m_bRenderingSnapshot) return false; @@ -2586,3 +2675,10 @@ bool CHyprRenderer::shouldBlur(PHLWINDOW w) { const bool DONT_BLUR = w->m_windowData.noBlur.valueOrDefault() || w->m_windowData.RGBX.valueOrDefault() || w->opaque(); return *PBLUR && !DONT_BLUR; } + +bool CHyprRenderer::shouldBlur(WP p) { + static CConfigValue PBLURPOPUPS = CConfigValue("decoration:blur:popups"); + static CConfigValue PBLUR = CConfigValue("decoration:blur:enabled"); + + return *PBLURPOPUPS && *PBLUR; +} diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index 7798c0d4..4018fe40 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -81,8 +81,10 @@ class CHyprRenderer { void addWindowToRenderUnfocused(PHLWINDOW window); void makeSnapshot(PHLWINDOW); void makeSnapshot(PHLLS); + void makeSnapshot(WP); void renderSnapshot(PHLWINDOW); void renderSnapshot(PHLLS); + void renderSnapshot(WP); // if RENDER_MODE_NORMAL, provided damage will be written to. // otherwise, it will be the one used. @@ -134,6 +136,7 @@ class CHyprRenderer { bool shouldBlur(PHLLS ls); bool shouldBlur(PHLWINDOW w); + bool shouldBlur(WP p); bool m_cursorHidden = false; bool m_cursorHasSurface = false;