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

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