File indexing completed on 2024-12-22 04:31:06

0001 /*
0002  * Copyright (C) 2021 CutefishOS Team.
0003  *
0004  * Author:     revenmartin <revenmartin@gmail.com>
0005  *
0006  * This program is free software: you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation, either version 3 of the License, or
0009  * any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include "windowshadow.h"
0021 #include "boxshadowrenderer.h"
0022 #include <QDebug>
0023 
0024 enum {
0025     ShadowNone,
0026     ShadowSmall,
0027     ShadowMedium,
0028     ShadowLarge,
0029     ShadowVeryLarge
0030 };
0031 
0032 const CompositeShadowParams s_shadowParams[] = {
0033     // None
0034     CompositeShadowParams(),
0035     // Small
0036     CompositeShadowParams(
0037         QPoint(0, 3),
0038         ShadowParams(QPoint(0, 0), 16, 0.26),
0039         ShadowParams(QPoint(0, -2), 8, 0.16)),
0040     // Medium
0041     CompositeShadowParams(
0042         QPoint(0, 4),
0043         ShadowParams(QPoint(0, 0), 20, 0.24),
0044         ShadowParams(QPoint(0, -2), 10, 0.14)),
0045     // Large
0046     CompositeShadowParams(
0047         QPoint(0, 5),
0048         ShadowParams(QPoint(0, 0), 24, 0.22),
0049         ShadowParams(QPoint(0, -3), 12, 0.12)),
0050     // Very Large
0051     CompositeShadowParams(
0052         QPoint(0, 6),
0053         ShadowParams(QPoint(0, 0), 32, 0.1),
0054         ShadowParams(QPoint(0, -3), 16, 0.05))
0055 };
0056 
0057 WindowShadow::WindowShadow(QObject *parent) noexcept
0058     : QObject(parent)
0059     , m_view(nullptr)
0060 {
0061 
0062 }
0063 
0064 WindowShadow::~WindowShadow()
0065 {
0066 }
0067 
0068 CompositeShadowParams WindowShadow::lookupShadowParams(int shadowSizeEnum)
0069 {
0070     switch (shadowSizeEnum) {
0071     case ShadowNone:
0072         return s_shadowParams[0];
0073     case ShadowSmall:
0074         return s_shadowParams[1];
0075     case ShadowMedium:
0076         return s_shadowParams[2];
0077     case ShadowLarge:
0078         return s_shadowParams[3];
0079     case ShadowVeryLarge:
0080         return s_shadowParams[4];
0081     default:
0082         // Fallback to the Large size.
0083         return s_shadowParams[3];
0084     }
0085 }
0086 
0087 void WindowShadow::classBegin()
0088 {
0089     m_shadowTiles = this->shadowTiles();
0090 }
0091 
0092 void WindowShadow::componentComplete()
0093 {
0094     configureTiles();
0095 }
0096 
0097 void WindowShadow::setView(QWindow *view)
0098 {
0099     if (view != m_view) {
0100         m_view = view;
0101         emit viewChanged();
0102         configureTiles();
0103 
0104         connect(m_view, &QWindow::visibleChanged, this, &WindowShadow::onViewVisibleChanged);
0105     }
0106 }
0107 
0108 QWindow *WindowShadow::view() const 
0109 {
0110     return m_view;
0111 }
0112 
0113 void WindowShadow::setGeometry(const QRect &rect)
0114 {
0115     if (rect != m_rect) {
0116         m_rect = rect;
0117         emit geometryChanged();
0118         configureTiles();
0119     }
0120 }
0121 
0122 QRect WindowShadow::geometry() const
0123 {
0124     return m_rect;
0125 }
0126 
0127 void WindowShadow::setRadius(qreal value)
0128 {
0129     if (m_radius != value) {
0130         m_radius = value;
0131         emit radiusChanged();
0132 
0133         this->classBegin();
0134 
0135         configureTiles();
0136     }
0137 }
0138 
0139 qreal WindowShadow::strength() const
0140 {
0141     return m_strength;
0142 }
0143 
0144 void WindowShadow::setStrength(qreal strength)
0145 {
0146     if (m_strength != strength) {
0147         m_strength = strength;
0148 
0149         this->classBegin();
0150         configureTiles();
0151 
0152         emit strengthChanged();
0153     }
0154 }
0155 
0156 void WindowShadow::onViewVisibleChanged(bool visible)
0157 {
0158     if (visible && m_view) {
0159         configureTiles();
0160     }
0161 }
0162 
0163 void WindowShadow::configureTiles()
0164 {
0165 }
0166 
0167 TileSet WindowShadow::shadowTiles()
0168 {
0169     const qreal frameRadius = m_radius;
0170     const CompositeShadowParams params = lookupShadowParams(ShadowVeryLarge);
0171 
0172     if (params.isNone())
0173         return TileSet();
0174 
0175     auto withOpacity = [](const QColor &color, qreal opacity) -> QColor {
0176         QColor c(color);
0177         c.setAlphaF(opacity);
0178         return c;
0179     };
0180 
0181     const QColor color = Qt::black;
0182     const qreal strength = m_strength;
0183 
0184     const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius)
0185         .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));
0186 
0187     const qreal dpr = qApp->devicePixelRatio();
0188 
0189     BoxShadowRenderer shadowRenderer;
0190     shadowRenderer.setBorderRadius(frameRadius);
0191     shadowRenderer.setBoxSize(boxSize);
0192     shadowRenderer.setDevicePixelRatio(dpr);
0193 
0194     shadowRenderer.addShadow(params.shadow1.offset, params.shadow1.radius,
0195         withOpacity(color, params.shadow1.opacity * strength));
0196     shadowRenderer.addShadow(params.shadow2.offset, params.shadow2.radius,
0197         withOpacity(color, params.shadow2.opacity * strength));
0198 
0199     QImage shadowTexture = shadowRenderer.render();
0200 
0201     const QRect outerRect(QPoint(0, 0), shadowTexture.size() / dpr);
0202 
0203     QRect boxRect(QPoint(0, 0), boxSize);
0204     boxRect.moveCenter(outerRect.center());
0205 
0206     // Mask out inner rect.
0207     QPainter painter(&shadowTexture);
0208     painter.setRenderHint(QPainter::Antialiasing);
0209 
0210     int Shadow_Overlap = 3;
0211     const QMargins margins = QMargins(
0212         boxRect.left() - outerRect.left() - Shadow_Overlap - params.offset.x(),
0213         boxRect.top() - outerRect.top() - Shadow_Overlap - params.offset.y(),
0214         outerRect.right() - boxRect.right() - Shadow_Overlap + params.offset.x(),
0215         outerRect.bottom() - boxRect.bottom() - Shadow_Overlap + params.offset.y());
0216 
0217     painter.setPen(Qt::NoPen);
0218     painter.setBrush(Qt::black);
0219     painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0220     painter.drawRoundedRect(
0221         outerRect - margins,
0222         frameRadius,
0223         frameRadius);
0224 
0225     // We're done.
0226     painter.end();
0227 
0228     const QPoint innerRectTopLeft = outerRect.center();
0229     TileSet tiles = TileSet(
0230         QPixmap::fromImage(shadowTexture),
0231         innerRectTopLeft.x(),
0232         innerRectTopLeft.y(),
0233         1, 1);
0234 
0235     return tiles;
0236 }
0237 
0238 QMargins WindowShadow::shadowMargins(TileSet shadowTiles) const
0239 {
0240     const CompositeShadowParams params = lookupShadowParams(ShadowVeryLarge);
0241     if (params.isNone())
0242         return QMargins();
0243 
0244     const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius)
0245         .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));
0246 
0247     const QSize shadowSize = BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow1.radius, params.shadow1.offset)
0248         .expandedTo(BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow2.radius, params.shadow2.offset));
0249 
0250     const QRect shadowRect(QPoint(0, 0), shadowSize);
0251 
0252     QRect boxRect(QPoint(0, 0), boxSize);
0253     boxRect.moveCenter(shadowRect.center());
0254 
0255     int Shadow_Overlap = 4;
0256     QMargins margins(
0257         boxRect.left() - shadowRect.left() - Shadow_Overlap - params.offset.x(),
0258         boxRect.top() - shadowRect.top() - Shadow_Overlap - params.offset.y(),
0259         shadowRect.right() - boxRect.right() - Shadow_Overlap + params.offset.x(),
0260         shadowRect.bottom() - boxRect.bottom() - Shadow_Overlap + params.offset.y());
0261 
0262     margins *= shadowTiles.pixmap(0).devicePixelRatio();
0263 
0264     return margins;
0265 }