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 }