File indexing completed on 2024-05-12 16:58:21

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