diff --git a/README.md b/README.md index d162c5005..485ca67a0 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,22 @@ # kwin-effects-forceblur [![AUR Version](https://img.shields.io/aur/version/kwin-effects-forceblur)](https://aur.archlinux.org/packages/kwin-effects-forceblur) -A fork of the KWin Blur effect for KDE Plasma 6 with the ability to blur any window on Wayland and X11. +Kwin-effects-forceblur (name subject to change) is a fork of the KWin Blur effect for KDE Plasma 6 with several improvements and bug fixes. Latest features are available on the ``develop`` branch. -![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/9d2f337e-badd-4d95-ba55-96c80202e196) -Window opacity has been set to 85% for System Settings and Dolphin, Firefox uses a transparent theme | [NixOS configuration](https://github.com/taj-ny/nix-config) +![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/1078cf12-e6da-43c7-80b4-d90a8b0f3404) +Window opacity has been set to 85% for System Settings, Dolphin and VSCodium, Firefox uses a transparent theme | [NixOS configuration](https://github.com/taj-ny/nix-config) # Features - Wayland support +- Force blur +- Rounded corners with optional anti-aliasing - Draw image behind windows instead of blurring (can be used with a blurred image of the wallpaper in order to achieve a very similar effect to blur but with **much** lower GPU usage) -- Rounded corners -- Fix for [artifacts](https://github.com/taj-ny/kwin-effects-forceblur/pull/38) when using a transparent color scheme + +### Bug fixes +Fixes for blur-related Plasma bugs that haven't been patched yet. + +- Blur may sometimes disappear during animations +- [Transparent color schemes don't work properly with the Breeze application style](https://github.com/taj-ny/kwin-effects-forceblur/pull/38) # Installation
diff --git a/flake.lock b/flake.lock index 88663c665..409ad87c0 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1716509168, - "narHash": "sha256-4zSIhSRRIoEBwjbPm3YiGtbd8HDWzFxJjw5DYSDy1n8=", + "lastModified": 1718160348, + "narHash": "sha256-9YrUjdztqi4Gz8n3mBuqvCkMo4ojrA6nASwyIKWMpus=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bfb7a882678e518398ce9a31a881538679f6f092", + "rev": "57d6973abba7ea108bac64ae7629e7431e0199b6", "type": "github" }, "original": { diff --git a/src/blur.cpp b/src/blur.cpp index 9ee260ddb..645c86506 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -14,7 +14,9 @@ #include "core/rendertarget.h" #include "core/renderviewport.h" #include "effect/effecthandler.h" +#include "opengl/glutils.h" #include "opengl/glplatform.h" +#include "utils.h" #include "utils/xcbutils.h" #include "wayland/blur.h" #include "wayland/display.h" @@ -93,15 +95,41 @@ BlurEffect::BlurEffect() } m_texturePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, - QStringLiteral(":/effects/blur/shaders/vertex.vert"), - QStringLiteral(":/effects/blur/shaders/texture.frag")); + QStringLiteral(":/effects/blur/shaders/vertex.vert"), + QStringLiteral(":/effects/blur/shaders/texture.frag")); if (!m_texturePass.shader) { - qCWarning(KWIN_BLUR) << "Failed to load noise pass shader"; + qCWarning(KWIN_BLUR) << "Failed to load texture pass shader"; return; } else { m_texturePass.mvpMatrixLocation = m_texturePass.shader->uniformLocation("modelViewProjectionMatrix"); m_texturePass.textureSizeLocation = m_texturePass.shader->uniformLocation("textureSize"); m_texturePass.texStartPosLocation = m_texturePass.shader->uniformLocation("texStartPos"); + m_texturePass.regionSizeLocation = m_texturePass.shader->uniformLocation("regionSize"); + } + + m_roundedCorners.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, + QStringLiteral(":/effects/blur/shaders/vertex.vert"), + QStringLiteral(":/effects/blur/shaders/roundedcorners.frag")); + if (!m_roundedCorners.shader) { + qCWarning(KWIN_BLUR) << "Failed to load rounded corners shader"; + return; + } else { + m_roundedCorners.roundTopLeftCornerLocation = m_roundedCorners.shader->uniformLocation("roundTopLeftCorner"); + m_roundedCorners.roundTopRightCornerLocation = m_roundedCorners.shader->uniformLocation("roundTopRightCorner"); + m_roundedCorners.roundBottomLeftCornerLocation = m_roundedCorners.shader->uniformLocation("roundBottomLeftCorner"); + m_roundedCorners.roundBottomRightCornerLocation = m_roundedCorners.shader->uniformLocation("roundBottomRightCorner"); + + m_roundedCorners.topCornerRadiusLocation = m_roundedCorners.shader->uniformLocation("topCornerRadius"); + m_roundedCorners.bottomCornerRadiusLocation = m_roundedCorners.shader->uniformLocation("bottomCornerRadius"); + + m_roundedCorners.antialiasingLocation = m_roundedCorners.shader->uniformLocation("antialiasing"); + + m_roundedCorners.regionSizeLocation = m_roundedCorners.shader->uniformLocation("regionSize"); + + m_roundedCorners.beforeBlurTextureLocation = m_roundedCorners.shader->uniformLocation("beforeBlurTexture"); + m_roundedCorners.afterBlurTextureLocation = m_roundedCorners.shader->uniformLocation("afterBlurTexture"); + + m_roundedCorners.mvpMatrixLocation = m_roundedCorners.shader->uniformLocation("modelViewProjectionMatrix"); } initBlurStrengthValues(); @@ -223,8 +251,11 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_blurDecorations = BlurConfig::blurDecorations(); m_windowClasses = BlurConfig::windowClasses().split("\n"); m_transparentBlur = BlurConfig::transparentBlur(); - m_topCornerRadius = BlurConfig::topCornerRadius(); - m_bottomCornerRadius = BlurConfig::bottomCornerRadius(); + m_windowTopCornerRadius = BlurConfig::topCornerRadius(); + m_windowBottomCornerRadius = BlurConfig::bottomCornerRadius(); + m_menuCornerRadius = BlurConfig::menuCornerRadius(); + m_dockCornerRadius = BlurConfig::dockCornerRadius(); + m_roundedCornersAntialiasing = BlurConfig::roundedCornersAntialiasing(); m_roundCornersOfMaximizedWindows = BlurConfig::roundCornersOfMaximizedWindows(); m_blurMenus = BlurConfig::blurMenus(); m_blurDocks = BlurConfig::blurDocks(); @@ -232,13 +263,16 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_fakeBlur = BlurConfig::fakeBlur(); m_fakeBlurImage = BlurConfig::fakeBlurImage(); + // Antialiasing does take up a bit of space, so the corner radius will be reduced by the offset in order to leave some space. + m_cornerRadiusOffset = m_roundedCornersAntialiasing == 0 ? 0 : std::round(m_roundedCornersAntialiasing) + 2; + QImage fakeBlurImage(m_fakeBlurImage); m_hasValidFakeBlurTexture = !fakeBlurImage.isNull(); if (m_hasValidFakeBlurTexture) { m_texturePass.texture = GLTexture::upload(fakeBlurImage); } - updateCornerRegions(); + m_corners.clear(); for (EffectWindow *w : effects->stackingOrder()) { updateBlurRegion(w); @@ -291,7 +325,6 @@ void BlurEffect::updateBlurRegion(EffectWindow *w) if (shouldForceBlur(w)) { content = w->expandedGeometry().toRect().translated(-w->x(), -w->y()); if (m_blurDecorations && w->decoration()) { - const QMargins borders = w->decoration()->borders(); frame = w->frameGeometry().toRect().translated(-w->x(), -w->y()); } } @@ -308,34 +341,115 @@ void BlurEffect::updateBlurRegion(EffectWindow *w) } } -void BlurEffect::updateCornerRegions() +void BlurEffect::generateRoundedCornerMasks(int radius, QRegion &left, QRegion &right, bool top) const +{ + // This method uses OpenGL to draw circles, since the ones drawn by Qt are terrible. + + left = QRegion(); + right = QRegion(); + + if (radius == 0) { + return; + } + + float size = radius * 2; + auto cornersTexture = GLTexture::allocate(GL_RGB8, QSize(size, size)); + auto cornersFramebuffer = std::make_unique(cornersTexture.get()); + + GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); + vbo->reset(); + vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D)); + if (auto result = vbo->map(6)) { + auto map = *result; + + size_t vboIndex = 0; + + const float x0 = 0; + const float y0 = 0; + const float x1 = size; + const float y1 = size; + + const float u0 = x0 / size; + const float v0 = 1.0f - y0 / size; + const float u1 = x1 / size; + const float v1 = 1.0f - y1 / size; + + // first triangle + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x0, y0), + .texcoord = QVector2D(u0, v0), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x1, y1), + .texcoord = QVector2D(u1, v1), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x0, y1), + .texcoord = QVector2D(u0, v1), + }; + + // second triangle + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x0, y0), + .texcoord = QVector2D(u0, v0), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x1, y0), + .texcoord = QVector2D(u1, v0), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x1, y1), + .texcoord = QVector2D(u1, v1), + }; + + vbo->unmap(); + } else { + qCWarning(KWIN_BLUR) << "Failed to map vertex buffer"; + return; + } + + vbo->bindArrays(); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(QRect(0, 0, size, size)); + + ShaderManager::instance()->pushShader(m_roundedCorners.shader.get()); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, false); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, false); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, true); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, true); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, static_cast(0)); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, radius); + m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, static_cast(0)); + m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(size, size)); + m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); + + GLFramebuffer::pushFramebuffer(cornersFramebuffer.get()); + vbo->draw(GL_TRIANGLES, 0, 6); + GLFramebuffer::popFramebuffer(); + ShaderManager::instance()->popShader(); + vbo->unbindArrays(); + + QImage img = cornersTexture->toImage().mirrored().copy(0, 0, radius, radius); + if (!top) { + img.mirror(); + } + + left = QRegion(QBitmap::fromImage(img.createMaskFromColor(QColor(Qt::black).rgb(), Qt::MaskOutColor), Qt::DiffuseAlphaDither)); + right = QRegion(QBitmap::fromImage(img.mirrored(true, false).createMaskFromColor(QColor(Qt::black).rgb(), Qt::MaskOutColor), Qt::DiffuseAlphaDither));; +} + +std::array BlurEffect::roundedCorners(int topCornerRadius, int bottomCornerRadius, qreal scale) { - QRegion square = QRegion(0, 0, m_topCornerRadius, m_topCornerRadius); - QRegion circle = QRegion(0, 0, 2 * m_topCornerRadius, 2 * m_topCornerRadius, QRegion::RegionType::Ellipse); - m_topLeftCorner = QRegion(0, 0, m_topCornerRadius, m_topCornerRadius); - m_topRightCorner = QRegion(0, 0, m_topCornerRadius, m_topCornerRadius); - - m_topLeftCorner &= circle; - m_topLeftCorner ^= square; - circle.translate(-m_topCornerRadius, 0); - m_topRightCorner &= circle; - m_topRightCorner ^= square; - - square = QRegion(0, 0, m_bottomCornerRadius, m_bottomCornerRadius); - circle = QRegion(0, 0, 2 * m_bottomCornerRadius, 2 * m_bottomCornerRadius, QRegion::RegionType::Ellipse); - - m_bottomLeftCorner = QRegion(0, 0, m_bottomCornerRadius, m_bottomCornerRadius); - m_bottomRightCorner = QRegion(0, 0, m_bottomCornerRadius, m_bottomCornerRadius); - circle.translate(0, -m_bottomCornerRadius); - m_bottomLeftCorner &= circle; - m_bottomLeftCorner ^= square; - - circle.translate(0, m_bottomCornerRadius); - circle.translate(-m_bottomCornerRadius, 0); - circle.translate(0, -m_bottomCornerRadius); - - m_bottomRightCorner &= circle; - m_bottomRightCorner ^= square; + const auto key = std::make_tuple(topCornerRadius, bottomCornerRadius, scale); + if (m_corners.contains(key)) { + return m_corners[key]; + } + + std::array corners; + generateRoundedCornerMasks(topCornerRadius, corners[0], corners[1], true); + generateRoundedCornerMasks(bottomCornerRadius, corners[2], corners[3], false); + return m_corners[key] = corners; } void BlurEffect::slotWindowAdded(EffectWindow *w) @@ -458,7 +572,7 @@ QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const return decorationRegion.intersected(w->decoration()->blurRegion()); } -QRegion BlurEffect::blurRegion(EffectWindow *w, bool noRoundedCorners) const +QRegion BlurEffect::blurRegion(EffectWindow *w) const { QRegion region; @@ -481,23 +595,27 @@ QRegion BlurEffect::blurRegion(EffectWindow *w, bool noRoundedCorners) const } } - bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); - if (!w->isDock() && !noRoundedCorners && (!isMaximized || m_roundCornersOfMaximizedWindows)) { - if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - QPoint topRightPosition = QPoint(w->rect().width() - m_topCornerRadius, 0); - region -= m_topLeftCorner; - region -= m_topRightCorner.translated(topRightPosition); - } + return region; +} - if (m_bottomCornerRadius) { - QPoint bottomLeftPosition = QPoint(0, w->rect().height() - m_bottomCornerRadius); - QPoint bottomRightPosition = QPoint(w->rect().width() - m_bottomCornerRadius, w->rect().height() - m_bottomCornerRadius); - region -= m_bottomLeftCorner.translated(bottomLeftPosition); - region -= m_bottomRightCorner.translated(bottomRightPosition); +QRegion BlurEffect::transformedBlurRegion(QRegion blurRegion, const WindowPaintData &data) const +{ + if (data.xScale() != 1 || data.yScale() != 1) { + QPoint pt = blurRegion.boundingRect().topLeft(); + QRegion scaledShape; + for (const QRect &r : blurRegion) { + const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), + pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); + const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1, + std::floor(topLeft.y() + r.height() * data.yScale()) - 1); + scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight); } + blurRegion = scaledShape; + } else if (data.xTranslation() || data.yTranslation()) { + blurRegion.translate(std::round(data.xTranslation()), std::round(data.yTranslation())); } - return region; + return blurRegion; } void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) @@ -519,17 +637,31 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: bool hasFakeBlur = m_fakeBlur && m_hasValidFakeBlurTexture && !blurArea.isEmpty(); if (hasFakeBlur) { data.opaque += blurArea; - } - if (shouldForceBlur(w) && m_paintAsTranslucent) { - if (hasFakeBlur) { - // Remove rounded corners region - data.opaque -= blurRegion(w, true).translated(w->pos().toPoint()) - blurArea; + int topCornerRadius; + int bottomCornerRadius; + if (isMenu(w)) { + topCornerRadius = bottomCornerRadius = m_menuCornerRadius; + } else if (w->isDock()) { + topCornerRadius = bottomCornerRadius = m_dockCornerRadius; } else { - data.setTranslucent(); + topCornerRadius = m_windowTopCornerRadius; + bottomCornerRadius = m_windowBottomCornerRadius; + } + + if (!w->isDock() || (w->isDock() && isDockFloating(w, blurArea))) { + const QRect blurRect = blurArea.boundingRect(); + data.opaque -= QRect(blurRect.x(), blurRect.y(), topCornerRadius, topCornerRadius); + data.opaque -= QRect(blurRect.x() + blurRect.width() - topCornerRadius, blurRect.y(), topCornerRadius, topCornerRadius); + data.opaque -= QRect(blurRect.x(), blurRect.y() + blurRect.height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius); + data.opaque -= QRect(blurRect.x() + blurRect.width() - bottomCornerRadius, blurRect.y() + blurRect.height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius); } } + if (shouldForceBlur(w) && m_paintAsTranslucent) { + data.setTranslucent(); + } + effects->prePaintWindow(w, data, presentTime); if (!hasFakeBlur) { @@ -605,7 +737,7 @@ bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintDa bool BlurEffect::shouldForceBlur(const EffectWindow *w) const { - if ((!m_blurDocks && w->isDock()) || (!m_blurMenus && (w->isMenu() || w->isDropdownMenu() || w->isPopupMenu() || w->isPopupWindow()))) { + if ((!m_blurDocks && w->isDock()) || (!m_blurMenus && isMenu(w))) { return false; } @@ -671,52 +803,99 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi return; } - // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. - QRegion blurShape = blurRegion(w).translated(w->pos().toPoint()); - if (data.xScale() != 1 || data.yScale() != 1) { - QPoint pt = blurShape.boundingRect().topLeft(); - QRegion scaledShape; - for (const QRect &r : blurShape) { - const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), - pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); - const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1, - std::floor(topLeft.y() + r.height() * data.yScale()) - 1); - scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight); - } - blurShape = scaledShape; - } else if (data.xTranslation() || data.yTranslation()) { - blurShape.translate(std::round(data.xTranslation()), std::round(data.yTranslation())); + int topCornerRadius; + int bottomCornerRadius; + if (isMenu(w)) { + topCornerRadius = bottomCornerRadius = m_menuCornerRadius; + } else if (w->isDock()) { + topCornerRadius = bottomCornerRadius = m_dockCornerRadius; + } else { + topCornerRadius = m_windowTopCornerRadius; + bottomCornerRadius = m_windowBottomCornerRadius; } + topCornerRadius = std::max(0, (int)std::round(topCornerRadius * viewport.scale()) - m_cornerRadiusOffset); + bottomCornerRadius = std::max(0, (int)std::round(bottomCornerRadius * viewport.scale()) - m_cornerRadiusOffset); + bool hasRoundedCorners = topCornerRadius || bottomCornerRadius; + + const QRegion rawBlurRegion = blurRegion(w); + const QRegion blurShape = transformedBlurRegion(rawBlurRegion.translated(w->pos().toPoint()), data); const QRect backgroundRect = blurShape.boundingRect(); + + // The blur shape has to be moved to 0,0 before being scaled, otherwise the size may end up being off by 1 pixel. + QRegion scaledBlurShape = scaledRegion(blurShape.translated(-backgroundRect.topLeft()), viewport.scale()); const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); + + bool roundTopLeftCorner = false; + bool roundTopRightCorner = false; + bool roundBottomLeftCorner = false; + bool roundBottomRightCorner = false; + const bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); + if (hasRoundedCorners && ((!w->isFullScreen() && !isMaximized) || m_roundCornersOfMaximizedWindows)) { + if (w->isDock()) { + if (isDockFloating(w, rawBlurRegion)) { + roundTopLeftCorner = roundTopRightCorner = topCornerRadius; + roundBottomLeftCorner = roundBottomRightCorner = bottomCornerRadius; + } + } else { + // Ensure the blur region corners touch the window corners before rounding them. + if (topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { + roundTopLeftCorner = rawBlurRegion.intersects(QRect(0, 0, topCornerRadius, topCornerRadius)); + roundTopRightCorner = rawBlurRegion.intersects(QRect(w->width() - topCornerRadius, 0, topCornerRadius, topCornerRadius)); + } + if (bottomCornerRadius) { + roundBottomLeftCorner = rawBlurRegion.intersects(QRect(0, w->height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius)); + roundBottomRightCorner = rawBlurRegion.intersects(QRect(w->width() - bottomCornerRadius, w->height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius)); + } + hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; + } + + const auto corners = roundedCorners(topCornerRadius, bottomCornerRadius, viewport.scale()); + const QRect blurRect = scaledBlurShape.boundingRect(); + + if (roundTopLeftCorner) { + scaledBlurShape -= corners[0]; + } + if (roundTopRightCorner) { + scaledBlurShape -= corners[1].translated(blurRect.width() - topCornerRadius, 0); + } + + if (roundBottomLeftCorner) { + scaledBlurShape -= corners[2].translated(0, blurRect.height() - bottomCornerRadius); + } + if (roundBottomRightCorner) { + scaledBlurShape -= corners[3].translated(blurRect.width() - bottomCornerRadius, blurRect.height() - bottomCornerRadius); + } + } + const auto opacity = m_transparentBlur ? w->opacity() * data.opacity() : data.opacity(); // Get the effective shape that will be actually blurred. It's possible that all of it will be clipped. - QList effectiveShape; - effectiveShape.reserve(blurShape.rectCount()); + QRegion effectiveShape; if (region != infiniteRegion()) { for (const QRect &clipRect : region) { - const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, viewport.scale())) - .translated(-deviceBackgroundRect.topLeft()); - for (const QRect &shapeRect : blurShape) { - const QRectF deviceShapeRect = snapToPixelGridF(scaledRect(shapeRect.translated(-backgroundRect.topLeft()), viewport.scale())); - if (const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { - effectiveShape.append(intersected); + const QRect deviceClipRect = snapToPixelGrid(scaledRect(clipRect, viewport.scale())) + .translated(-deviceBackgroundRect.topLeft()); + for (const QRect &shapeRect : scaledBlurShape) { + const QRect deviceShapeRect = shapeRect; + if (const QRect intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { + effectiveShape += intersected; } } } } else { - for (const QRect &rect : blurShape) { - effectiveShape.append(snapToPixelGridF(scaledRect(rect.translated(-backgroundRect.topLeft()), viewport.scale()))); + for (const QRect &rect : scaledBlurShape) { + effectiveShape += rect; } } - if (effectiveShape.isEmpty()) { + if (!effectiveShape.rectCount()) { return; } + const bool hasAntialiasedRoundedCorners = hasRoundedCorners && m_roundedCornersAntialiasing > 0; + // Maybe reallocate offscreen render targets. Keep in mind that the first one contains // original background behind the window, it's not blurred. GLenum textureFormat = GL_RGBA8; @@ -748,6 +927,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi } // Fetch the pixels behind the shape that is going to be blurred. + // This framebuffer is left unchanged, so we can use that for rounding corners. const QRegion dirtyRegion = region & backgroundRect; for (const QRect &dirtyRect : dirtyRegion) { renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft())); @@ -759,7 +939,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->reset(); vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D)); - const int vertexCount = effectiveShape.size() * 6; + const int vertexCount = effectiveShape.rectCount() * 6; if (auto result = vbo->map(6 + vertexCount)) { auto map = *result; @@ -857,29 +1037,62 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->bindArrays(); + /* + * The anti-aliasing implementation is actually really bad, but that's the best I can do for now. Suprisingly, + * there are no performance issues. + * + * Anti-aliasing is done by a shader that paints rounded rectangles. It's a modified version of + * https://www.shadertoy.com/view/ldfSDj. + * The shader requires two textures: the blur region before being blurred and after being blurred. + * The first texture can simply be taken from renderInfo.textures[0], as it's left unchanged. + * The second texture is more tricky. We could just blit, but that's slow. A faster solution is to create a virtual + * framebuffer with a texture attached to it and paint the blur in that framebuffer instead of the screen. + * + * Since only a fragment of the window may be painted, the shader allows to toggle rounding for each corner. + */ + + const auto finalBlurTexture = GLTexture::allocate(textureFormat, backgroundRect.size()); + finalBlurTexture->setFilter(GL_LINEAR); + finalBlurTexture->setWrapMode(GL_CLAMP_TO_EDGE); + const auto finalBlurFramebuffer = std::make_unique(finalBlurTexture.get()); + if (m_fakeBlur && m_hasValidFakeBlurTexture) { ShaderManager::instance()->pushShader(m_texturePass.shader.get()); - QMatrix4x4 projectionMatrix = viewport.projectionMatrix(); - projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + QMatrix4x4 projectionMatrix; + if (hasAntialiasedRoundedCorners) { + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); + GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); + } else { + projectionMatrix = viewport.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + } m_texturePass.shader->setUniform(m_texturePass.mvpMatrixLocation, projectionMatrix); m_texturePass.shader->setUniform(m_texturePass.textureSizeLocation, QVector2D(m_texturePass.texture.get()->width(), m_texturePass.texture.get()->height())); - m_texturePass.shader->setUniform(m_texturePass.texStartPosLocation, QVector2D(0, 0)); + m_texturePass.shader->setUniform(m_texturePass.texStartPosLocation, QVector2D(backgroundRect.x(), backgroundRect.y())); + m_texturePass.shader->setUniform(m_texturePass.regionSizeLocation, QVector2D(backgroundRect.width(), backgroundRect.height())); m_texturePass.texture.get()->bind(); - glEnable(GL_BLEND); - float o = 1.0f - (opacity); - o = 1.0f - o * o; - glBlendColor(0, 0, 0, o); - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + if (!hasAntialiasedRoundedCorners) { + glEnable(GL_BLEND); + float o = 1.0f - (opacity); + o = 1.0f - o * o; + glBlendColor(0, 0, 0, o); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + } - vbo->draw(GL_TRIANGLES, 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasAntialiasedRoundedCorners ? 0 : 6, hasAntialiasedRoundedCorners ? 6 : vertexCount); - glDisable(GL_BLEND); + if (!hasAntialiasedRoundedCorners) { + glDisable(GL_BLEND); + } ShaderManager::instance()->popShader(); + if (hasAntialiasedRoundedCorners) { + GLFramebuffer::popFramebuffer(); + } } else { // The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration. { @@ -896,7 +1109,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi const auto &draw = renderInfo.framebuffers[i]; const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), - 0.5 / read->colorAttachment()->height()); + 0.5 / read->colorAttachment()->height()); m_downsamplePass.shader->setUniform(m_downsamplePass.halfpixelLocation, halfpixel); read->colorAttachment()->bind(); @@ -934,8 +1147,14 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi GLFramebuffer::popFramebuffer(); const auto &read = renderInfo.framebuffers[1]; - projectionMatrix = viewport.projectionMatrix(); - projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + if (hasAntialiasedRoundedCorners) { + GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); + projectionMatrix = QMatrix4x4(); + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); + } else { + projectionMatrix = viewport.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + } m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), @@ -945,7 +1164,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi read->colorAttachment()->bind(); // Modulate the blurred texture with the window opacity if the window isn't opaque - if (opacity < 1.0) { + if (!hasAntialiasedRoundedCorners && opacity < 1.0) { glEnable(GL_BLEND); float o = 1.0f - (opacity); o = 1.0f - o * o; @@ -953,9 +1172,9 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - vbo->draw(GL_TRIANGLES, 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasAntialiasedRoundedCorners ? 0 : 6, hasAntialiasedRoundedCorners ? 6 : vertexCount); - if (opacity < 1.0) { + if (!hasAntialiasedRoundedCorners && opacity < 1.0) { glDisable(GL_BLEND); } @@ -975,8 +1194,13 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi if (GLTexture *noiseTexture = ensureNoiseTexture()) { ShaderManager::instance()->pushShader(m_noisePass.shader.get()); - QMatrix4x4 projectionMatrix = viewport.projectionMatrix(); - projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + QMatrix4x4 projectionMatrix; + if (hasAntialiasedRoundedCorners) { + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); + } else { + projectionMatrix = viewport.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + } m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix); m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); @@ -984,13 +1208,60 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi noiseTexture->bind(); - vbo->draw(GL_TRIANGLES, 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasAntialiasedRoundedCorners ? 0 : 6, hasAntialiasedRoundedCorners ? 6 : vertexCount); ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } + + if (hasAntialiasedRoundedCorners) { + GLFramebuffer::popFramebuffer(); + } + } + + if (hasAntialiasedRoundedCorners) { + QMatrix4x4 projectionMatrix = viewport.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + + // The Y axis is flipped in OpenGL. + // TODO Rename the uniforms + ShaderManager::instance()->pushShader(m_roundedCorners.shader.get()); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, roundBottomLeftCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundBottomRightCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, roundTopLeftCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, roundTopRightCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, bottomCornerRadius + m_cornerRadiusOffset); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, topCornerRadius + m_cornerRadiusOffset); + m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); + m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.width(), deviceBackgroundRect.height())); + m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); + + glUniform1i(m_roundedCorners.beforeBlurTextureLocation, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, renderInfo.textures[0]->texture()); + + glUniform1i(m_roundedCorners.afterBlurTextureLocation, 1); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, finalBlurTexture->texture()); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (opacity < 1.0f) { + float o = 1.0f - (opacity); + o = 1.0f - o * o; + glBlendColor(0, 0, 0, o); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + } + + vbo->draw(GL_TRIANGLES, 6, vertexCount); + + glDisable(GL_BLEND); + glActiveTexture(GL_TEXTURE0); + renderInfo.textures[0]->unbind(); + finalBlurTexture->unbind(); + ShaderManager::instance()->popShader(); } vbo->unbindArrays(); @@ -1006,11 +1277,6 @@ bool BlurEffect::blocksDirectScanout() const return false; } -bool BlurEffect::hasFakeBlur(EffectWindow *w) const -{ - return m_fakeBlur && m_hasValidFakeBlurTexture && !blurRegion(w).isEmpty(); -} - } // namespace KWin #include "moc_blur.cpp" diff --git a/src/blur.h b/src/blur.h index fa135646c..fde304191 100644 --- a/src/blur.h +++ b/src/blur.h @@ -77,16 +77,27 @@ public Q_SLOTS: private: void initBlurStrengthValues(); - QRegion blurRegion(EffectWindow *w, bool noRoundedCorners = false) const; + QRegion blurRegion(EffectWindow *w) const; QRegion decorationBlurRegion(const EffectWindow *w) const; + QRegion transformedBlurRegion(QRegion blurRegion, const WindowPaintData &data) const; bool decorationSupportsBlurBehind(const EffectWindow *w) const; bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data); bool shouldForceBlur(const EffectWindow *w) const; void updateBlurRegion(EffectWindow *w); - void updateCornerRegions(); void blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data); GLTexture *ensureNoiseTexture(); - bool hasFakeBlur(EffectWindow *w) const; + + /* + * @returns An array containing rounded corner masks for the given screen scale and radii. If no masks exist, they + * will be generated. + */ + std::array roundedCorners(int topCornerRadius, int bottomCornerRadius, qreal scale); + + /* + * Generates rounded corner masks for the left and right corner of the given radius. + * @param top Whether the corners belong to the top part of the window. + */ + void generateRoundedCornerMasks(int radius, QRegion &left, QRegion &right, bool top) const; private: struct @@ -123,10 +134,33 @@ public Q_SLOTS: int mvpMatrixLocation; int textureSizeLocation; int texStartPosLocation; + int regionSizeLocation; std::unique_ptr texture; } m_texturePass; + struct + { + std::unique_ptr shader; + + int roundTopLeftCornerLocation; + int roundTopRightCornerLocation; + int roundBottomLeftCornerLocation; + int roundBottomRightCornerLocation; + + int topCornerRadiusLocation; + int bottomCornerRadiusLocation; + + int antialiasingLocation; + + int regionSizeLocation; + + int beforeBlurTextureLocation; + int afterBlurTextureLocation; + + int mvpMatrixLocation; + } m_roundedCorners; + bool m_valid = false; long net_wm_blur_region = 0; QRegion m_paintedArea; // keeps track of all painted areas (from bottom to top) @@ -142,9 +176,6 @@ public Q_SLOTS: bool m_blurNonMatching; bool m_blurDecorations; bool m_transparentBlur; - int m_topCornerRadius; - int m_bottomCornerRadius; - bool m_roundCornersOfMaximizedWindows; bool m_blurMenus; bool m_blurDocks; bool m_paintAsTranslucent; @@ -153,11 +184,17 @@ public Q_SLOTS: bool m_hasValidFakeBlurTexture; - // Regions to subtract from the blurred region - QRegion m_topLeftCorner; - QRegion m_topRightCorner; - QRegion m_bottomLeftCorner; - QRegion m_bottomRightCorner; + int m_windowTopCornerRadius; + int m_windowBottomCornerRadius; + int m_menuCornerRadius; + int m_dockCornerRadius; + float m_roundedCornersAntialiasing; + bool m_roundCornersOfMaximizedWindows; + int m_cornerRadiusOffset; + + // Corner masks where the key is the screen scale and the value is an array of the masks + // (top left, top right, bottom left, bottom right). Used for rounding the blur region. + std::map, std::array> m_corners; struct OffsetStruct { diff --git a/src/blur.kcfg b/src/blur.kcfg index 04544a0af..734cf1af6 100644 --- a/src/blur.kcfg +++ b/src/blur.kcfg @@ -34,6 +34,15 @@ class3 0 + + 0 + + + 0 + + + 1.0 + false diff --git a/src/blur.qrc b/src/blur.qrc index 8039e2b48..d7c8b16b0 100644 --- a/src/blur.qrc +++ b/src/blur.qrc @@ -4,6 +4,8 @@ shaders/downsample_core.frag shaders/noise.frag shaders/noise_core.frag + shaders/roundedcorners.frag + shaders/roundedcorners_core.frag shaders/texture.frag shaders/texture_core.frag shaders/upsample.frag diff --git a/src/kcm/blur_config.ui b/src/kcm/blur_config.ui index b89605845..4d542f015 100644 --- a/src/kcm/blur_config.ui +++ b/src/kcm/blur_config.ui @@ -321,7 +321,7 @@ - Top corner radius + Window top corner radius @@ -339,7 +339,7 @@ - Bottom corner radius + Window bottom corner radius @@ -352,6 +352,60 @@ + + + + + + Menu corner radius + + + + + + + 0 + + + + + + + + + + + Dock corner radius + + + + + + + 0 + + + + + + + + + + + Anti-aliasing + + + + + + + 0 + + + + + diff --git a/src/shaders/roundedcorners.frag b/src/shaders/roundedcorners.frag new file mode 100644 index 000000000..58cfd2653 --- /dev/null +++ b/src/shaders/roundedcorners.frag @@ -0,0 +1,53 @@ +// Modified version of https://www.shadertoy.com/view/ldfSDj + +uniform bool roundTopLeftCorner; +uniform bool roundTopRightCorner; +uniform bool roundBottomLeftCorner; +uniform bool roundBottomRightCorner; + +uniform int topCornerRadius; +uniform int bottomCornerRadius; + +uniform float antialiasing; + +uniform vec2 regionSize; + +uniform sampler2D beforeBlurTexture; +uniform sampler2D afterBlurTexture; + +varying vec2 uv; + +float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) +{ + float radius = 0.0; + if ((fragCoord.y <= topCornerRadius) + && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) + || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { + radius = topCornerRadius; + p.y -= radius; + } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) + && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) + || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { + radius = bottomCornerRadius; + p.y += radius; + } + + return length(max(abs(p) - (b + vec2(0.0, radius)) + radius, 0.0)) - radius; +} + +void main(void) +{ + vec2 halfRegionSize = regionSize * 0.5; + vec2 fragCoord = uv * regionSize; + float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); + + // If antialiasing is 0, the shader will be used to generate corner masks. + vec3 foreground = vec3(1.0, 1.0, 1.0); + vec3 background = vec3(0.0, 0.0, 0.0); + if (antialiasing > 0.0) { + foreground = texture2D(afterBlurTexture, uv).rgb; + background = texture2D(beforeBlurTexture, uv).rgb; + } + + gl_FragColor = vec4(mix(foreground, background, smoothstep(0.0, antialiasing, box)), 1.0); +} \ No newline at end of file diff --git a/src/shaders/roundedcorners_core.frag b/src/shaders/roundedcorners_core.frag new file mode 100644 index 000000000..e0233d703 --- /dev/null +++ b/src/shaders/roundedcorners_core.frag @@ -0,0 +1,57 @@ +#version 140 + +// Modified version of https://www.shadertoy.com/view/ldfSDj + +uniform bool roundTopLeftCorner; +uniform bool roundTopRightCorner; +uniform bool roundBottomLeftCorner; +uniform bool roundBottomRightCorner; + +uniform int topCornerRadius; +uniform int bottomCornerRadius; + +uniform float antialiasing; + +uniform vec2 regionSize; + +uniform sampler2D beforeBlurTexture; +uniform sampler2D afterBlurTexture; + +in vec2 uv; + +out vec4 fragColor; + +float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) +{ + float radius = 0.0; + if ((fragCoord.y <= topCornerRadius) + && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) + || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { + radius = topCornerRadius; + p.y -= radius; + } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) + && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) + || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { + radius = bottomCornerRadius; + p.y += radius; + } + + return length(max(abs(p) - (b + vec2(0.0, radius)) + radius, 0.0)) - radius; +} + +void main(void) +{ + vec2 halfRegionSize = regionSize * 0.5; + vec2 fragCoord = uv * regionSize; + float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); + + // If antialiasing is 0, the shader will be used to generate corner masks. + vec3 foreground = vec3(1.0, 1.0, 1.0); + vec3 background = vec3(0.0, 0.0, 0.0); + if (antialiasing > 0.0) { + foreground = texture(afterBlurTexture, uv).rgb; + background = texture(beforeBlurTexture, uv).rgb; + } + + fragColor = vec4(mix(foreground, background, smoothstep(0.0, antialiasing, box)), 1.0); +} diff --git a/src/shaders/texture.frag b/src/shaders/texture.frag index 61b0dd133..2d0348e78 100644 --- a/src/shaders/texture.frag +++ b/src/shaders/texture.frag @@ -1,12 +1,12 @@ uniform sampler2D texUnit; uniform vec2 textureSize; uniform vec2 texStartPos; +uniform vec2 regionSize; varying vec2 uv; void main(void) { - vec2 tex = vec2((texStartPos.xy + gl_FragCoord.xy) / textureSize); - + vec2 tex = (texStartPos.xy + vec2(uv.x, 1.0 - uv.y) * regionSize) / textureSize; gl_FragColor = vec4(texture2D(texUnit, tex).rgb, 0); } diff --git a/src/shaders/texture_core.frag b/src/shaders/texture_core.frag index 3e3583361..afc99a28d 100644 --- a/src/shaders/texture_core.frag +++ b/src/shaders/texture_core.frag @@ -3,6 +3,7 @@ uniform sampler2D texUnit; uniform vec2 textureSize; uniform vec2 texStartPos; +uniform vec2 regionSize; in vec2 uv; @@ -10,7 +11,6 @@ out vec4 fragColor; void main(void) { - vec2 tex = vec2((texStartPos.xy + gl_FragCoord.xy) / textureSize); - + vec2 tex = (texStartPos.xy + vec2(uv.x, 1.0 - uv.y) * regionSize) / textureSize; fragColor = vec4(texture(texUnit, tex).rgb, 0); } diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 000000000..218793153 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "core/pixelgrid.h" + +namespace KWin +{ + +inline QRegion scaledRegion(const QRegion ®ion, qreal scale) +{ + QRegion scaledRegion; + for (const QRect &rect : region) { + scaledRegion += snapToPixelGridF(scaledRect(QRectF(rect), scale)).toRect(); + } + + return scaledRegion; +} + +inline bool isMenu(const EffectWindow *w) +{ + return w->isMenu() || w->isDropdownMenu() || w->isPopupMenu() || w->isPopupWindow(); +} + +inline bool isDockFloating(const EffectWindow *dock, const QRegion blurRegion) +{ + // If the pixel at (0, height / 2) for horizontal panels and (width / 2, 0) for vertical panels doesn't belong to + // the blur region, the dock is most likely floating. The (0,0) pixel may be outside the blur region if the dock + // can float but isn't at the moment. + return !blurRegion.intersects(QRect(0, dock->height() / 2, 1, 1)) && !blurRegion.intersects(QRect(dock->width() / 2, 0, 1, 1)); +} + +} \ No newline at end of file