File indexing completed on 2024-05-19 16:34:46

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