File indexing completed on 2024-12-22 04:31:08
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 }