File indexing completed on 2024-05-05 05:28:58

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003  * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 #include "breezebutton.h"
0008 
0009 #include <KColorUtils>
0010 #include <KDecoration2/DecoratedClient>
0011 #include <KIconLoader>
0012 
0013 #include <QPainter>
0014 #include <QPainterPath>
0015 #include <QVariantAnimation>
0016 
0017 namespace Breeze
0018 {
0019 using KDecoration2::ColorGroup;
0020 using KDecoration2::ColorRole;
0021 using KDecoration2::DecorationButtonType;
0022 
0023 //__________________________________________________________________
0024 Button::Button(DecorationButtonType type, Decoration *decoration, QObject *parent)
0025     : DecorationButton(type, decoration, parent)
0026     , m_animation(new QVariantAnimation(this))
0027 {
0028     // setup animation
0029     // It is important start and end value are of the same type, hence 0.0 and not just 0
0030     m_animation->setStartValue(0.0);
0031     m_animation->setEndValue(1.0);
0032     m_animation->setEasingCurve(QEasingCurve::InOutQuad);
0033     connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
0034         setOpacity(value.toReal());
0035     });
0036 
0037     // connections
0038     connect(decoration->client(), SIGNAL(iconChanged(QIcon)), this, SLOT(update()));
0039     connect(decoration->settings().get(), &KDecoration2::DecorationSettings::reconfigured, this, &Button::reconfigure);
0040     connect(this, &KDecoration2::DecorationButton::hoveredChanged, this, &Button::updateAnimationState);
0041 
0042     reconfigure();
0043 }
0044 
0045 //__________________________________________________________________
0046 Button::Button(QObject *parent, const QVariantList &args)
0047     : Button(args.at(0).value<DecorationButtonType>(), args.at(1).value<Decoration *>(), parent)
0048 {
0049     setGeometry(QRectF(QPointF(0, 0), preferredSize()));
0050 }
0051 
0052 //__________________________________________________________________
0053 Button *Button::create(DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent)
0054 {
0055     if (auto d = qobject_cast<Decoration *>(decoration)) {
0056         Button *b = new Button(type, d, parent);
0057         const auto c = d->client();
0058         switch (type) {
0059         case DecorationButtonType::Close:
0060             b->setVisible(c->isCloseable());
0061             QObject::connect(c, &KDecoration2::DecoratedClient::closeableChanged, b, &Breeze::Button::setVisible);
0062             break;
0063 
0064         case DecorationButtonType::Maximize:
0065             b->setVisible(c->isMaximizeable());
0066             QObject::connect(c, &KDecoration2::DecoratedClient::maximizeableChanged, b, &Breeze::Button::setVisible);
0067             break;
0068 
0069         case DecorationButtonType::Minimize:
0070             b->setVisible(c->isMinimizeable());
0071             QObject::connect(c, &KDecoration2::DecoratedClient::minimizeableChanged, b, &Breeze::Button::setVisible);
0072             break;
0073 
0074         case DecorationButtonType::ContextHelp:
0075             b->setVisible(c->providesContextHelp());
0076             QObject::connect(c, &KDecoration2::DecoratedClient::providesContextHelpChanged, b, &Breeze::Button::setVisible);
0077             break;
0078 
0079         case DecorationButtonType::Shade:
0080             b->setVisible(c->isShadeable());
0081             QObject::connect(c, &KDecoration2::DecoratedClient::shadeableChanged, b, &Breeze::Button::setVisible);
0082             break;
0083 
0084         case DecorationButtonType::Menu:
0085             QObject::connect(c, &KDecoration2::DecoratedClient::iconChanged, b, [b]() {
0086                 b->update();
0087             });
0088             break;
0089 
0090         default:
0091             break;
0092         }
0093 
0094         return b;
0095     }
0096 
0097     return nullptr;
0098 }
0099 
0100 //__________________________________________________________________
0101 void Button::paint(QPainter *painter, const QRect &repaintRegion)
0102 {
0103     Q_UNUSED(repaintRegion)
0104 
0105     if (!decoration()) {
0106         return;
0107     }
0108 
0109     switch (type()) {
0110     case KDecoration2::DecorationButtonType::Menu: {
0111         const QRectF iconRect = geometry().marginsRemoved(m_padding);
0112         const auto c = decoration()->client();
0113         if (auto deco = qobject_cast<Decoration *>(decoration())) {
0114             const QPalette activePalette = KIconLoader::global()->customPalette();
0115             QPalette palette = c->palette();
0116             palette.setColor(QPalette::WindowText, deco->fontColor());
0117             KIconLoader::global()->setCustomPalette(palette);
0118             c->icon().paint(painter, iconRect.toRect());
0119             if (activePalette == QPalette()) {
0120                 KIconLoader::global()->resetPalette();
0121             } else {
0122                 KIconLoader::global()->setCustomPalette(palette);
0123             }
0124         } else {
0125             c->icon().paint(painter, iconRect.toRect());
0126         }
0127         break;
0128     }
0129     case KDecoration2::DecorationButtonType::Spacer:
0130         break;
0131     default:
0132         painter->save();
0133         drawIcon(painter);
0134         painter->restore();
0135         break;
0136     }
0137 }
0138 
0139 //__________________________________________________________________
0140 void Button::drawIcon(QPainter *painter) const
0141 {
0142     painter->setRenderHints(QPainter::Antialiasing);
0143 
0144     /*
0145     scale painter so that its window matches QRect( -1, -1, 20, 20 )
0146     this makes all further rendering and scaling simpler
0147     all further rendering is performed inside QRect( 0, 0, 18, 18 )
0148     */
0149     const QRectF rect = geometry().marginsRemoved(m_padding);
0150     painter->translate(rect.topLeft());
0151 
0152     const qreal width(rect.width());
0153     painter->scale(width / 20, width / 20);
0154     painter->translate(1, 1);
0155 
0156     // render background
0157     const QColor backgroundColor(this->backgroundColor());
0158     if (backgroundColor.isValid()) {
0159         painter->setPen(Qt::NoPen);
0160         painter->setBrush(backgroundColor);
0161         painter->drawEllipse(QRectF(0, 0, 18, 18));
0162     }
0163 
0164     // render mark
0165     const QColor foregroundColor(this->foregroundColor());
0166     if (foregroundColor.isValid()) {
0167         // setup painter
0168         QPen pen(foregroundColor);
0169         pen.setCapStyle(Qt::RoundCap);
0170         pen.setJoinStyle(Qt::MiterJoin);
0171         pen.setWidthF(PenWidth::Symbol * qMax((qreal)1.0, 20 / width));
0172 
0173         painter->setPen(pen);
0174         painter->setBrush(Qt::NoBrush);
0175 
0176         switch (type()) {
0177         case DecorationButtonType::Close: {
0178             painter->drawLine(QPointF(5, 5), QPointF(13, 13));
0179             painter->drawLine(13, 5, 5, 13);
0180             break;
0181         }
0182 
0183         case DecorationButtonType::Maximize: {
0184             if (isChecked()) {
0185                 pen.setJoinStyle(Qt::RoundJoin);
0186                 painter->setPen(pen);
0187 
0188                 painter->drawPolygon(QVector<QPointF>{QPointF(4, 9), QPointF(9, 4), QPointF(14, 9), QPointF(9, 14)});
0189 
0190             } else {
0191                 painter->drawPolyline(QVector<QPointF>{QPointF(4, 11), QPointF(9, 6), QPointF(14, 11)});
0192             }
0193             break;
0194         }
0195 
0196         case DecorationButtonType::Minimize: {
0197             painter->drawPolyline(QVector<QPointF>{QPointF(4, 7), QPointF(9, 12), QPointF(14, 7)});
0198             break;
0199         }
0200 
0201         case DecorationButtonType::OnAllDesktops: {
0202             painter->setPen(Qt::NoPen);
0203             painter->setBrush(foregroundColor);
0204 
0205             if (isChecked()) {
0206                 // outer ring
0207                 painter->drawEllipse(QRectF(3, 3, 12, 12));
0208 
0209                 // center dot
0210                 QColor backgroundColor(this->backgroundColor());
0211                 auto d = qobject_cast<Decoration *>(decoration());
0212                 if (!backgroundColor.isValid() && d) {
0213                     backgroundColor = d->titleBarColor();
0214                 }
0215 
0216                 if (backgroundColor.isValid()) {
0217                     painter->setBrush(backgroundColor);
0218                     painter->drawEllipse(QRectF(8, 8, 2, 2));
0219                 }
0220 
0221             } else {
0222                 painter->drawPolygon(QVector<QPointF>{QPointF(6.5, 8.5), QPointF(12, 3), QPointF(15, 6), QPointF(9.5, 11.5)});
0223 
0224                 painter->setPen(pen);
0225                 painter->drawLine(QPointF(5.5, 7.5), QPointF(10.5, 12.5));
0226                 painter->drawLine(QPointF(12, 6), QPointF(4.5, 13.5));
0227             }
0228             break;
0229         }
0230 
0231         case DecorationButtonType::Shade: {
0232             if (isChecked()) {
0233                 painter->drawLine(QPointF(4, 5.5), QPointF(14, 5.5));
0234                 painter->drawPolyline(QVector<QPointF>{QPointF(4, 8), QPointF(9, 13), QPointF(14, 8)});
0235 
0236             } else {
0237                 painter->drawLine(QPointF(4, 5.5), QPointF(14, 5.5));
0238                 painter->drawPolyline(QVector<QPointF>{QPointF(4, 13), QPointF(9, 8), QPointF(14, 13)});
0239             }
0240 
0241             break;
0242         }
0243 
0244         case DecorationButtonType::KeepBelow: {
0245             painter->drawPolyline(QVector<QPointF>{QPointF(4, 5), QPointF(9, 10), QPointF(14, 5)});
0246 
0247             painter->drawPolyline(QVector<QPointF>{QPointF(4, 9), QPointF(9, 14), QPointF(14, 9)});
0248             break;
0249         }
0250 
0251         case DecorationButtonType::KeepAbove: {
0252             painter->drawPolyline(QVector<QPointF>{QPointF(4, 9), QPointF(9, 4), QPointF(14, 9)});
0253 
0254             painter->drawPolyline(QVector<QPointF>{QPointF(4, 13), QPointF(9, 8), QPointF(14, 13)});
0255             break;
0256         }
0257 
0258         case DecorationButtonType::ApplicationMenu: {
0259             painter->drawRect(QRectF(3.5, 4.5, 11, 1));
0260             painter->drawRect(QRectF(3.5, 8.5, 11, 1));
0261             painter->drawRect(QRectF(3.5, 12.5, 11, 1));
0262             break;
0263         }
0264 
0265         case DecorationButtonType::ContextHelp: {
0266             QPainterPath path;
0267             path.moveTo(5, 6);
0268             path.arcTo(QRectF(5, 3.5, 8, 5), 180, -180);
0269             path.cubicTo(QPointF(12.5, 9.5), QPointF(9, 7.5), QPointF(9, 11.5));
0270             painter->drawPath(path);
0271 
0272             painter->drawRect(QRectF(9, 15, 0.5, 0.5));
0273 
0274             break;
0275         }
0276 
0277         default:
0278             break;
0279         }
0280     }
0281 }
0282 
0283 //__________________________________________________________________
0284 QColor Button::foregroundColor() const
0285 {
0286     auto d = qobject_cast<Decoration *>(decoration());
0287     if (!d) {
0288         return QColor();
0289 
0290     } else if (isPressed()) {
0291         return d->titleBarColor();
0292 
0293     } else if (type() == DecorationButtonType::Close && d->internalSettings()->outlineCloseButton()) {
0294         return d->titleBarColor();
0295 
0296     } else if ((type() == DecorationButtonType::KeepBelow || type() == DecorationButtonType::KeepAbove || type() == DecorationButtonType::Shade)
0297                && isChecked()) {
0298         return d->titleBarColor();
0299 
0300     } else if (m_animation->state() == QAbstractAnimation::Running) {
0301         return KColorUtils::mix(d->fontColor(), d->titleBarColor(), m_opacity);
0302 
0303     } else if (isHovered()) {
0304         return d->titleBarColor();
0305 
0306     } else {
0307         return d->fontColor();
0308     }
0309 }
0310 
0311 //__________________________________________________________________
0312 QColor Button::backgroundColor() const
0313 {
0314     auto d = qobject_cast<Decoration *>(decoration());
0315     if (!d) {
0316         return QColor();
0317     }
0318 
0319     auto c = d->client();
0320     QColor redColor(c->color(ColorGroup::Warning, ColorRole::Foreground));
0321 
0322     if (isPressed()) {
0323         if (type() == DecorationButtonType::Close) {
0324             return redColor.darker();
0325         } else {
0326             return KColorUtils::mix(d->titleBarColor(), d->fontColor(), 0.3);
0327         }
0328 
0329     } else if ((type() == DecorationButtonType::KeepBelow || type() == DecorationButtonType::KeepAbove || type() == DecorationButtonType::Shade)
0330                && isChecked()) {
0331         return d->fontColor();
0332 
0333     } else if (m_animation->state() == QAbstractAnimation::Running) {
0334         if (type() == DecorationButtonType::Close) {
0335             if (d->internalSettings()->outlineCloseButton()) {
0336                 return c->isActive() ? KColorUtils::mix(redColor, redColor.lighter(), m_opacity) : KColorUtils::mix(redColor.lighter(), redColor, m_opacity);
0337 
0338             } else {
0339                 QColor color(redColor.lighter());
0340                 color.setAlpha(color.alpha() * m_opacity);
0341                 return color;
0342             }
0343 
0344         } else {
0345             QColor color(d->fontColor());
0346             color.setAlpha(color.alpha() * m_opacity);
0347             return color;
0348         }
0349 
0350     } else if (isHovered()) {
0351         if (type() == DecorationButtonType::Close) {
0352             return c->isActive() ? redColor.lighter() : redColor;
0353         } else {
0354             return d->fontColor();
0355         }
0356 
0357     } else if (type() == DecorationButtonType::Close && d->internalSettings()->outlineCloseButton()) {
0358         return c->isActive() ? redColor : d->fontColor();
0359 
0360     } else {
0361         return QColor();
0362     }
0363 }
0364 
0365 //________________________________________________________________
0366 void Button::reconfigure()
0367 {
0368     // animation
0369     auto d = qobject_cast<Decoration *>(decoration());
0370     if (!d) {
0371         return;
0372     }
0373 
0374     switch (type()) {
0375     case KDecoration2::DecorationButtonType::Spacer:
0376         setPreferredSize(QSizeF(d->buttonSize() * 0.5, d->captionHeight()));
0377         break;
0378     default:
0379         setPreferredSize(QSizeF(d->buttonSize(), d->captionHeight()));
0380         break;
0381     }
0382 
0383     m_animation->setDuration(d->animationsDuration());
0384 }
0385 
0386 //__________________________________________________________________
0387 void Button::updateAnimationState(bool hovered)
0388 {
0389     auto d = qobject_cast<Decoration *>(decoration());
0390     if (!(d && d->animationsDuration() > 0)) {
0391         return;
0392     }
0393 
0394     m_animation->setDirection(hovered ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
0395     if (m_animation->state() != QAbstractAnimation::Running) {
0396         m_animation->start();
0397     }
0398 }
0399 
0400 } // namespace