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

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003  * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0004  * SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0005  * SPDX-FileCopyrightText: 2021 Paul McAuley <kde@paulmcauley.com>
0006  *
0007  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008  */
0009 
0010 #include "breezedecoration.h"
0011 
0012 #include "breezesettingsprovider.h"
0013 #include "config/breezeconfigwidget.h"
0014 
0015 #include "breezebutton.h"
0016 
0017 #include "breezeboxshadowrenderer.h"
0018 
0019 #include <KDecoration2/DecorationButtonGroup>
0020 #include <KDecoration2/DecorationShadow>
0021 
0022 #include <KColorUtils>
0023 #include <KConfigGroup>
0024 #include <KPluginFactory>
0025 #include <KSharedConfig>
0026 
0027 #include <QDBusConnection>
0028 #include <QDBusMessage>
0029 #include <QDBusPendingCallWatcher>
0030 #include <QDBusPendingReply>
0031 #include <QPainter>
0032 #include <QPainterPath>
0033 #include <QTextStream>
0034 #include <QTimer>
0035 
0036 #include <cmath>
0037 
0038 K_PLUGIN_FACTORY_WITH_JSON(BreezeDecoFactory, "breeze.json", registerPlugin<Breeze::Decoration>(); registerPlugin<Breeze::Button>();
0039                            registerPlugin<Breeze::ConfigWidget>();)
0040 
0041 namespace
0042 {
0043 struct ShadowParams {
0044     ShadowParams()
0045         : offset(QPoint(0, 0))
0046         , radius(0)
0047         , opacity(0)
0048     {
0049     }
0050 
0051     ShadowParams(const QPoint &offset, int radius, qreal opacity)
0052         : offset(offset)
0053         , radius(radius)
0054         , opacity(opacity)
0055     {
0056     }
0057 
0058     QPoint offset;
0059     int radius;
0060     qreal opacity;
0061 };
0062 
0063 struct CompositeShadowParams {
0064     CompositeShadowParams() = default;
0065 
0066     CompositeShadowParams(const QPoint &offset, const ShadowParams &shadow1, const ShadowParams &shadow2)
0067         : offset(offset)
0068         , shadow1(shadow1)
0069         , shadow2(shadow2)
0070     {
0071     }
0072 
0073     bool isNone() const
0074     {
0075         return qMax(shadow1.radius, shadow2.radius) == 0;
0076     }
0077 
0078     QPoint offset;
0079     ShadowParams shadow1;
0080     ShadowParams shadow2;
0081 };
0082 
0083 const CompositeShadowParams s_shadowParams[] = {
0084     // None
0085     CompositeShadowParams(),
0086     // Small
0087     CompositeShadowParams(QPoint(0, 4), ShadowParams(QPoint(0, 0), 16, 1), ShadowParams(QPoint(0, -2), 8, 0.4)),
0088     // Medium
0089     CompositeShadowParams(QPoint(0, 8), ShadowParams(QPoint(0, 0), 32, 0.9), ShadowParams(QPoint(0, -4), 16, 0.3)),
0090     // Large
0091     CompositeShadowParams(QPoint(0, 12), ShadowParams(QPoint(0, 0), 48, 0.8), ShadowParams(QPoint(0, -6), 24, 0.2)),
0092     // Very large
0093     CompositeShadowParams(QPoint(0, 16), ShadowParams(QPoint(0, 0), 64, 0.7), ShadowParams(QPoint(0, -8), 32, 0.1)),
0094 };
0095 
0096 inline CompositeShadowParams lookupShadowParams(int size)
0097 {
0098     switch (size) {
0099     case Breeze::InternalSettings::ShadowNone:
0100         return s_shadowParams[0];
0101     case Breeze::InternalSettings::ShadowSmall:
0102         return s_shadowParams[1];
0103     case Breeze::InternalSettings::ShadowMedium:
0104         return s_shadowParams[2];
0105     case Breeze::InternalSettings::ShadowLarge:
0106         return s_shadowParams[3];
0107     case Breeze::InternalSettings::ShadowVeryLarge:
0108         return s_shadowParams[4];
0109     default:
0110         // Fallback to the Large size.
0111         return s_shadowParams[3];
0112     }
0113 }
0114 }
0115 
0116 namespace Breeze
0117 {
0118 using KDecoration2::ColorGroup;
0119 using KDecoration2::ColorRole;
0120 
0121 //________________________________________________________________
0122 static int g_sDecoCount = 0;
0123 static int g_shadowSizeEnum = InternalSettings::ShadowLarge;
0124 static int g_shadowStrength = 255;
0125 static QColor g_shadowColor = Qt::black;
0126 static QSharedPointer<KDecoration2::DecorationShadow> g_sShadow;
0127 static QSharedPointer<KDecoration2::DecorationShadow> g_sShadowInactive;
0128 static int g_lastBorderSize;
0129 static QColor g_lastOutlineColor;
0130 
0131 //________________________________________________________________
0132 Decoration::Decoration(QObject *parent, const QVariantList &args)
0133     : KDecoration2::Decoration(parent, args)
0134     , m_animation(new QVariantAnimation(this))
0135     , m_shadowAnimation(new QVariantAnimation(this))
0136 {
0137     g_sDecoCount++;
0138 }
0139 
0140 //________________________________________________________________
0141 Decoration::~Decoration()
0142 {
0143     g_sDecoCount--;
0144     if (g_sDecoCount == 0) {
0145         // last deco destroyed, clean up shadow
0146         g_sShadow.clear();
0147     }
0148 }
0149 
0150 //________________________________________________________________
0151 void Decoration::setOpacity(qreal value)
0152 {
0153     if (m_opacity == value) {
0154         return;
0155     }
0156     m_opacity = value;
0157     update();
0158 }
0159 
0160 //________________________________________________________________
0161 QColor Decoration::titleBarColor() const
0162 {
0163     const auto c = client().toStrongRef();
0164     if (hideTitleBar()) {
0165         return c->color(ColorGroup::Inactive, ColorRole::TitleBar);
0166     } else if (m_animation->state() == QAbstractAnimation::Running) {
0167         return KColorUtils::mix(c->color(ColorGroup::Inactive, ColorRole::TitleBar), c->color(ColorGroup::Active, ColorRole::TitleBar), m_opacity);
0168     } else {
0169         return c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::TitleBar);
0170     }
0171 }
0172 
0173 //________________________________________________________________
0174 QColor Decoration::fontColor() const
0175 {
0176     const auto c = client().toStrongRef();
0177     if (m_animation->state() == QAbstractAnimation::Running) {
0178         return KColorUtils::mix(c->color(ColorGroup::Inactive, ColorRole::Foreground), c->color(ColorGroup::Active, ColorRole::Foreground), m_opacity);
0179     } else {
0180         return c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Foreground);
0181     }
0182 }
0183 
0184 //________________________________________________________________
0185 void Decoration::init()
0186 {
0187     const auto c = client().toStrongRef();
0188 
0189     // active state change animation
0190     // It is important start and end value are of the same type, hence 0.0 and not just 0
0191     m_animation->setStartValue(0.0);
0192     m_animation->setEndValue(1.0);
0193     // Linear to have the same easing as Breeze animations
0194     m_animation->setEasingCurve(QEasingCurve::Linear);
0195     connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
0196         setOpacity(value.toReal());
0197     });
0198 
0199     m_shadowAnimation->setStartValue(0.0);
0200     m_shadowAnimation->setEndValue(1.0);
0201     m_shadowAnimation->setEasingCurve(QEasingCurve::OutCubic);
0202     connect(m_shadowAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) {
0203         m_shadowOpacity = value.toReal();
0204         updateShadow();
0205     });
0206 
0207     // use DBus connection to update on breeze configuration change
0208     auto dbus = QDBusConnection::sessionBus();
0209     dbus.connect(QString(),
0210                  QStringLiteral("/KGlobalSettings"),
0211                  QStringLiteral("org.kde.KGlobalSettings"),
0212                  QStringLiteral("notifyChange"),
0213                  this,
0214                  SLOT(reconfigure()));
0215 
0216     dbus.connect(QStringLiteral("org.kde.KWin"),
0217                  QStringLiteral("/org/kde/KWin"),
0218                  QStringLiteral("org.kde.KWin.TabletModeManager"),
0219                  QStringLiteral("tabletModeChanged"),
0220                  QStringLiteral("b"),
0221                  this,
0222                  SLOT(onTabletModeChanged(bool)));
0223 
0224     auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
0225                                                   QStringLiteral("/org/kde/KWin"),
0226                                                   QStringLiteral("org.freedesktop.DBus.Properties"),
0227                                                   QStringLiteral("Get"));
0228     message.setArguments({QStringLiteral("org.kde.KWin.TabletModeManager"), QStringLiteral("tabletMode")});
0229     auto call = new QDBusPendingCallWatcher(dbus.asyncCall(message), this);
0230     connect(call, &QDBusPendingCallWatcher::finished, this, [this, call]() {
0231         QDBusPendingReply<QVariant> reply = *call;
0232         if (!reply.isError()) {
0233             onTabletModeChanged(reply.value().toBool());
0234         }
0235 
0236         call->deleteLater();
0237     });
0238 
0239     reconfigure();
0240     updateTitleBar();
0241     auto s = settings();
0242     connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, this, &Decoration::recalculateBorders);
0243 
0244     // a change in font might cause the borders to change
0245     connect(s.data(), &KDecoration2::DecorationSettings::fontChanged, this, &Decoration::recalculateBorders);
0246     connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::recalculateBorders);
0247 
0248     // buttons
0249     connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::updateButtonsGeometryDelayed);
0250     connect(s.data(), &KDecoration2::DecorationSettings::decorationButtonsLeftChanged, this, &Decoration::updateButtonsGeometryDelayed);
0251     connect(s.data(), &KDecoration2::DecorationSettings::decorationButtonsRightChanged, this, &Decoration::updateButtonsGeometryDelayed);
0252 
0253     // full reconfiguration
0254     connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::reconfigure);
0255     connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, SettingsProvider::self(), &SettingsProvider::reconfigure, Qt::UniqueConnection);
0256     connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::updateButtonsGeometryDelayed);
0257 
0258     connect(c.data(), &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::recalculateBorders);
0259     connect(c.data(), &KDecoration2::DecoratedClient::maximizedHorizontallyChanged, this, &Decoration::recalculateBorders);
0260     connect(c.data(), &KDecoration2::DecoratedClient::maximizedVerticallyChanged, this, &Decoration::recalculateBorders);
0261     connect(c.data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::recalculateBorders);
0262     connect(c.data(), &KDecoration2::DecoratedClient::captionChanged, this, [this]() {
0263         // update the caption area
0264         update(titleBar());
0265     });
0266 
0267     connect(c.data(), &KDecoration2::DecoratedClient::activeChanged, this, &Decoration::updateAnimationState);
0268     connect(c.data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateTitleBar);
0269     connect(c.data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateTitleBar);
0270     connect(c.data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::setOpaque);
0271 
0272     connect(c.data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateButtonsGeometry);
0273     connect(c.data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateButtonsGeometry);
0274     connect(c.data(), &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::updateButtonsGeometry);
0275     connect(c.data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateButtonsGeometry);
0276 
0277     createButtons();
0278     updateShadow();
0279 }
0280 
0281 //________________________________________________________________
0282 void Decoration::updateTitleBar()
0283 {
0284     // The titlebar rect has margins around it so the window can be resized by dragging a decoration edge.
0285     auto s = settings();
0286     const auto c = client().toStrongRef();
0287     const bool maximized = isMaximized();
0288     const int width = maximized ? c->width() : c->width() - 2 * s->smallSpacing() * Metrics::TitleBar_SideMargin;
0289     const int height = maximized ? borderTop() : borderTop() - s->smallSpacing() * Metrics::TitleBar_TopMargin;
0290     const int x = maximized ? 0 : s->smallSpacing() * Metrics::TitleBar_SideMargin;
0291     const int y = maximized ? 0 : s->smallSpacing() * Metrics::TitleBar_TopMargin;
0292     setTitleBar(QRect(x, y, width, height));
0293 }
0294 
0295 //________________________________________________________________
0296 void Decoration::updateAnimationState()
0297 {
0298     if (m_shadowAnimation->duration() > 0) {
0299         const auto c = client().toStrongRef();
0300         m_shadowAnimation->setDirection(c->isActive() ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
0301         m_shadowAnimation->setEasingCurve(c->isActive() ? QEasingCurve::OutCubic : QEasingCurve::InCubic);
0302         if (m_shadowAnimation->state() != QAbstractAnimation::Running) {
0303             m_shadowAnimation->start();
0304         }
0305 
0306     } else {
0307         updateShadow();
0308     }
0309 
0310     if (m_animation->duration() > 0) {
0311         const auto c = client().toStrongRef();
0312         m_animation->setDirection(c->isActive() ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
0313         if (m_animation->state() != QAbstractAnimation::Running) {
0314             m_animation->start();
0315         }
0316 
0317     } else {
0318         update();
0319     }
0320 }
0321 
0322 //________________________________________________________________
0323 int Decoration::borderSize(bool bottom) const
0324 {
0325     const int baseSize = settings()->smallSpacing();
0326     if (m_internalSettings && (m_internalSettings->mask() & BorderSize)) {
0327         switch (m_internalSettings->borderSize()) {
0328         case InternalSettings::BorderNone:
0329             return 0;
0330         case InternalSettings::BorderNoSides:
0331             return bottom ? qMax(4, baseSize) : 0;
0332         default:
0333         case InternalSettings::BorderTiny:
0334             return bottom ? qMax(4, baseSize) : baseSize;
0335         case InternalSettings::BorderNormal:
0336             return baseSize * 2;
0337         case InternalSettings::BorderLarge:
0338             return baseSize * 3;
0339         case InternalSettings::BorderVeryLarge:
0340             return baseSize * 4;
0341         case InternalSettings::BorderHuge:
0342             return baseSize * 5;
0343         case InternalSettings::BorderVeryHuge:
0344             return baseSize * 6;
0345         case InternalSettings::BorderOversized:
0346             return baseSize * 10;
0347         }
0348 
0349     } else {
0350         switch (settings()->borderSize()) {
0351         case KDecoration2::BorderSize::None:
0352             return 0;
0353         case KDecoration2::BorderSize::NoSides:
0354             return bottom ? qMax(4, baseSize) : 0;
0355         default:
0356         case KDecoration2::BorderSize::Tiny:
0357             return bottom ? qMax(4, baseSize) : baseSize;
0358         case KDecoration2::BorderSize::Normal:
0359             return baseSize * 2;
0360         case KDecoration2::BorderSize::Large:
0361             return baseSize * 3;
0362         case KDecoration2::BorderSize::VeryLarge:
0363             return baseSize * 4;
0364         case KDecoration2::BorderSize::Huge:
0365             return baseSize * 5;
0366         case KDecoration2::BorderSize::VeryHuge:
0367             return baseSize * 6;
0368         case KDecoration2::BorderSize::Oversized:
0369             return baseSize * 10;
0370         }
0371     }
0372 }
0373 
0374 //________________________________________________________________
0375 void Decoration::reconfigure()
0376 {
0377     m_internalSettings = SettingsProvider::self()->internalSettings(this);
0378 
0379     setScaledCornerRadius();
0380 
0381     // animation
0382 
0383     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0384     const KConfigGroup cg(config, QStringLiteral("KDE"));
0385 
0386     m_animation->setDuration(0);
0387     // Syncing anis between client and decoration is troublesome, so we're not using
0388     // any animations right now.
0389     // m_animation->setDuration( cg.readEntry("AnimationDurationFactor", 1.0f) * 100.0f );
0390 
0391     // But the shadow is fine to animate like this!
0392     m_shadowAnimation->setDuration(cg.readEntry("AnimationDurationFactor", 1.0f) * 100.0f);
0393 
0394     // borders
0395     recalculateBorders();
0396 
0397     // shadow
0398     updateShadow();
0399 }
0400 
0401 //________________________________________________________________
0402 void Decoration::recalculateBorders()
0403 {
0404     const auto c = client().toStrongRef();
0405     auto s = settings();
0406 
0407     // left, right and bottom borders
0408     const int left = isLeftEdge() ? 0 : borderSize();
0409     const int right = isRightEdge() ? 0 : borderSize();
0410     const int bottom = (c->isShaded() || isBottomEdge()) ? 0 : borderSize(true);
0411 
0412     int top = 0;
0413     if (hideTitleBar()) {
0414         top = bottom;
0415     } else {
0416         QFontMetrics fm(s->font());
0417         top += qMax(fm.height(), buttonHeight());
0418 
0419         // padding below
0420         const int baseSize = s->smallSpacing();
0421         top += baseSize * Metrics::TitleBar_BottomMargin;
0422 
0423         // padding above
0424         top += baseSize * Metrics::TitleBar_TopMargin;
0425     }
0426 
0427     setBorders(QMargins(left, top, right, bottom));
0428 
0429     // extended sizes
0430     const int extSize = s->largeSpacing();
0431     int extSides = 0;
0432     int extBottom = 0;
0433     if (hasNoBorders()) {
0434         if (!isMaximizedHorizontally()) {
0435             extSides = extSize;
0436         }
0437         if (!isMaximizedVertically()) {
0438             extBottom = extSize;
0439         }
0440 
0441     } else if (hasNoSideBorders() && !isMaximizedHorizontally()) {
0442         extSides = extSize;
0443     }
0444 
0445     setResizeOnlyBorders(QMargins(extSides, 0, extSides, extBottom));
0446 
0447     // Update shadows and clear outline to make sure outline changes when borders are changed
0448     updateShadow();
0449 }
0450 
0451 //________________________________________________________________
0452 void Decoration::createButtons()
0453 {
0454     m_leftButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Left, this, &Button::create);
0455     m_rightButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Right, this, &Button::create);
0456     updateButtonsGeometry();
0457 }
0458 
0459 //________________________________________________________________
0460 void Decoration::updateButtonsGeometryDelayed()
0461 {
0462     QTimer::singleShot(0, this, &Decoration::updateButtonsGeometry);
0463 }
0464 
0465 //________________________________________________________________
0466 void Decoration::updateButtonsGeometry()
0467 {
0468     const auto s = settings();
0469 
0470     // adjust button position
0471     const int bHeight = captionHeight() + (isTopEdge() ? s->smallSpacing() * Metrics::TitleBar_TopMargin : 0);
0472     const int bWidth = buttonHeight();
0473     const int verticalOffset = (isTopEdge() ? s->smallSpacing() * Metrics::TitleBar_TopMargin : 0) + (captionHeight() - buttonHeight()) / 2;
0474     foreach (const QPointer<KDecoration2::DecorationButton> &button, m_leftButtons->buttons() + m_rightButtons->buttons()) {
0475         button.data()->setGeometry(QRectF(QPoint(0, 0), QSizeF(bWidth, bHeight)));
0476         static_cast<Button *>(button.data())->setOffset(QPointF(0, verticalOffset));
0477         static_cast<Button *>(button.data())->setIconSize(QSize(bWidth, bWidth));
0478     }
0479 
0480     // left buttons
0481     if (!m_leftButtons->buttons().isEmpty()) {
0482         // spacing
0483         m_leftButtons->setSpacing(s->smallSpacing() * Metrics::TitleBar_ButtonSpacing);
0484 
0485         // padding
0486         const int vPadding = isTopEdge() ? 0 : s->smallSpacing() * Metrics::TitleBar_TopMargin;
0487         const int hPadding = s->smallSpacing() * Metrics::TitleBar_SideMargin;
0488         if (isLeftEdge()) {
0489             // add offsets on the side buttons, to preserve padding, but satisfy Fitts law
0490             auto button = static_cast<Button *>(m_leftButtons->buttons().front().data());
0491             button->setGeometry(QRectF(QPoint(0, 0), QSizeF(bWidth + hPadding, bHeight)));
0492             button->setFlag(Button::FlagFirstInList);
0493             button->setHorizontalOffset(hPadding);
0494 
0495             m_leftButtons->setPos(QPointF(0, vPadding));
0496 
0497         } else {
0498             m_leftButtons->setPos(QPointF(hPadding + borderLeft(), vPadding));
0499         }
0500     }
0501 
0502     // right buttons
0503     if (!m_rightButtons->buttons().isEmpty()) {
0504         // spacing
0505         m_rightButtons->setSpacing(s->smallSpacing() * Metrics::TitleBar_ButtonSpacing);
0506 
0507         // padding
0508         const int vPadding = isTopEdge() ? 0 : s->smallSpacing() * Metrics::TitleBar_TopMargin;
0509         const int hPadding = s->smallSpacing() * Metrics::TitleBar_SideMargin;
0510         if (isRightEdge()) {
0511             auto button = static_cast<Button *>(m_rightButtons->buttons().back().data());
0512             button->setGeometry(QRectF(QPoint(0, 0), QSizeF(bWidth + hPadding, bHeight)));
0513             button->setFlag(Button::FlagLastInList);
0514 
0515             m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width(), vPadding));
0516 
0517         } else {
0518             m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - hPadding - borderRight(), vPadding));
0519         }
0520     }
0521 
0522     update();
0523 }
0524 
0525 //________________________________________________________________
0526 void Decoration::paint(QPainter *painter, const QRect &repaintRegion)
0527 {
0528     // TODO: optimize based on repaintRegion
0529     auto c = client().toStrongRef();
0530     auto s = settings();
0531 
0532     // paint background
0533     if (!c->isShaded()) {
0534         painter->fillRect(rect(), Qt::transparent);
0535         painter->save();
0536         painter->setRenderHint(QPainter::Antialiasing);
0537         painter->setPen(Qt::NoPen);
0538         painter->setBrush(c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Frame));
0539 
0540         // clip away the top part
0541         if (!hideTitleBar()) {
0542             painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip);
0543         }
0544 
0545         if (s->isAlphaChannelSupported()) {
0546             painter->drawRoundedRect(rect(), m_scaledCornerRadius, m_scaledCornerRadius);
0547         } else {
0548             painter->drawRect(rect());
0549         }
0550 
0551         painter->restore();
0552     }
0553 
0554     if (!hideTitleBar()) {
0555         paintTitleBar(painter, repaintRegion);
0556     }
0557 
0558     if (hasBorders() && !s->isAlphaChannelSupported()) {
0559         painter->save();
0560         painter->setRenderHint(QPainter::Antialiasing, false);
0561         painter->setBrush(Qt::NoBrush);
0562         painter->setPen(c->isActive() ? c->color(ColorGroup::Active, ColorRole::TitleBar) : c->color(ColorGroup::Inactive, ColorRole::Foreground));
0563 
0564         painter->drawRect(rect().adjusted(0, 0, -1, -1));
0565         painter->restore();
0566     }
0567 }
0568 
0569 //________________________________________________________________
0570 void Decoration::paintTitleBar(QPainter *painter, const QRect &repaintRegion)
0571 {
0572     const auto c = client().toStrongRef();
0573     const QRect frontRect(QPoint(0, 0), QSize(size().width(), borderTop()));
0574     const QRect backRect(QPoint(0, 0), QSize(size().width(), borderTop()));
0575 
0576     QBrush frontBrush;
0577     QBrush backBrush(this->titleBarColor());
0578 
0579     if (!backRect.intersects(repaintRegion)) {
0580         return;
0581     }
0582 
0583     painter->save();
0584     painter->setPen(Qt::NoPen);
0585 
0586     // render a linear gradient on title area
0587     if (c->isActive() && m_internalSettings->drawBackgroundGradient()) {
0588         QLinearGradient gradient(0, 0, 0, frontRect.height());
0589         gradient.setColorAt(0.0, titleBarColor().lighter(120));
0590         gradient.setColorAt(0.8, titleBarColor());
0591 
0592         frontBrush = gradient;
0593 
0594     } else {
0595         frontBrush = titleBarColor();
0596 
0597         painter->setBrush(titleBarColor());
0598     }
0599 
0600     auto s = settings();
0601     if (isMaximized() || !s->isAlphaChannelSupported()) {
0602         painter->setBrush(backBrush);
0603         painter->drawRect(backRect);
0604 
0605         painter->setBrush(frontBrush);
0606         painter->drawRect(frontRect);
0607 
0608     } else if (c->isShaded()) {
0609         painter->setBrush(backBrush);
0610         painter->drawRoundedRect(backRect, m_scaledCornerRadius, m_scaledCornerRadius);
0611 
0612         painter->setBrush(frontBrush);
0613         painter->drawRoundedRect(frontRect, m_scaledCornerRadius, m_scaledCornerRadius);
0614 
0615     } else {
0616         painter->setClipRect(backRect, Qt::IntersectClip);
0617 
0618         auto drawThe = [=](const QRect &r) {
0619             // the rect is made a little bit larger to be able to clip away the rounded corners at the bottom and sides
0620             painter->drawRoundedRect(r.adjusted(isLeftEdge() ? -m_scaledCornerRadius : 0,
0621                                                 isTopEdge() ? -m_scaledCornerRadius : 0,
0622                                                 isRightEdge() ? m_scaledCornerRadius : 0,
0623                                                 m_scaledCornerRadius),
0624                                      m_scaledCornerRadius,
0625                                      m_scaledCornerRadius);
0626         };
0627 
0628         painter->setBrush(backBrush);
0629         drawThe(backRect);
0630 
0631         painter->setBrush(frontBrush);
0632         drawThe(frontRect);
0633     }
0634 
0635     painter->restore();
0636 
0637     // draw caption
0638     painter->setFont(s->font());
0639     painter->setPen(fontColor());
0640     const auto cR = captionRect();
0641     const QString caption = painter->fontMetrics().elidedText(c->caption(), Qt::ElideMiddle, cR.first.width());
0642     painter->drawText(cR.first, cR.second | Qt::TextSingleLine, caption);
0643 
0644     // draw all buttons
0645     m_leftButtons->paint(painter, repaintRegion);
0646     m_rightButtons->paint(painter, repaintRegion);
0647 }
0648 
0649 //________________________________________________________________
0650 int Decoration::buttonHeight() const
0651 {
0652     const int baseSize = m_tabletMode ? settings()->gridUnit() * 2 : settings()->gridUnit();
0653     switch (m_internalSettings->buttonSize()) {
0654     case InternalSettings::ButtonTiny:
0655         return baseSize;
0656     case InternalSettings::ButtonSmall:
0657         return baseSize * 1.5;
0658     default:
0659     case InternalSettings::ButtonDefault:
0660         return baseSize * 2;
0661     case InternalSettings::ButtonLarge:
0662         return baseSize * 2.5;
0663     case InternalSettings::ButtonVeryLarge:
0664         return baseSize * 3.5;
0665     }
0666 }
0667 
0668 void Decoration::onTabletModeChanged(bool mode)
0669 {
0670     m_tabletMode = mode;
0671     recalculateBorders();
0672     updateButtonsGeometry();
0673 }
0674 
0675 //________________________________________________________________
0676 int Decoration::captionHeight() const
0677 {
0678     return hideTitleBar() ? borderTop() : borderTop() - settings()->smallSpacing() * (Metrics::TitleBar_BottomMargin + Metrics::TitleBar_TopMargin) - 1;
0679 }
0680 
0681 //________________________________________________________________
0682 QPair<QRect, Qt::Alignment> Decoration::captionRect() const
0683 {
0684     if (hideTitleBar()) {
0685         return qMakePair(QRect(), Qt::AlignCenter);
0686     } else {
0687         auto c = client().toStrongRef();
0688         const int leftOffset = m_leftButtons->buttons().isEmpty()
0689             ? Metrics::TitleBar_SideMargin * settings()->smallSpacing()
0690             : m_leftButtons->geometry().x() + m_leftButtons->geometry().width() + Metrics::TitleBar_SideMargin * settings()->smallSpacing();
0691 
0692         const int rightOffset = m_rightButtons->buttons().isEmpty()
0693             ? Metrics::TitleBar_SideMargin * settings()->smallSpacing()
0694             : size().width() - m_rightButtons->geometry().x() + Metrics::TitleBar_SideMargin * settings()->smallSpacing();
0695 
0696         const int yOffset = settings()->smallSpacing() * Metrics::TitleBar_TopMargin;
0697         const QRect maxRect(leftOffset, yOffset, size().width() - leftOffset - rightOffset, captionHeight());
0698 
0699         switch (m_internalSettings->titleAlignment()) {
0700         case InternalSettings::AlignLeft:
0701             return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignLeft);
0702 
0703         case InternalSettings::AlignRight:
0704             return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignRight);
0705 
0706         case InternalSettings::AlignCenter:
0707             return qMakePair(maxRect, Qt::AlignCenter);
0708 
0709         default:
0710         case InternalSettings::AlignCenterFullWidth: {
0711             // full caption rect
0712             const QRect fullRect = QRect(0, yOffset, size().width(), captionHeight());
0713             QRect boundingRect(settings()->fontMetrics().boundingRect(c->caption()).toRect());
0714 
0715             // text bounding rect
0716             boundingRect.setTop(yOffset);
0717             boundingRect.setHeight(captionHeight());
0718             boundingRect.moveLeft((size().width() - boundingRect.width()) / 2);
0719 
0720             if (boundingRect.left() < leftOffset) {
0721                 return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignLeft);
0722             } else if (boundingRect.right() > size().width() - rightOffset) {
0723                 return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignRight);
0724             } else {
0725                 return qMakePair(fullRect, Qt::AlignCenter);
0726             }
0727         }
0728         }
0729     }
0730 }
0731 
0732 //________________________________________________________________
0733 void Decoration::updateShadow()
0734 {
0735     auto s = settings();
0736     auto c = client().toStrongRef();
0737     auto outlineColor = c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::TitleBar);
0738     auto backgroundColor = c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Frame);
0739     // Bind lightness between 0.1 and 1.0 so it can never be completely black.
0740     // Outlines only have transparency if alpha channel is supported
0741     outlineColor.setHslF(outlineColor.hslHueF(),
0742                          outlineColor.hslSaturationF(),
0743                          qBound(0.1, outlineColor.lightnessF(), 1.0),
0744                          s->isAlphaChannelSupported() ? 0.9 : 1.0);
0745     outlineColor.lightnessF() >= 0.5 ? outlineColor = outlineColor.darker(170) : outlineColor = outlineColor.lighter(170);
0746 
0747     // Animated case, no cached shadow object
0748     if ((m_shadowAnimation->state() == QAbstractAnimation::Running) && (m_shadowOpacity != 0.0) && (m_shadowOpacity != 1.0)) {
0749         setShadow(createShadowObject(0.5 + m_shadowOpacity * 0.5, outlineColor));
0750         return;
0751     }
0752 
0753     if (g_shadowSizeEnum != m_internalSettings->shadowSize() || g_shadowStrength != m_internalSettings->shadowStrength()
0754         || g_shadowColor != m_internalSettings->shadowColor()) {
0755         g_sShadow.clear();
0756         g_sShadowInactive.clear();
0757         g_shadowSizeEnum = m_internalSettings->shadowSize();
0758         g_shadowStrength = m_internalSettings->shadowStrength();
0759         g_shadowColor = m_internalSettings->shadowColor();
0760     }
0761 
0762     auto &shadow = (c->isActive()) ? g_sShadow : g_sShadowInactive;
0763     if (!shadow || g_lastBorderSize != borderSize(true) || g_lastOutlineColor != outlineColor) {
0764         // Update both active and inactive shadows so outline stays consistent between the two
0765         g_sShadow = createShadowObject(1.0, outlineColor);
0766         g_sShadowInactive = createShadowObject(0.5, outlineColor);
0767         g_lastBorderSize = borderSize(true);
0768         g_lastOutlineColor = outlineColor;
0769     }
0770     setShadow(shadow);
0771 }
0772 
0773 //________________________________________________________________
0774 QSharedPointer<KDecoration2::DecorationShadow> Decoration::createShadowObject(const float strengthScale, const QColor &outlineColor)
0775 {
0776     CompositeShadowParams params = lookupShadowParams(m_internalSettings->shadowSize());
0777     if (params.isNone()) {
0778         // If shadows are disabled, set shadow opacity to 0.
0779         // This allows the outline effect to show up without the shadow effect.
0780         params = CompositeShadowParams(QPoint(0, 4), ShadowParams(QPoint(0, 0), 16, 0), ShadowParams(QPoint(0, -2), 8, 0));
0781     }
0782 
0783     auto withOpacity = [](const QColor &color, qreal opacity) -> QColor {
0784         QColor c(color);
0785         c.setAlphaF(opacity);
0786         return c;
0787     };
0788 
0789     const QSize boxSize =
0790         BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius).expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));
0791 
0792     BoxShadowRenderer shadowRenderer;
0793     shadowRenderer.setBorderRadius(m_scaledCornerRadius + 0.5);
0794     shadowRenderer.setBoxSize(boxSize);
0795 
0796     const qreal strength = m_internalSettings->shadowStrength() / 255.0 * strengthScale;
0797     shadowRenderer.addShadow(params.shadow1.offset, params.shadow1.radius, withOpacity(m_internalSettings->shadowColor(), params.shadow1.opacity * strength));
0798     shadowRenderer.addShadow(params.shadow2.offset, params.shadow2.radius, withOpacity(m_internalSettings->shadowColor(), params.shadow2.opacity * strength));
0799 
0800     QImage shadowTexture = shadowRenderer.render();
0801 
0802     QPainter painter(&shadowTexture);
0803     painter.setRenderHint(QPainter::Antialiasing);
0804 
0805     const QRect outerRect = shadowTexture.rect();
0806 
0807     QRect boxRect(QPoint(0, 0), boxSize);
0808     boxRect.moveCenter(outerRect.center());
0809 
0810     // Mask out inner rect.
0811     const QMargins padding = QMargins(boxRect.left() - outerRect.left() - Metrics::Shadow_Overlap - params.offset.x(),
0812                                       boxRect.top() - outerRect.top() - Metrics::Shadow_Overlap - params.offset.y(),
0813                                       outerRect.right() - boxRect.right() - Metrics::Shadow_Overlap + params.offset.x(),
0814                                       outerRect.bottom() - boxRect.bottom() - Metrics::Shadow_Overlap + params.offset.y());
0815     const QRect innerRect = outerRect - padding;
0816 
0817     painter.setPen(Qt::NoPen);
0818     painter.setBrush(Qt::black);
0819     painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0820     painter.drawRoundedRect(innerRect, m_scaledCornerRadius + 0.5, m_scaledCornerRadius + 0.5);
0821 
0822     // Draw window outline
0823     const qreal outlineWidth = 1.001;
0824     const qreal penOffset = outlineWidth / 2;
0825 
0826     QRectF outlineRect = innerRect + QMarginsF(penOffset, penOffset, penOffset, penOffset);
0827     qreal cornerSize = m_scaledCornerRadius * 2;
0828     QRectF cornerRect(outlineRect.x(), outlineRect.y(), cornerSize, cornerSize);
0829     QPainterPath outlinePath;
0830 
0831     outlinePath.arcMoveTo(cornerRect, 180);
0832     outlinePath.arcTo(cornerRect, 180, -90);
0833     cornerRect.moveTopRight(outlineRect.topRight());
0834     outlinePath.arcTo(cornerRect, 90, -90);
0835 
0836     // Check if border size is "no borders" or "no side-borders"
0837     if (borderSize(true) == 0) {
0838         outlinePath.lineTo(outlineRect.bottomRight());
0839         outlinePath.lineTo(outlineRect.bottomLeft());
0840     } else {
0841         cornerRect.moveBottomRight(outlineRect.bottomRight());
0842         outlinePath.arcTo(cornerRect, 0, -90);
0843         cornerRect.moveBottomLeft(outlineRect.bottomLeft());
0844         outlinePath.arcTo(cornerRect, 270, -90);
0845     }
0846     outlinePath.closeSubpath();
0847 
0848     painter.setPen(QPen(outlineColor, outlineWidth));
0849     painter.setBrush(Qt::NoBrush);
0850     painter.setCompositionMode(QPainter::CompositionMode_Source);
0851     painter.setRenderHint(QPainter::Antialiasing);
0852     painter.drawPath(outlinePath);
0853 
0854     painter.end();
0855 
0856     auto ret = QSharedPointer<KDecoration2::DecorationShadow>::create();
0857     ret->setPadding(padding);
0858     ret->setInnerShadowRect(QRect(outerRect.center(), QSize(1, 1)));
0859     ret->setShadow(shadowTexture);
0860     return ret;
0861 }
0862 
0863 void Decoration::setScaledCornerRadius()
0864 {
0865     m_scaledCornerRadius = Metrics::Frame_FrameRadius * settings()->smallSpacing();
0866 }
0867 } // namespace
0868 
0869 #include "breezedecoration.moc"