File indexing completed on 2024-05-12 09:30:35
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 0.1; 0118 case Breeze::InternalSettings::OutlineMedium: 0119 return Breeze::Metrics::Bias_Default; 0120 case Breeze::InternalSettings::OutlineHigh: 0121 return 0.4; 0122 case Breeze::InternalSettings::OutlineMaximum: 0123 return 0.6; 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(), buttonHeight()); 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 int bHeight = captionHeight() + (isTopEdge() ? s->smallSpacing() * Metrics::TitleBar_TopMargin : 0); 0490 const int bWidth = buttonHeight(); 0491 const int verticalOffset = (isTopEdge() ? s->smallSpacing() * Metrics::TitleBar_TopMargin : 0) + (captionHeight() - buttonHeight()) / 2; 0492 const auto buttonList = m_leftButtons->buttons() + m_rightButtons->buttons(); 0493 for (const QPointer<KDecoration2::DecorationButton> &button : buttonList) { 0494 button.data()->setGeometry(QRectF(QPoint(0, 0), QSizeF(bWidth, bHeight))); 0495 static_cast<Button *>(button.data())->setOffset(QPointF(0, verticalOffset)); 0496 static_cast<Button *>(button.data())->setIconSize(QSize(bWidth, bWidth)); 0497 } 0498 0499 // left buttons 0500 if (!m_leftButtons->buttons().isEmpty()) { 0501 // spacing 0502 m_leftButtons->setSpacing(s->smallSpacing() * Metrics::TitleBar_ButtonSpacing); 0503 0504 // padding 0505 const int vPadding = isTopEdge() ? 0 : s->smallSpacing() * Metrics::TitleBar_TopMargin; 0506 const int hPadding = s->smallSpacing() * Metrics::TitleBar_SideMargin; 0507 if (isLeftEdge()) { 0508 // add offsets on the side buttons, to preserve padding, but satisfy Fitts law 0509 auto button = static_cast<Button *>(m_leftButtons->buttons().front()); 0510 button->setGeometry(QRectF(QPoint(0, 0), QSizeF(bWidth + hPadding, bHeight))); 0511 button->setFlag(Button::FlagFirstInList); 0512 button->setHorizontalOffset(hPadding); 0513 0514 m_leftButtons->setPos(QPointF(0, vPadding)); 0515 0516 } else { 0517 m_leftButtons->setPos(QPointF(hPadding + borderLeft(), vPadding)); 0518 } 0519 } 0520 0521 // right buttons 0522 if (!m_rightButtons->buttons().isEmpty()) { 0523 // spacing 0524 m_rightButtons->setSpacing(s->smallSpacing() * Metrics::TitleBar_ButtonSpacing); 0525 0526 // padding 0527 const int vPadding = isTopEdge() ? 0 : s->smallSpacing() * Metrics::TitleBar_TopMargin; 0528 const int hPadding = s->smallSpacing() * Metrics::TitleBar_SideMargin; 0529 if (isRightEdge()) { 0530 auto button = static_cast<Button *>(m_rightButtons->buttons().back()); 0531 button->setGeometry(QRectF(QPoint(0, 0), QSizeF(bWidth + hPadding, bHeight))); 0532 button->setFlag(Button::FlagLastInList); 0533 0534 m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width(), vPadding)); 0535 0536 } else { 0537 m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - hPadding - borderRight(), vPadding)); 0538 } 0539 } 0540 0541 update(); 0542 } 0543 0544 //________________________________________________________________ 0545 void Decoration::paint(QPainter *painter, const QRect &repaintRegion) 0546 { 0547 // TODO: optimize based on repaintRegion 0548 auto c = client(); 0549 auto s = settings(); 0550 // paint background 0551 if (!c->isShaded()) { 0552 painter->fillRect(rect(), Qt::transparent); 0553 painter->save(); 0554 painter->setRenderHint(QPainter::Antialiasing); 0555 painter->setPen(Qt::NoPen); 0556 painter->setBrush(c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Frame)); 0557 0558 // clip away the top part 0559 if (!hideTitleBar()) { 0560 painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip); 0561 } 0562 0563 if (s->isAlphaChannelSupported()) { 0564 if (hasNoBorders()) { 0565 painter->drawRoundedRect(rect(), 0, 0); 0566 } else { 0567 painter->drawRoundedRect(rect(), m_scaledCornerRadius, m_scaledCornerRadius); 0568 } 0569 } else { 0570 painter->drawRect(rect()); 0571 } 0572 0573 painter->restore(); 0574 } 0575 0576 if (!hideTitleBar()) { 0577 paintTitleBar(painter, repaintRegion); 0578 } 0579 0580 if (hasBorders() && !s->isAlphaChannelSupported()) { 0581 painter->save(); 0582 painter->setRenderHint(QPainter::Antialiasing, false); 0583 painter->setBrush(Qt::NoBrush); 0584 painter->setPen(c->isActive() ? c->color(ColorGroup::Active, ColorRole::TitleBar) : c->color(ColorGroup::Inactive, ColorRole::Foreground)); 0585 0586 painter->drawRect(rect().adjusted(0, 0, -1, -1)); 0587 painter->restore(); 0588 } 0589 if (outlinesEnabled() && !isMaximized()) { 0590 auto outlineColor = KColorUtils::mix(c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Frame), 0591 c->palette().text().color(), 0592 lookupOutlineIntensity(m_internalSettings->outlineIntensity())); 0593 0594 QRectF outlineRect = rect(); 0595 qreal cornerSize = m_scaledCornerRadius * 2.0; 0596 QRectF cornerRect(outlineRect.x(), outlineRect.y(), cornerSize, cornerSize); 0597 0598 QPainterPath outlinePath; 0599 outlinePath.arcMoveTo(cornerRect, 180); 0600 outlinePath.arcTo(cornerRect, 180, -90); 0601 cornerRect.moveTopRight(outlineRect.topRight()); 0602 outlinePath.arcTo(cornerRect, 90, -90); 0603 // Check if border size is "no borders" or "no side-borders" 0604 if (hasNoBorders()) { 0605 outlinePath.lineTo(outlineRect.bottomRight()); 0606 outlinePath.lineTo(outlineRect.bottomLeft()); 0607 } else { 0608 cornerRect.moveBottomRight(outlineRect.bottomRight()); 0609 outlinePath.arcTo(cornerRect, 0, -90); 0610 cornerRect.moveBottomLeft(outlineRect.bottomLeft()); 0611 outlinePath.arcTo(cornerRect, 270, -90); 0612 } 0613 outlinePath.closeSubpath(); 0614 0615 painter->fillPath(outlinePath.simplified(), Qt::transparent); 0616 painter->save(); 0617 painter->setPen(QPen(outlineColor, 2)); 0618 painter->setBrush(Qt::NoBrush); 0619 painter->setRenderHint(QPainter::Antialiasing); 0620 painter->setCompositionMode(QPainter::CompositionMode_SourceAtop); 0621 painter->drawPath(outlinePath.simplified()); 0622 painter->restore(); 0623 } 0624 } 0625 0626 //________________________________________________________________ 0627 void Decoration::paintTitleBar(QPainter *painter, const QRect &repaintRegion) 0628 { 0629 const auto c = client(); 0630 const QRect frontRect(QPoint(0, 0), QSize(size().width(), borderTop())); 0631 const QRect backRect(QPoint(0, 0), QSize(size().width(), borderTop())); 0632 0633 QBrush frontBrush; 0634 QBrush backBrush(this->titleBarColor()); 0635 0636 if (!backRect.intersects(repaintRegion)) { 0637 return; 0638 } 0639 0640 painter->save(); 0641 painter->setPen(Qt::NoPen); 0642 0643 // render a linear gradient on title area 0644 if (c->isActive() && m_internalSettings->drawBackgroundGradient()) { 0645 QLinearGradient gradient(0, 0, 0, frontRect.height()); 0646 gradient.setColorAt(0.0, titleBarColor().lighter(120)); 0647 gradient.setColorAt(0.8, titleBarColor()); 0648 0649 frontBrush = gradient; 0650 0651 } else { 0652 frontBrush = titleBarColor(); 0653 0654 painter->setBrush(titleBarColor()); 0655 } 0656 0657 auto s = settings(); 0658 if (isMaximized() || !s->isAlphaChannelSupported()) { 0659 painter->setBrush(backBrush); 0660 painter->drawRect(backRect); 0661 0662 painter->setBrush(frontBrush); 0663 painter->drawRect(frontRect); 0664 0665 } else if (c->isShaded()) { 0666 painter->setBrush(backBrush); 0667 painter->drawRoundedRect(backRect, m_scaledCornerRadius, m_scaledCornerRadius); 0668 0669 painter->setBrush(frontBrush); 0670 painter->drawRoundedRect(frontRect, m_scaledCornerRadius, m_scaledCornerRadius); 0671 0672 } else { 0673 painter->setClipRect(backRect, Qt::IntersectClip); 0674 0675 auto drawThe = [=](const QRect &r) { 0676 // the rect is made a little bit larger to be able to clip away the rounded corners at the bottom and sides 0677 painter->drawRoundedRect(r.adjusted(isLeftEdge() ? -m_scaledCornerRadius : 0, 0678 isTopEdge() ? -m_scaledCornerRadius : 0, 0679 isRightEdge() ? m_scaledCornerRadius : 0, 0680 m_scaledCornerRadius), 0681 m_scaledCornerRadius, 0682 m_scaledCornerRadius); 0683 }; 0684 0685 painter->setBrush(backBrush); 0686 drawThe(backRect); 0687 0688 painter->setBrush(frontBrush); 0689 drawThe(frontRect); 0690 } 0691 0692 painter->restore(); 0693 0694 // draw caption 0695 painter->setFont(s->font()); 0696 painter->setPen(fontColor()); 0697 const auto cR = captionRect(); 0698 const QString caption = painter->fontMetrics().elidedText(c->caption(), Qt::ElideMiddle, cR.first.width()); 0699 painter->drawText(cR.first, cR.second | Qt::TextSingleLine, caption); 0700 0701 // draw all buttons 0702 m_leftButtons->paint(painter, repaintRegion); 0703 m_rightButtons->paint(painter, repaintRegion); 0704 } 0705 0706 //________________________________________________________________ 0707 int Decoration::buttonHeight() const 0708 { 0709 const int baseSize = m_tabletMode ? settings()->gridUnit() * 2 : settings()->gridUnit(); 0710 switch (m_internalSettings->buttonSize()) { 0711 case InternalSettings::ButtonTiny: 0712 return baseSize; 0713 case InternalSettings::ButtonSmall: 0714 return baseSize * 1.5; 0715 default: 0716 case InternalSettings::ButtonDefault: 0717 return baseSize * 2; 0718 case InternalSettings::ButtonLarge: 0719 return baseSize * 2.5; 0720 case InternalSettings::ButtonVeryLarge: 0721 return baseSize * 3.5; 0722 } 0723 } 0724 0725 void Decoration::onTabletModeChanged(bool mode) 0726 { 0727 m_tabletMode = mode; 0728 recalculateBorders(); 0729 updateButtonsGeometry(); 0730 } 0731 0732 //________________________________________________________________ 0733 int Decoration::captionHeight() const 0734 { 0735 return hideTitleBar() ? borderTop() : borderTop() - settings()->smallSpacing() * (Metrics::TitleBar_BottomMargin + Metrics::TitleBar_TopMargin) - 1; 0736 } 0737 0738 //________________________________________________________________ 0739 QPair<QRect, Qt::Alignment> Decoration::captionRect() const 0740 { 0741 if (hideTitleBar()) { 0742 return qMakePair(QRect(), Qt::AlignCenter); 0743 } else { 0744 auto c = client(); 0745 const int leftOffset = m_leftButtons->buttons().isEmpty() 0746 ? Metrics::TitleBar_SideMargin * settings()->smallSpacing() 0747 : m_leftButtons->geometry().x() + m_leftButtons->geometry().width() + Metrics::TitleBar_SideMargin * settings()->smallSpacing(); 0748 0749 const int rightOffset = m_rightButtons->buttons().isEmpty() 0750 ? Metrics::TitleBar_SideMargin * settings()->smallSpacing() 0751 : size().width() - m_rightButtons->geometry().x() + Metrics::TitleBar_SideMargin * settings()->smallSpacing(); 0752 0753 const int yOffset = settings()->smallSpacing() * Metrics::TitleBar_TopMargin; 0754 const QRect maxRect(leftOffset, yOffset, size().width() - leftOffset - rightOffset, captionHeight()); 0755 0756 switch (m_internalSettings->titleAlignment()) { 0757 case InternalSettings::AlignLeft: 0758 return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignLeft); 0759 0760 case InternalSettings::AlignRight: 0761 return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignRight); 0762 0763 case InternalSettings::AlignCenter: 0764 return qMakePair(maxRect, Qt::AlignCenter); 0765 0766 default: 0767 case InternalSettings::AlignCenterFullWidth: { 0768 // full caption rect 0769 const QRect fullRect = QRect(0, yOffset, size().width(), captionHeight()); 0770 QRect boundingRect(settings()->fontMetrics().boundingRect(c->caption()).toRect()); 0771 0772 // text bounding rect 0773 boundingRect.setTop(yOffset); 0774 boundingRect.setHeight(captionHeight()); 0775 boundingRect.moveLeft((size().width() - boundingRect.width()) / 2); 0776 0777 if (boundingRect.left() < leftOffset) { 0778 return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignLeft); 0779 } else if (boundingRect.right() > size().width() - rightOffset) { 0780 return qMakePair(maxRect, Qt::AlignVCenter | Qt::AlignRight); 0781 } else { 0782 return qMakePair(fullRect, Qt::AlignCenter); 0783 } 0784 } 0785 } 0786 } 0787 } 0788 0789 //________________________________________________________________ 0790 void Decoration::updateShadow() 0791 { 0792 auto s = settings(); 0793 auto c = client(); 0794 0795 // Animated case, no cached shadow object 0796 if ((m_shadowAnimation->state() == QAbstractAnimation::Running) && (m_shadowOpacity != 0.0) && (m_shadowOpacity != 1.0)) { 0797 setShadow(createShadowObject(0.5 + m_shadowOpacity * 0.5)); 0798 return; 0799 } 0800 0801 if (g_shadowSizeEnum != m_internalSettings->shadowSize() || g_shadowStrength != m_internalSettings->shadowStrength() 0802 || g_shadowColor != m_internalSettings->shadowColor()) { 0803 g_sShadow.reset(); 0804 g_sShadowInactive.reset(); 0805 g_shadowSizeEnum = m_internalSettings->shadowSize(); 0806 g_shadowStrength = m_internalSettings->shadowStrength(); 0807 g_shadowColor = m_internalSettings->shadowColor(); 0808 } 0809 0810 auto &shadow = (c->isActive()) ? g_sShadow : g_sShadowInactive; 0811 if (!shadow || g_lastBorderSize != borderSize(true)) { 0812 // Update both active and inactive shadows so outline stays consistent between the two 0813 g_sShadow = createShadowObject(1.0); 0814 g_sShadowInactive = createShadowObject(0.5); 0815 g_lastBorderSize = borderSize(true); 0816 } 0817 setShadow(shadow); 0818 } 0819 0820 //________________________________________________________________ 0821 std::shared_ptr<KDecoration2::DecorationShadow> Decoration::createShadowObject(const float strengthScale) 0822 { 0823 CompositeShadowParams params = lookupShadowParams(m_internalSettings->shadowSize()); 0824 if (params.isNone()) { 0825 // If shadows are disabled, return nothing 0826 return nullptr; 0827 } 0828 0829 auto withOpacity = [](const QColor &color, qreal opacity) -> QColor { 0830 QColor c(color); 0831 c.setAlphaF(opacity); 0832 return c; 0833 }; 0834 0835 const QSize boxSize = 0836 BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius).expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius)); 0837 0838 BoxShadowRenderer shadowRenderer; 0839 shadowRenderer.setBorderRadius(m_scaledCornerRadius + 0.5); 0840 shadowRenderer.setBoxSize(boxSize); 0841 0842 const qreal strength = m_internalSettings->shadowStrength() / 255.0 * strengthScale; 0843 shadowRenderer.addShadow(params.shadow1.offset, params.shadow1.radius, withOpacity(m_internalSettings->shadowColor(), params.shadow1.opacity * strength)); 0844 shadowRenderer.addShadow(params.shadow2.offset, params.shadow2.radius, withOpacity(m_internalSettings->shadowColor(), params.shadow2.opacity * strength)); 0845 0846 QImage shadowTexture = shadowRenderer.render(); 0847 0848 QPainter painter(&shadowTexture); 0849 painter.setRenderHint(QPainter::Antialiasing); 0850 0851 const QRect outerRect = shadowTexture.rect(); 0852 0853 QRect boxRect(QPoint(0, 0), boxSize); 0854 boxRect.moveCenter(outerRect.center()); 0855 0856 // Mask out inner rect. 0857 const QMargins padding = QMargins(boxRect.left() - outerRect.left() - Metrics::Shadow_Overlap - params.offset.x(), 0858 boxRect.top() - outerRect.top() - Metrics::Shadow_Overlap - params.offset.y(), 0859 outerRect.right() - boxRect.right() - Metrics::Shadow_Overlap + params.offset.x(), 0860 outerRect.bottom() - boxRect.bottom() - Metrics::Shadow_Overlap + params.offset.y()); 0861 QRect innerRect = outerRect - padding; 0862 // Push the shadow slightly under the window, which helps avoiding glitches with fractional scaling 0863 innerRect.adjust(2, 2, -2, -2); 0864 0865 painter.setPen(Qt::NoPen); 0866 painter.setBrush(Qt::black); 0867 painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); 0868 painter.drawRoundedRect(innerRect, m_scaledCornerRadius + 0.5, m_scaledCornerRadius + 0.5); 0869 0870 painter.end(); 0871 0872 auto ret = std::make_shared<KDecoration2::DecorationShadow>(); 0873 ret->setPadding(padding); 0874 ret->setInnerShadowRect(QRect(outerRect.center(), QSize(1, 1))); 0875 ret->setShadow(shadowTexture); 0876 return ret; 0877 } 0878 0879 void Decoration::setScaledCornerRadius() 0880 { 0881 m_scaledCornerRadius = Metrics::Frame_FrameRadius * settings()->smallSpacing(); 0882 } 0883 } // namespace 0884 0885 #include "breezedecoration.moc"