File indexing completed on 2024-05-05 05:35:16

0001 /*
0002     SPDX-FileCopyrightText: 2009 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0003     SPDX-FileCopyrightText: 2006, 2007 Riccardo Iaconelli <riccardo@kde.org>
0004     SPDX-FileCopyrightText: 2006, 2007 Casper Boemann <cbr@boemann.dk>
0005     SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008 */
0009 
0010 #include "oxygenbutton.h"
0011 #include "oxygensettingsprovider.h"
0012 
0013 #include <KColorScheme>
0014 #include <KColorUtils>
0015 #include <KDecoration2/DecoratedClient>
0016 
0017 #include <QPainter>
0018 
0019 namespace Oxygen
0020 {
0021 //____________________________________________________________________________________
0022 Button *Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent)
0023 {
0024     if (auto d = qobject_cast<Decoration *>(decoration)) {
0025         const auto clientPtr = d->client();
0026         Button *b = new Button(type, d, parent);
0027         switch (type) {
0028         case KDecoration2::DecorationButtonType::Close:
0029             b->setVisible(clientPtr->isCloseable());
0030             QObject::connect(clientPtr, &KDecoration2::DecoratedClient::closeableChanged, b, &Oxygen::Button::setVisible);
0031             break;
0032 
0033         case KDecoration2::DecorationButtonType::Maximize:
0034             b->setVisible(clientPtr->isMaximizeable());
0035             QObject::connect(clientPtr, &KDecoration2::DecoratedClient::maximizeableChanged, b, &Oxygen::Button::setVisible);
0036             break;
0037 
0038         case KDecoration2::DecorationButtonType::Minimize:
0039             b->setVisible(clientPtr->isMinimizeable());
0040             QObject::connect(clientPtr, &KDecoration2::DecoratedClient::minimizeableChanged, b, &Oxygen::Button::setVisible);
0041             break;
0042 
0043         case KDecoration2::DecorationButtonType::ContextHelp:
0044             b->setVisible(clientPtr->providesContextHelp());
0045             QObject::connect(clientPtr, &KDecoration2::DecoratedClient::providesContextHelpChanged, b, &Oxygen::Button::setVisible);
0046             break;
0047 
0048         case KDecoration2::DecorationButtonType::Shade:
0049             b->setVisible(clientPtr->isShadeable());
0050             QObject::connect(clientPtr, &KDecoration2::DecoratedClient::shadeableChanged, b, &Oxygen::Button::setVisible);
0051             break;
0052 
0053         case KDecoration2::DecorationButtonType::Menu:
0054             QObject::connect(clientPtr, &KDecoration2::DecoratedClient::iconChanged, b, [b]() {
0055                 b->update();
0056             });
0057             break;
0058 
0059         default:
0060             break;
0061         }
0062 
0063         return b;
0064 
0065     } else
0066         return nullptr;
0067 }
0068 
0069 //____________________________________________________________________________________
0070 Button::Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent)
0071     : KDecoration2::DecorationButton(type, decoration, parent)
0072     , m_animation(new QPropertyAnimation(this))
0073     , m_opacity(0)
0074 {
0075     // setup animation
0076     m_animation->setStartValue(0);
0077     m_animation->setEndValue(1.0);
0078     m_animation->setTargetObject(this);
0079     m_animation->setPropertyName("opacity");
0080     m_animation->setEasingCurve(QEasingCurve::InOutQuad);
0081 
0082     // setup default geometry
0083     const int height = decoration->buttonHeight();
0084     setGeometry(QRect(0, 0, height, height));
0085     setIconSize(QSize(height, height));
0086 
0087     reconfigure();
0088 
0089     // setup connections
0090     if (isMenuButton()) {
0091         connect(decoration->client(), SIGNAL(iconChanged(QIcon)), this, SLOT(update()));
0092     }
0093 
0094     connect(decoration->settings().get(), &KDecoration2::DecorationSettings::reconfigured, this, &Button::reconfigure);
0095     connect(this, &KDecoration2::DecorationButton::hoveredChanged, this, &Button::updateAnimationState);
0096 }
0097 
0098 //_______________________________________________
0099 Button::Button(QObject *parent, const QVariantList &args)
0100     : Button(args.at(0).value<KDecoration2::DecorationButtonType>(), args.at(1).value<Decoration *>(), parent)
0101 {
0102     m_flag = FlagStandalone;
0103     //! icon size must return to !valid because it was altered from the default constructor,
0104     //! in Standalone mode the button is not using the decoration metrics but its geometry
0105     m_iconSize = QSize(-1, -1);
0106 }
0107 
0108 //_______________________________________________
0109 QColor Button::foregroundColor(const QPalette &palette) const
0110 {
0111     auto d(qobject_cast<Decoration *>(decoration()));
0112     if (d->isAnimated()) {
0113         return KColorUtils::mix(foregroundColor(palette, false), foregroundColor(palette, true), d->opacity());
0114 
0115     } else {
0116         return foregroundColor(palette, isActive());
0117     }
0118 }
0119 
0120 //___________________________________________________
0121 QColor Button::foregroundColor(const QPalette &palette, bool active) const
0122 {
0123     auto d(qobject_cast<Decoration *>(decoration()));
0124     if (d->internalSettings()->useWindowColors()) {
0125         return palette.color(active ? QPalette::Active : QPalette::Disabled, QPalette::ButtonText);
0126 
0127     } else {
0128         return d->fontColor(palette, active);
0129     }
0130 }
0131 
0132 //_______________________________________________
0133 QColor Button::backgroundColor(const QPalette &palette) const
0134 {
0135     auto d(qobject_cast<Decoration *>(decoration()));
0136     if (d->isAnimated()) {
0137         return KColorUtils::mix(backgroundColor(palette, false), backgroundColor(palette, true), d->opacity());
0138 
0139     } else {
0140         return backgroundColor(palette, isActive());
0141     }
0142 }
0143 
0144 //___________________________________________________
0145 QColor Button::backgroundColor(const QPalette &palette, bool active) const
0146 {
0147     auto d(qobject_cast<Decoration *>(decoration()));
0148     if (d->internalSettings()->useWindowColors()) {
0149         return palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Button);
0150 
0151     } else {
0152         return d->titleBarColor(palette, active);
0153     }
0154 }
0155 
0156 //___________________________________________________
0157 bool Button::isActive(void) const
0158 {
0159     return decoration()->client()->isActive();
0160 }
0161 
0162 //___________________________________________________
0163 void Button::reconfigure(void)
0164 {
0165     // animation
0166     auto d = qobject_cast<Decoration *>(decoration());
0167     if (d)
0168         m_animation->setDuration(d->internalSettings()->buttonAnimationsDuration());
0169 }
0170 
0171 //___________________________________________________
0172 void Button::paint(QPainter *painter, const QRect &repaintRegion)
0173 {
0174     Q_UNUSED(repaintRegion)
0175 
0176     if (!decoration())
0177         return;
0178 
0179     painter->save();
0180 
0181     // translate from offset
0182     if (m_flag == FlagFirstInList)
0183         painter->translate(m_offset);
0184     else
0185         painter->translate(0, m_offset.y());
0186 
0187     if (!m_iconSize.isValid() || isStandAlone())
0188         m_iconSize = geometry().size().toSize();
0189 
0190     const auto clientPtr = decoration()->client();
0191     // menu buttons
0192     if (isMenuButton()) {
0193         const QRectF iconRect(geometry().topLeft(), m_iconSize);
0194         clientPtr->icon().paint(painter, iconRect.toRect());
0195         painter->restore();
0196         return;
0197     }
0198 
0199     // palette
0200     QPalette palette(clientPtr->palette());
0201     palette.setCurrentColorGroup(isActive() ? QPalette::Active : QPalette::Inactive);
0202 
0203     // base button color
0204     QColor base = backgroundColor(palette);
0205 
0206     // text color
0207     QColor color = foregroundColor(palette);
0208 
0209     // decide decoration color
0210     QColor glow;
0211     if (isAnimated() || isHovered() || (isToggleButton() && isChecked())) {
0212         QColor toggleColor = SettingsProvider::self()->helper()->focusColor(palette);
0213         QColor toggledHoverGlow = foregroundColor(palette, false);
0214         QColor toggledHoverColor = KColorUtils::mix(toggledHoverColor, color, 0.6);
0215 
0216         if (isCloseButton())
0217             glow = SettingsProvider::self()->helper()->negativeTextColor(palette); // Button is close button
0218         else if (isHovered() && (isToggleButton() && isChecked()))
0219             glow = toggledHoverGlow; // Button is checked and hovered
0220         else if (isToggleButton() && isChecked())
0221             glow = toggleColor; // Button is checked but not hovered
0222         else
0223             glow = SettingsProvider::self()->helper()->hoverColor(palette); // Button is hovered but not checked
0224 
0225         if (isAnimated()) {
0226             if (isToggleButton() && isChecked()) {
0227                 color = KColorUtils::mix(toggleColor, toggledHoverColor, m_opacity);
0228                 glow = KColorUtils::mix(toggleColor, toggledHoverGlow, m_opacity);
0229             } else {
0230                 color = KColorUtils::mix(color, glow, m_opacity);
0231                 glow = SettingsProvider::self()->helper()->alphaColor(glow, m_opacity);
0232             }
0233 
0234         } else if (!isHovered() != !(isToggleButton() && isChecked()))
0235             color = glow; // If button is eigther hovered or checked, use glow color as text color
0236         else if ((isToggleButton() && isChecked()))
0237             color = toggledHoverColor; // If button is checked and hovered, use different color
0238     }
0239 
0240     // draw button shape
0241     const bool sunken = isPressed() || (isToggleButton() && isChecked());
0242     const QRectF iconRect(geometry().topLeft(), m_iconSize);
0243     painter->drawPixmap(iconRect.topLeft(), SettingsProvider::self()->helper()->windecoButton(base, glow, sunken, m_iconSize.width()));
0244 
0245     // Icon
0246     painter->setRenderHints(QPainter::Antialiasing);
0247     painter->translate(geometry().topLeft());
0248 
0249     qreal width(1.2);
0250 
0251     // contrast
0252     painter->setBrush(Qt::NoBrush);
0253     painter->translate(0, 1.5);
0254     painter->setPen(QPen(SettingsProvider::self()->helper()->calcLightColor(base), width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
0255     drawIcon(painter);
0256 
0257     // main
0258     painter->translate(0, -1.5);
0259     painter->setPen(QPen(color, width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
0260     drawIcon(painter);
0261 
0262     painter->restore();
0263 }
0264 
0265 //___________________________________________________
0266 void Button::drawIcon(QPainter *painter)
0267 {
0268     painter->save();
0269 
0270     // keep all co-ordinates between 0 and 21
0271     const qreal width(m_iconSize.width());
0272     painter->scale(width / 21, width / 21);
0273 
0274     // make sure pen width is always larger than 1.1 in "real" coordinates
0275     QPen pen(painter->pen());
0276     pen.setWidthF(qMax(1.1 * 21 / width, pen.widthF()));
0277     painter->setPen(pen);
0278 
0279     switch (type()) {
0280     case KDecoration2::DecorationButtonType::OnAllDesktops:
0281         painter->drawPoint(QPointF(10.5, 10.5));
0282         break;
0283 
0284     case KDecoration2::DecorationButtonType::ContextHelp:
0285         painter->translate(1.5, 1.5);
0286         painter->drawArc(7, 5, 4, 4, 135 * 16, -180 * 16);
0287         painter->drawArc(9, 8, 4, 4, 135 * 16, 45 * 16);
0288         painter->drawPoint(9, 12);
0289         painter->translate(-1.5, -1.5);
0290         break;
0291 
0292     case KDecoration2::DecorationButtonType::ApplicationMenu:
0293         painter->drawLine(QPointF(7.5, 7.5), QPointF(13.5, 7.5));
0294         painter->drawLine(QPointF(7.5, 10.5), QPointF(13.5, 10.5));
0295         painter->drawLine(QPointF(7.5, 13.5), QPointF(13.5, 13.5));
0296         break;
0297 
0298     case KDecoration2::DecorationButtonType::Minimize: {
0299         painter->drawPolyline(QPolygonF() << QPointF(7.5, 9.5) << QPointF(10.5, 12.5) << QPointF(13.5, 9.5));
0300         break;
0301     }
0302 
0303     case KDecoration2::DecorationButtonType::Maximize:
0304         if (decoration()->client()->isMaximized()) {
0305             painter->drawPolygon(QPolygonF() << QPointF(7.5, 10.5) << QPointF(10.5, 7.5) << QPointF(13.5, 10.5) << QPointF(10.5, 13.5));
0306 
0307         } else {
0308             painter->drawPolyline(QPolygonF() << QPointF(7.5, 11.5) << QPointF(10.5, 8.5) << QPointF(13.5, 11.5));
0309         }
0310         break;
0311 
0312     case KDecoration2::DecorationButtonType::Close:
0313         painter->drawLine(QPointF(7.5, 7.5), QPointF(13.5, 13.5));
0314         painter->drawLine(QPointF(13.5, 7.5), QPointF(7.5, 13.5));
0315         break;
0316 
0317     case KDecoration2::DecorationButtonType::KeepAbove: {
0318         painter->drawPolyline(QPolygonF() << QPointF(7.5, 14) << QPointF(10.5, 11) << QPointF(13.5, 14));
0319 
0320         painter->drawPolyline(QPolygonF() << QPointF(7.5, 10) << QPointF(10.5, 7) << QPointF(13.5, 10));
0321         break;
0322     }
0323 
0324     case KDecoration2::DecorationButtonType::KeepBelow: {
0325         painter->drawPolyline(QPolygonF() << QPointF(7.5, 11) << QPointF(10.5, 14) << QPointF(13.5, 11));
0326 
0327         painter->drawPolyline(QPolygonF() << QPointF(7.5, 7) << QPointF(10.5, 10) << QPointF(13.5, 7));
0328 
0329         break;
0330     }
0331 
0332     case KDecoration2::DecorationButtonType::Shade:
0333         if (!isChecked()) {
0334             // shade button
0335             painter->drawPolyline(QPolygonF() << QPointF(7.5, 7.5) << QPointF(10.5, 10.5) << QPointF(13.5, 7.5));
0336 
0337             painter->drawLine(QPointF(7.5, 13.0), QPointF(13.5, 13.0));
0338 
0339         } else {
0340             painter->drawPolyline(QPolygonF() << QPointF(7.5, 10.5) << QPointF(10.5, 7.5) << QPointF(13.5, 10.5));
0341 
0342             painter->drawLine(QPointF(7.5, 13.0), QPointF(13.5, 13.0));
0343         }
0344         break;
0345 
0346     default:
0347         break;
0348     }
0349     painter->restore();
0350     return;
0351 }
0352 
0353 //__________________________________________________________________
0354 void Button::updateAnimationState(bool hovered)
0355 {
0356     auto d = qobject_cast<Decoration *>(decoration());
0357     if (!(d && d->internalSettings()->animationsEnabled()))
0358         return;
0359 
0360     m_animation->setDirection(hovered ? QPropertyAnimation::Forward : QPropertyAnimation::Backward);
0361     if (m_animation->state() != QPropertyAnimation::Running)
0362         m_animation->start();
0363 }
0364 }