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