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 }