File indexing completed on 2024-05-19 05:32:20

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "scene/shadowitem.h"
0008 #include "compositor.h"
0009 #include "scene/workspacescene.h"
0010 #include "shadow.h"
0011 #include "window.h"
0012 
0013 namespace KWin
0014 {
0015 
0016 ShadowTextureProvider::ShadowTextureProvider(Shadow *shadow)
0017     : m_shadow(shadow)
0018 {
0019 }
0020 
0021 ShadowTextureProvider::~ShadowTextureProvider()
0022 {
0023 }
0024 
0025 ShadowItem::ShadowItem(Shadow *shadow, Window *window, Scene *scene, Item *parent)
0026     : Item(scene, parent)
0027     , m_window(window)
0028     , m_shadow(shadow)
0029     , m_textureProvider(Compositor::self()->scene()->createShadowTextureProvider(shadow))
0030 {
0031     connect(shadow, &Shadow::offsetChanged, this, &ShadowItem::updateGeometry);
0032     connect(shadow, &Shadow::rectChanged, this, &ShadowItem::updateGeometry);
0033     connect(shadow, &Shadow::textureChanged, this, &ShadowItem::handleTextureChanged);
0034 
0035     updateGeometry();
0036     handleTextureChanged();
0037 }
0038 
0039 ShadowItem::~ShadowItem()
0040 {
0041 }
0042 
0043 Shadow *ShadowItem::shadow() const
0044 {
0045     return m_shadow;
0046 }
0047 
0048 ShadowTextureProvider *ShadowItem::textureProvider() const
0049 {
0050     return m_textureProvider.get();
0051 }
0052 
0053 void ShadowItem::updateGeometry()
0054 {
0055     const QRectF rect = m_shadow->rect() + m_shadow->offset();
0056 
0057     setPosition(rect.topLeft());
0058     setSize(rect.size());
0059     discardQuads();
0060 }
0061 
0062 void ShadowItem::handleTextureChanged()
0063 {
0064     scheduleRepaint(rect());
0065     discardQuads();
0066     m_textureDirty = true;
0067 }
0068 
0069 static inline void distributeHorizontally(QRectF &leftRect, QRectF &rightRect)
0070 {
0071     if (leftRect.right() > rightRect.left()) {
0072         const qreal boundedRight = std::min(leftRect.right(), rightRect.right());
0073         const qreal boundedLeft = std::max(leftRect.left(), rightRect.left());
0074         const qreal halfOverlap = (boundedRight - boundedLeft) / 2.0;
0075         leftRect.setRight(boundedRight - halfOverlap);
0076         rightRect.setLeft(boundedLeft + halfOverlap);
0077     }
0078 }
0079 
0080 static inline void distributeVertically(QRectF &topRect, QRectF &bottomRect)
0081 {
0082     if (topRect.bottom() > bottomRect.top()) {
0083         const qreal boundedBottom = std::min(topRect.bottom(), bottomRect.bottom());
0084         const qreal boundedTop = std::max(topRect.top(), bottomRect.top());
0085         const qreal halfOverlap = (boundedBottom - boundedTop) / 2.0;
0086         topRect.setBottom(boundedBottom - halfOverlap);
0087         bottomRect.setTop(boundedTop + halfOverlap);
0088     }
0089 }
0090 
0091 WindowQuadList ShadowItem::buildQuads() const
0092 {
0093     // Do not draw shadows if window width or window height is less than 5 px. 5 is an arbitrary choice.
0094     if (!m_window->wantsShadowToBeRendered() || m_window->width() < 5 || m_window->height() < 5) {
0095         return WindowQuadList();
0096     }
0097 
0098     const QSizeF top(m_shadow->elementSize(Shadow::ShadowElementTop));
0099     const QSizeF topRight(m_shadow->elementSize(Shadow::ShadowElementTopRight));
0100     const QSizeF right(m_shadow->elementSize(Shadow::ShadowElementRight));
0101     const QSizeF bottomRight(m_shadow->elementSize(Shadow::ShadowElementBottomRight));
0102     const QSizeF bottom(m_shadow->elementSize(Shadow::ShadowElementBottom));
0103     const QSizeF bottomLeft(m_shadow->elementSize(Shadow::ShadowElementBottomLeft));
0104     const QSizeF left(m_shadow->elementSize(Shadow::ShadowElementLeft));
0105     const QSizeF topLeft(m_shadow->elementSize(Shadow::ShadowElementTopLeft));
0106 
0107     const QMarginsF shadowMargins(
0108         std::max({topLeft.width(), left.width(), bottomLeft.width()}),
0109         std::max({topLeft.height(), top.height(), topRight.height()}),
0110         std::max({topRight.width(), right.width(), bottomRight.width()}),
0111         std::max({bottomRight.height(), bottom.height(), bottomLeft.height()}));
0112 
0113     const QRectF outerRect = rect();
0114 
0115     const int width = shadowMargins.left() + std::max(top.width(), bottom.width()) + shadowMargins.right();
0116     const int height = shadowMargins.top() + std::max(left.height(), right.height()) + shadowMargins.bottom();
0117 
0118     QRectF topLeftRect;
0119     if (!topLeft.isEmpty()) {
0120         topLeftRect = QRectF(outerRect.topLeft(), topLeft);
0121     } else {
0122         topLeftRect = QRectF(outerRect.left() + shadowMargins.left(),
0123                              outerRect.top() + shadowMargins.top(), 0, 0);
0124     }
0125 
0126     QRectF topRightRect;
0127     if (!topRight.isEmpty()) {
0128         topRightRect = QRectF(outerRect.right() - topRight.width(), outerRect.top(),
0129                               topRight.width(), topRight.height());
0130     } else {
0131         topRightRect = QRectF(outerRect.right() - shadowMargins.right(),
0132                               outerRect.top() + shadowMargins.top(), 0, 0);
0133     }
0134 
0135     QRectF bottomRightRect;
0136     if (!bottomRight.isEmpty()) {
0137         bottomRightRect = QRectF(outerRect.right() - bottomRight.width(),
0138                                  outerRect.bottom() - bottomRight.height(),
0139                                  bottomRight.width(), bottomRight.height());
0140     } else {
0141         bottomRightRect = QRectF(outerRect.right() - shadowMargins.right(),
0142                                  outerRect.bottom() - shadowMargins.bottom(), 0, 0);
0143     }
0144 
0145     QRectF bottomLeftRect;
0146     if (!bottomLeft.isEmpty()) {
0147         bottomLeftRect = QRectF(outerRect.left(), outerRect.bottom() - bottomLeft.height(),
0148                                 bottomLeft.width(), bottomLeft.height());
0149     } else {
0150         bottomLeftRect = QRectF(outerRect.left() + shadowMargins.left(),
0151                                 outerRect.bottom() - shadowMargins.bottom(), 0, 0);
0152     }
0153 
0154     // Re-distribute the corner tiles so no one of them is overlapping with others.
0155     // By doing this, we assume that shadow's corner tiles are symmetric
0156     // and it is OK to not draw top/right/bottom/left tile between corners.
0157     // For example, let's say top-left and top-right tiles are overlapping.
0158     // In that case, the right side of the top-left tile will be shifted to left,
0159     // the left side of the top-right tile will shifted to right, and the top
0160     // tile won't be rendered.
0161     distributeHorizontally(topLeftRect, topRightRect);
0162     distributeHorizontally(bottomLeftRect, bottomRightRect);
0163     distributeVertically(topLeftRect, bottomLeftRect);
0164     distributeVertically(topRightRect, bottomRightRect);
0165 
0166     qreal tx1 = 0.0,
0167           tx2 = 0.0,
0168           ty1 = 0.0,
0169           ty2 = 0.0;
0170 
0171     WindowQuadList quads;
0172     quads.reserve(8);
0173 
0174     if (topLeftRect.isValid()) {
0175         tx1 = 0.0;
0176         ty1 = 0.0;
0177         tx2 = topLeftRect.width();
0178         ty2 = topLeftRect.height();
0179         WindowQuad topLeftQuad;
0180         topLeftQuad[0] = WindowVertex(topLeftRect.left(), topLeftRect.top(), tx1, ty1);
0181         topLeftQuad[1] = WindowVertex(topLeftRect.right(), topLeftRect.top(), tx2, ty1);
0182         topLeftQuad[2] = WindowVertex(topLeftRect.right(), topLeftRect.bottom(), tx2, ty2);
0183         topLeftQuad[3] = WindowVertex(topLeftRect.left(), topLeftRect.bottom(), tx1, ty2);
0184         quads.append(topLeftQuad);
0185     }
0186 
0187     if (topRightRect.isValid()) {
0188         tx1 = width - topRightRect.width();
0189         ty1 = 0.0;
0190         tx2 = width;
0191         ty2 = topRightRect.height();
0192         WindowQuad topRightQuad;
0193         topRightQuad[0] = WindowVertex(topRightRect.left(), topRightRect.top(), tx1, ty1);
0194         topRightQuad[1] = WindowVertex(topRightRect.right(), topRightRect.top(), tx2, ty1);
0195         topRightQuad[2] = WindowVertex(topRightRect.right(), topRightRect.bottom(), tx2, ty2);
0196         topRightQuad[3] = WindowVertex(topRightRect.left(), topRightRect.bottom(), tx1, ty2);
0197         quads.append(topRightQuad);
0198     }
0199 
0200     if (bottomRightRect.isValid()) {
0201         tx1 = width - bottomRightRect.width();
0202         tx2 = width;
0203         ty1 = height - bottomRightRect.height();
0204         ty2 = height;
0205         WindowQuad bottomRightQuad;
0206         bottomRightQuad[0] = WindowVertex(bottomRightRect.left(), bottomRightRect.top(), tx1, ty1);
0207         bottomRightQuad[1] = WindowVertex(bottomRightRect.right(), bottomRightRect.top(), tx2, ty1);
0208         bottomRightQuad[2] = WindowVertex(bottomRightRect.right(), bottomRightRect.bottom(), tx2, ty2);
0209         bottomRightQuad[3] = WindowVertex(bottomRightRect.left(), bottomRightRect.bottom(), tx1, ty2);
0210         quads.append(bottomRightQuad);
0211     }
0212 
0213     if (bottomLeftRect.isValid()) {
0214         tx1 = 0.0;
0215         tx2 = bottomLeftRect.width();
0216         ty1 = height - bottomLeftRect.height();
0217         ty2 = height;
0218         WindowQuad bottomLeftQuad;
0219         bottomLeftQuad[0] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.top(), tx1, ty1);
0220         bottomLeftQuad[1] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.top(), tx2, ty1);
0221         bottomLeftQuad[2] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.bottom(), tx2, ty2);
0222         bottomLeftQuad[3] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.bottom(), tx1, ty2);
0223         quads.append(bottomLeftQuad);
0224     }
0225 
0226     QRectF topRect(QPointF(topLeftRect.right(), outerRect.top()),
0227                    QPointF(topRightRect.left(), outerRect.top() + top.height()));
0228 
0229     QRectF rightRect(QPointF(outerRect.right() - right.width(), topRightRect.bottom()),
0230                      QPointF(outerRect.right(), bottomRightRect.top()));
0231 
0232     QRectF bottomRect(QPointF(bottomLeftRect.right(), outerRect.bottom() - bottom.height()),
0233                       QPointF(bottomRightRect.left(), outerRect.bottom()));
0234 
0235     QRectF leftRect(QPointF(outerRect.left(), topLeftRect.bottom()),
0236                     QPointF(outerRect.left() + left.width(), bottomLeftRect.top()));
0237 
0238     // Re-distribute left/right and top/bottom shadow tiles so they don't
0239     // overlap when the window is too small. Please notice that we don't
0240     // fix overlaps between left/top(left/bottom, right/top, and so on)
0241     // corner tiles because corresponding counter parts won't be valid when
0242     // the window is too small, which means they won't be rendered.
0243     distributeHorizontally(leftRect, rightRect);
0244     distributeVertically(topRect, bottomRect);
0245 
0246     if (topRect.isValid()) {
0247         tx1 = shadowMargins.left();
0248         ty1 = 0.0;
0249         tx2 = tx1 + top.width();
0250         ty2 = topRect.height();
0251         WindowQuad topQuad;
0252         topQuad[0] = WindowVertex(topRect.left(), topRect.top(), tx1, ty1);
0253         topQuad[1] = WindowVertex(topRect.right(), topRect.top(), tx2, ty1);
0254         topQuad[2] = WindowVertex(topRect.right(), topRect.bottom(), tx2, ty2);
0255         topQuad[3] = WindowVertex(topRect.left(), topRect.bottom(), tx1, ty2);
0256         quads.append(topQuad);
0257     }
0258 
0259     if (rightRect.isValid()) {
0260         tx1 = width - rightRect.width();
0261         ty1 = shadowMargins.top();
0262         tx2 = width;
0263         ty2 = ty1 + right.height();
0264         WindowQuad rightQuad;
0265         rightQuad[0] = WindowVertex(rightRect.left(), rightRect.top(), tx1, ty1);
0266         rightQuad[1] = WindowVertex(rightRect.right(), rightRect.top(), tx2, ty1);
0267         rightQuad[2] = WindowVertex(rightRect.right(), rightRect.bottom(), tx2, ty2);
0268         rightQuad[3] = WindowVertex(rightRect.left(), rightRect.bottom(), tx1, ty2);
0269         quads.append(rightQuad);
0270     }
0271 
0272     if (bottomRect.isValid()) {
0273         tx1 = shadowMargins.left();
0274         ty1 = height - bottomRect.height();
0275         tx2 = tx1 + bottom.width();
0276         ty2 = height;
0277         WindowQuad bottomQuad;
0278         bottomQuad[0] = WindowVertex(bottomRect.left(), bottomRect.top(), tx1, ty1);
0279         bottomQuad[1] = WindowVertex(bottomRect.right(), bottomRect.top(), tx2, ty1);
0280         bottomQuad[2] = WindowVertex(bottomRect.right(), bottomRect.bottom(), tx2, ty2);
0281         bottomQuad[3] = WindowVertex(bottomRect.left(), bottomRect.bottom(), tx1, ty2);
0282         quads.append(bottomQuad);
0283     }
0284 
0285     if (leftRect.isValid()) {
0286         tx1 = 0.0;
0287         ty1 = shadowMargins.top();
0288         tx2 = leftRect.width();
0289         ty2 = ty1 + left.height();
0290         WindowQuad leftQuad;
0291         leftQuad[0] = WindowVertex(leftRect.left(), leftRect.top(), tx1, ty1);
0292         leftQuad[1] = WindowVertex(leftRect.right(), leftRect.top(), tx2, ty1);
0293         leftQuad[2] = WindowVertex(leftRect.right(), leftRect.bottom(), tx2, ty2);
0294         leftQuad[3] = WindowVertex(leftRect.left(), leftRect.bottom(), tx1, ty2);
0295         quads.append(leftQuad);
0296     }
0297 
0298     return quads;
0299 }
0300 
0301 void ShadowItem::preprocess()
0302 {
0303     if (m_textureDirty) {
0304         m_textureDirty = false;
0305         m_textureProvider->update();
0306     }
0307 }
0308 
0309 } // namespace KWin
0310 
0311 #include "moc_shadowitem.cpp"