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"