File indexing completed on 2024-05-05 05:35:34
0001 ////////////////////////////////////////////////////////////////////////////// 0002 // oxygenshadowcache.cpp 0003 // handles caching of TileSet objects to draw shadows 0004 // ------------------- 0005 // 0006 // SPDX-FileCopyrightText: 2009 Hugo Pereira Da Costa <hugo.pereira@free.fr> 0007 // 0008 // SPDX-License-Identifier: MIT 0009 ////////////////////////////////////////////////////////////////////////////// 0010 0011 #include "oxygenshadowcache.h" 0012 #include "oxygenactiveshadowconfiguration.h" 0013 #include "oxygeninactiveshadowconfiguration.h" 0014 0015 #include <KColorUtils> 0016 #include <QPainter> 0017 #include <QTextStream> 0018 0019 namespace Oxygen 0020 { 0021 //* square utility function 0022 static qreal square(qreal x) 0023 { 0024 return x * x; 0025 } 0026 0027 //* functions used to draw shadows 0028 class Parabolic 0029 { 0030 public: 0031 //* constructor 0032 Parabolic(qreal amplitude, qreal width) 0033 : amplitude_(amplitude) 0034 , width_(width) 0035 { 0036 } 0037 0038 //* destructor 0039 virtual ~Parabolic(void) 0040 { 0041 } 0042 0043 //* value 0044 virtual qreal operator()(qreal x) const 0045 { 0046 return qMax(0.0, amplitude_ * (1.0 - square(x / width_))); 0047 } 0048 0049 private: 0050 qreal amplitude_; 0051 qreal width_; 0052 }; 0053 0054 //* functions used to draw shadows 0055 class Gaussian 0056 { 0057 public: 0058 //* constructor 0059 Gaussian(qreal amplitude, qreal width) 0060 : amplitude_(amplitude) 0061 , width_(width) 0062 { 0063 } 0064 0065 //* destructor 0066 virtual ~Gaussian(void) 0067 { 0068 } 0069 0070 //* value 0071 virtual qreal operator()(qreal x) const 0072 { 0073 return qMax(0.0, amplitude_ * (std::exp(-square(x / width_) - 0.05))); 0074 } 0075 0076 private: 0077 qreal amplitude_; 0078 qreal width_; 0079 }; 0080 0081 //_______________________________________________________ 0082 ShadowCache::ShadowCache(Helper &helper) 0083 : _helper(helper) 0084 , _enabled(true) 0085 , _activeShadowSize(40) 0086 , _inactiveShadowSize(40) 0087 { 0088 setMaxIndex(256); 0089 } 0090 0091 //_______________________________________________________ 0092 void ShadowCache::readConfig(void) 0093 { 0094 if (!_enabled) 0095 setEnabled(true); 0096 0097 // shadows 0098 ActiveShadowConfiguration::self()->load(); 0099 InactiveShadowConfiguration::self()->load(); 0100 0101 // copy sizes to local 0102 _activeShadowSize = ActiveShadowConfiguration::shadowSize(); 0103 _inactiveShadowSize = InactiveShadowConfiguration::shadowSize(); 0104 0105 // invalidate caches 0106 invalidateCaches(); 0107 } 0108 0109 //_______________________________________________________ 0110 void ShadowCache::setAnimationsDuration(int value) 0111 { 0112 setMaxIndex(qMin(256, int((120 * value) / 1000))); 0113 invalidateCaches(); 0114 } 0115 0116 //_______________________________________________________ 0117 bool ShadowCache::isEnabled(QPalette::ColorGroup group) const 0118 { 0119 if (group == QPalette::Active) 0120 return ActiveShadowConfiguration::enabled(); 0121 else if (group == QPalette::Inactive) 0122 return InactiveShadowConfiguration::enabled(); 0123 else 0124 return false; 0125 } 0126 0127 //_______________________________________________________ 0128 void ShadowCache::setShadowSize(QPalette::ColorGroup group, int size) 0129 { 0130 if (group == QPalette::Active && _activeShadowSize != size) { 0131 _activeShadowSize = size; 0132 invalidateCaches(); 0133 0134 } else if (group == QPalette::Inactive && _inactiveShadowSize != size) { 0135 _inactiveShadowSize = size; 0136 invalidateCaches(); 0137 } 0138 } 0139 0140 //_______________________________________________________ 0141 int ShadowCache::shadowSize(void) const 0142 { 0143 int activeSize(ActiveShadowConfiguration::enabled() ? _activeShadowSize : 0); 0144 int inactiveSize(InactiveShadowConfiguration::enabled() ? _inactiveShadowSize : 0); 0145 0146 // even if shadows are disabled, 0147 return qMax(activeSize, inactiveSize); 0148 } 0149 0150 //_______________________________________________________ 0151 TileSet ShadowCache::tileSet(const Key &key) 0152 { 0153 // check if tileSet already in cache 0154 int hash(key.hash()); 0155 if (_enabled) { 0156 if (TileSet *cachedTileSet = _shadowCache.object(hash)) { 0157 return *cachedTileSet; 0158 } 0159 } 0160 0161 // create tileSet otherwise 0162 const qreal size(shadowSize() + overlap); 0163 TileSet tileSet(pixmap(key), size, size, size, size, size, size, 1, 1); 0164 _shadowCache.insert(hash, new TileSet(tileSet)); 0165 0166 return tileSet; 0167 } 0168 0169 //_______________________________________________________ 0170 TileSet ShadowCache::tileSet(Key key, qreal opacity) 0171 { 0172 int index(opacity * _maxIndex); 0173 Q_ASSERT(index <= _maxIndex); 0174 0175 // construct key 0176 key.index = index; 0177 0178 // check if tileSet already in cache 0179 int hash(key.hash()); 0180 if (_enabled) { 0181 if (TileSet *cachedTileSet = _animatedShadowCache.object(hash)) { 0182 return *cachedTileSet; 0183 } 0184 } 0185 0186 // create shadow and tileset otherwise 0187 const qreal size(shadowSize() + overlap); 0188 TileSet tileSet(animatedPixmap(key, opacity), size, size, 1, 1); 0189 _animatedShadowCache.insert(hash, new TileSet(tileSet)); 0190 return tileSet; 0191 } 0192 0193 //_______________________________________________________ 0194 QPixmap ShadowCache::animatedPixmap(const Key &key, qreal opacity) 0195 { 0196 // create shadow and tileset otherwise 0197 const qreal size(shadowSize() + overlap); 0198 0199 QPixmap shadow(_helper.highDpiPixmap(size * 2)); 0200 shadow.fill(Qt::transparent); 0201 QPainter painter(&shadow); 0202 painter.setRenderHint(QPainter::Antialiasing); 0203 0204 QPixmap inactiveShadow(pixmap(key, false)); 0205 if (!inactiveShadow.isNull()) { 0206 QPainter local(&inactiveShadow); 0207 local.setRenderHint(QPainter::Antialiasing); 0208 local.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0209 local.fillRect(inactiveShadow.rect(), QColor(0, 0, 0, 255 * (1.0 - opacity))); 0210 } 0211 0212 QPixmap activeShadow(pixmap(key, true)); 0213 if (!activeShadow.isNull()) { 0214 QPainter local(&activeShadow); 0215 local.setRenderHint(QPainter::Antialiasing); 0216 local.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0217 local.fillRect(activeShadow.rect(), QColor(0, 0, 0, 255 * (opacity))); 0218 } 0219 0220 painter.drawPixmap(QPointF(0, 0), inactiveShadow); 0221 painter.drawPixmap(QPointF(0, 0), activeShadow); 0222 painter.end(); 0223 0224 return shadow; 0225 } 0226 0227 //_______________________________________________________ 0228 QPixmap ShadowCache::pixmap(const Key &key, bool active) const 0229 { 0230 static const qreal fixedSize = 25.5; 0231 qreal size(shadowSize()); 0232 qreal shadowSize(0); 0233 0234 if (active && ActiveShadowConfiguration::enabled()) 0235 shadowSize = _activeShadowSize; 0236 else if (!active && InactiveShadowConfiguration::enabled()) 0237 shadowSize = _inactiveShadowSize; 0238 0239 if (!shadowSize) 0240 return QPixmap(); 0241 0242 // add overlap 0243 size += overlap; 0244 shadowSize += overlap; 0245 0246 QPixmap shadow(_helper.highDpiPixmap(size * 2)); 0247 shadow.fill(Qt::transparent); 0248 0249 QPainter painter(&shadow); 0250 painter.setRenderHint(QPainter::Antialiasing); 0251 painter.setPen(Qt::NoPen); 0252 0253 // some gradients rendering are different at bottom corners if client has no border 0254 bool hasBorder(key.hasBorder || key.isShade); 0255 0256 if (active) { 0257 { 0258 // inner (sharp) gradient 0259 const qreal gradientSize = qMin(shadowSize, (shadowSize + fixedSize) / 2); 0260 const qreal voffset = qMin(12.0 * (gradientSize * ActiveShadowConfiguration::verticalOffset()) / fixedSize, 4.0); 0261 0262 QRadialGradient radialGradient = QRadialGradient(size, size + voffset, gradientSize); 0263 radialGradient.setColorAt(1, Qt::transparent); 0264 0265 // gaussian shadow is used 0266 int nPoints((10 * gradientSize) / fixedSize); 0267 Gaussian f(0.85, 0.17); 0268 QColor c = ActiveShadowConfiguration::innerColor(); 0269 for (int i = 0; i < nPoints; i++) { 0270 qreal x = qreal(i) / nPoints; 0271 c.setAlphaF(f(x)); 0272 radialGradient.setColorAt(x, c); 0273 } 0274 0275 painter.setBrush(radialGradient); 0276 renderGradient(painter, shadow.rect(), radialGradient, hasBorder); 0277 } 0278 0279 { 0280 // outer (spread) gradient 0281 const qreal gradientSize = shadowSize; 0282 const qreal voffset = qMin(12.0 * (gradientSize * ActiveShadowConfiguration::verticalOffset()) / fixedSize, 4.0); 0283 0284 QRadialGradient radialGradient = QRadialGradient(size, size + voffset, gradientSize); 0285 radialGradient.setColorAt(1, Qt::transparent); 0286 0287 // gaussian shadow is used 0288 int nPoints((10 * gradientSize) / fixedSize); 0289 Gaussian f(0.46, 0.34); 0290 QColor c = ActiveShadowConfiguration::useOuterColor() ? ActiveShadowConfiguration::outerColor() : ActiveShadowConfiguration::innerColor(); 0291 for (int i = 0; i < nPoints; i++) { 0292 qreal x = qreal(i) / nPoints; 0293 c.setAlphaF(f(x)); 0294 radialGradient.setColorAt(x, c); 0295 } 0296 0297 painter.setBrush(radialGradient); 0298 painter.drawRect(shadow.rect()); 0299 } 0300 0301 } else { 0302 { 0303 // inner (sharp gradient) 0304 const qreal gradientSize = qMin(shadowSize, fixedSize); 0305 const qreal voffset(0.2); 0306 0307 QRadialGradient radialGradient = QRadialGradient(size, size + voffset, gradientSize); 0308 radialGradient.setColorAt(1, Qt::transparent); 0309 0310 // parabolic shadow is used 0311 int nPoints((10 * gradientSize) / fixedSize); 0312 Parabolic f(1.0, 0.22); 0313 QColor c = InactiveShadowConfiguration::useOuterColor() ? InactiveShadowConfiguration::outerColor() : InactiveShadowConfiguration::innerColor(); 0314 for (int i = 0; i < nPoints; i++) { 0315 qreal x = qreal(i) / nPoints; 0316 c.setAlphaF(f(x)); 0317 radialGradient.setColorAt(x, c); 0318 } 0319 0320 painter.setBrush(radialGradient); 0321 renderGradient(painter, shadow.rect(), radialGradient, hasBorder); 0322 } 0323 0324 { 0325 // mid gradient 0326 const qreal gradientSize = qMin(shadowSize, (shadowSize + 2 * fixedSize) / 3); 0327 const qreal voffset = qMin(8.0 * (gradientSize * InactiveShadowConfiguration::verticalOffset()) / fixedSize, 4.0); 0328 0329 // gaussian shadow is used 0330 QRadialGradient radialGradient = QRadialGradient(size, size + voffset, gradientSize); 0331 radialGradient.setColorAt(1, Qt::transparent); 0332 0333 int nPoints((10 * gradientSize) / fixedSize); 0334 Gaussian f(0.54, 0.21); 0335 QColor c = InactiveShadowConfiguration::useOuterColor() ? InactiveShadowConfiguration::outerColor() : InactiveShadowConfiguration::innerColor(); 0336 for (int i = 0; i < nPoints; i++) { 0337 qreal x = qreal(i) / nPoints; 0338 c.setAlphaF(f(x)); 0339 radialGradient.setColorAt(x, c); 0340 } 0341 0342 painter.setBrush(radialGradient); 0343 painter.drawRect(shadow.rect()); 0344 } 0345 0346 { 0347 // outer (spread) gradient 0348 const qreal gradientSize = shadowSize; 0349 const qreal voffset = qMin(20.0 * (gradientSize * InactiveShadowConfiguration::verticalOffset()) / fixedSize, 4.0); 0350 0351 // gaussian shadow is used 0352 QRadialGradient radialGradient = QRadialGradient(size, size + voffset, gradientSize); 0353 radialGradient.setColorAt(1, Qt::transparent); 0354 0355 int nPoints((20 * gradientSize) / fixedSize); 0356 Gaussian f(0.155, 0.445); 0357 QColor c = InactiveShadowConfiguration::useOuterColor() ? InactiveShadowConfiguration::outerColor() : InactiveShadowConfiguration::innerColor(); 0358 for (int i = 0; i < nPoints; i++) { 0359 qreal x = qreal(i) / nPoints; 0360 c.setAlphaF(f(x)); 0361 radialGradient.setColorAt(x, c); 0362 } 0363 0364 painter.setBrush(radialGradient); 0365 painter.drawRect(shadow.rect()); 0366 } 0367 } 0368 0369 // mask 0370 painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); 0371 painter.setBrush(Qt::black); 0372 painter.drawEllipse(QRectF(size - 3, size - 3, 6, 6)); 0373 0374 painter.end(); 0375 return shadow; 0376 } 0377 0378 //_______________________________________________________ 0379 void ShadowCache::renderGradient(QPainter &painter, const QRectF &rect, const QRadialGradient &radialGradient, bool hasBorder) const 0380 { 0381 if (hasBorder) { 0382 painter.setBrush(radialGradient); 0383 painter.drawRect(rect); 0384 return; 0385 } 0386 0387 const qreal size(rect.width() / 2.0); 0388 const qreal hoffset(radialGradient.center().x() - size); 0389 const qreal voffset(radialGradient.center().y() - size); 0390 const qreal radius(radialGradient.radius()); 0391 0392 // load gradient stops 0393 QGradientStops stops(radialGradient.stops()); 0394 0395 // draw ellipse for the upper rect 0396 { 0397 QRectF rect(hoffset, voffset, 2 * size - hoffset, size); 0398 painter.setBrush(radialGradient); 0399 painter.drawRect(rect); 0400 } 0401 0402 // draw square gradients for the lower rect 0403 { 0404 // vertical lines 0405 const QRectF rect(hoffset, size + voffset, 2 * size - hoffset, 4); 0406 QLinearGradient lg(hoffset, 0.0, 2 * size + hoffset, 0.0); 0407 for (int i = 0; i < stops.size(); i++) { 0408 const QColor c(stops[i].second); 0409 const qreal xx(stops[i].first * radius); 0410 lg.setColorAt((size - xx) / (2.0 * size), c); 0411 lg.setColorAt((size + xx) / (2.0 * size), c); 0412 } 0413 0414 painter.setBrush(lg); 0415 painter.drawRect(rect); 0416 } 0417 0418 { 0419 // horizontal line 0420 const QRectF rect(size - 4 + hoffset, size + voffset, 8, size); 0421 QLinearGradient lg = QLinearGradient(0, voffset, 0, 2 * size + voffset); 0422 for (int i = 0; i < stops.size(); i++) { 0423 const QColor c(stops[i].second); 0424 const qreal xx(stops[i].first * radius); 0425 lg.setColorAt((size + xx) / (2.0 * size), c); 0426 } 0427 0428 painter.setBrush(lg); 0429 painter.drawRect(rect); 0430 } 0431 0432 { 0433 // bottom-left corner 0434 const QRectF rect(hoffset, size + voffset + 4, size - 4, size); 0435 QRadialGradient radialGradient = QRadialGradient(size + hoffset - 4, size + voffset + 4, radius); 0436 for (int i = 0; i < stops.size(); i++) { 0437 QColor c(stops[i].second); 0438 qreal xx(stops[i].first - 4.0 / radius); 0439 if (xx < 0) { 0440 if (i < stops.size() - 1) { 0441 const qreal x1(stops[i + 1].first - 4.0 / radius); 0442 c = KColorUtils::mix(c, stops[i + 1].second, -xx / (x1 - xx)); 0443 } 0444 xx = 0; 0445 } 0446 0447 radialGradient.setColorAt(xx, c); 0448 } 0449 0450 painter.setBrush(radialGradient); 0451 painter.drawRect(rect); 0452 } 0453 0454 { 0455 // bottom-right corner 0456 const QRectF rect(size + hoffset + 4, size + voffset + 4, size - 4, size); 0457 QRadialGradient radialGradient = QRadialGradient(size + hoffset + 4, size + voffset + 4, radius); 0458 for (int i = 0; i < stops.size(); i++) { 0459 QColor c(stops[i].second); 0460 qreal xx(stops[i].first - 4.0 / radius); 0461 if (xx < 0) { 0462 if (i < stops.size() - 1) { 0463 const qreal x1(stops[i + 1].first - 4.0 / radius); 0464 c = KColorUtils::mix(c, stops[i + 1].second, -xx / (x1 - xx)); 0465 } 0466 xx = 0; 0467 } 0468 0469 radialGradient.setColorAt(xx, c); 0470 } 0471 0472 painter.setBrush(radialGradient); 0473 painter.drawRect(rect); 0474 } 0475 } 0476 }