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