From 8a91ef78d5069603369600ca1fc715f782d917fb Mon Sep 17 00:00:00 2001 From: Akseli Lahtinen Date: Thu, 27 Oct 2022 16:52:06 +0000 Subject: [PATCH] Add outlines to window decoration This commit adds outlines to window decorations. This will help users who have a hard time distinguishing overlapping windows from each other, especially with dark themes that can be quite strong and make the shadows difficult to see. The outline takes the background color of the window and inverts it, so it should match any background color pretty well. --- kdecoration/breezedecoration.cpp | 86 ++++++++++++++++++++++++++------ kdecoration/breezedecoration.h | 2 +- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/kdecoration/breezedecoration.cpp b/kdecoration/breezedecoration.cpp index d16dea9d..36e8beff 100644 --- a/kdecoration/breezedecoration.cpp +++ b/kdecoration/breezedecoration.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -134,6 +135,8 @@ static int g_shadowStrength = 255; static QColor g_shadowColor = Qt::black; static QSharedPointer g_sShadow; static QSharedPointer g_sShadowInactive; +static int g_lastBorderSize; +static QColor g_lastOutlineColor; //________________________________________________________________ Decoration::Decoration(QObject *parent, const QVariantList &args) @@ -464,6 +467,9 @@ void Decoration::recalculateBorders() } setResizeOnlyBorders(QMargins(extSides, 0, extSides, extBottom)); + + // Update shadows and clear outline to make sure outline changes when borders are changed + updateShadow(); } //________________________________________________________________ @@ -583,11 +589,11 @@ void Decoration::paint(QPainter *painter, const QRect &repaintRegion) void Decoration::paintTitleBar(QPainter *painter, const QRect &repaintRegion) { const auto c = client().toStrongRef(); - const QRect frontRect(QPoint(0, 1), QSize(size().width(), borderTop() + 1)); - const QRect backRect(QPoint(0, 0), QSize(size().width(), borderTop())); + const QRect frontRect(QPoint(0, 1), QSize(size().width(), borderTop())); + const QRect backRect(QPoint(0, 1), QSize(size().width(), borderTop() - 1)); QBrush frontBrush; - QBrush backBrush(this->titleBarColor().lighter(130)); + QBrush backBrush(this->titleBarColor()); if (!backRect.intersects(repaintRegion)) return; @@ -744,9 +750,29 @@ QPair Decoration::captionRect() const void Decoration::updateShadow() { auto s = settings(); + auto c = client().toStrongRef(); + auto outlineColor = c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::TitleBar); + auto backgroundColor = c->color(c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Frame); + // Bind lightness between 0.1 and 1.0 so it can never be completely black. + // Outlines only have transparency if alpha channel is supported + outlineColor.setHslF(outlineColor.hslHueF(), + outlineColor.hslSaturationF(), + qBound(0.1, outlineColor.lightnessF(), 1.0), + s->isAlphaChannelSupported() ? 0.9 : 1.0); + // If outlinecolor is very close to the window backgroundcolor, the shadow coloring should be enough, + // so we use the background color as the outline as well. + // This is only checked for very light colors, like the default Breeze Light (#e5e6e7) + // If not, we just modify the lightness like usual + if (!lookupShadowParams(m_internalSettings->shadowSize()).isNone() && backgroundColor.lightnessF() >= 0.85 + && (backgroundColor.lightnessF() - outlineColor.lightnessF()) <= 0.05f) { + outlineColor = backgroundColor; + } else { + outlineColor.lightnessF() >= 0.5 ? outlineColor = outlineColor.darker(130) : outlineColor = outlineColor.lighter(130); + } + // Animated case, no cached shadow object if ((m_shadowAnimation->state() == QAbstractAnimation::Running) && (m_shadowOpacity != 0.0) && (m_shadowOpacity != 1.0)) { - setShadow(createShadowObject(0.5 + m_shadowOpacity * 0.5)); + setShadow(createShadowObject(0.5 + m_shadowOpacity * 0.5, outlineColor)); return; } @@ -759,20 +785,25 @@ void Decoration::updateShadow() g_shadowColor = m_internalSettings->shadowColor(); } - auto c = client().toStrongRef(); auto &shadow = (c->isActive()) ? g_sShadow : g_sShadowInactive; - if (!shadow) { - shadow = createShadowObject(c->isActive() ? 1.0 : 0.5); + if (!shadow || g_lastBorderSize != borderSize(true) || g_lastOutlineColor != outlineColor) { + // Update both active and inactive shadows so outline stays consistent between the two + g_sShadow = createShadowObject(1.0, outlineColor); + g_sShadowInactive = createShadowObject(0.5, outlineColor); + g_lastBorderSize = borderSize(true); + g_lastOutlineColor = outlineColor; } setShadow(shadow); } //________________________________________________________________ -QSharedPointer Decoration::createShadowObject(const float strengthScale) +QSharedPointer Decoration::createShadowObject(const float strengthScale, const QColor &outlineColor) { - const CompositeShadowParams params = lookupShadowParams(m_internalSettings->shadowSize()); + CompositeShadowParams params = lookupShadowParams(m_internalSettings->shadowSize()); if (params.isNone()) { - return nullptr; + // If shadows are disabled, set shadow opacity to 0. + // This allows the outline effect to show up without the shadow effect. + params = CompositeShadowParams(QPoint(0, 4), ShadowParams(QPoint(0, 0), 16, 0), ShadowParams(QPoint(0, -2), 8, 0)); } auto withOpacity = [](const QColor &color, qreal opacity) -> QColor { @@ -814,11 +845,38 @@ QSharedPointer Decoration::createShadowObject(co painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); painter.drawRoundedRect(innerRect, m_scaledCornerRadius + 0.5, m_scaledCornerRadius + 0.5); - // Draw outline. - painter.setPen(withOpacity(Qt::black, 0.1)); + // Draw window outline + const qreal outlineWidth = 1.001; + const qreal penOffset = outlineWidth / 2; + + // Titlebar already has an outline, so move the top of the outline on the same level to avoid 2px width on top outline. + QRectF outlineRect = innerRect + QMarginsF(penOffset, -penOffset, penOffset, penOffset); + qreal cornerSize = m_scaledCornerRadius * 2; + QRectF cornerRect(outlineRect.x(), outlineRect.y(), cornerSize, cornerSize); + QPainterPath outlinePath; + + outlinePath.arcMoveTo(cornerRect, 180); + outlinePath.arcTo(cornerRect, 180, -90); + cornerRect.moveTopRight(outlineRect.topRight()); + outlinePath.arcTo(cornerRect, 90, -90); + + // Check if border size is "no borders" or "no side-borders" + if (borderSize(true) == 0) { + outlinePath.lineTo(outlineRect.bottomRight()); + outlinePath.lineTo(outlineRect.bottomLeft()); + } else { + cornerRect.moveBottomRight(outlineRect.bottomRight()); + outlinePath.arcTo(cornerRect, 0, -90); + cornerRect.moveBottomLeft(outlineRect.bottomLeft()); + outlinePath.arcTo(cornerRect, 270, -90); + } + outlinePath.closeSubpath(); + + painter.setPen(QPen(outlineColor, outlineWidth)); painter.setBrush(Qt::NoBrush); - painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - painter.drawRoundedRect(innerRect, m_scaledCornerRadius - 0.5, m_scaledCornerRadius - 0.5); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawPath(outlinePath); painter.end(); diff --git a/kdecoration/breezedecoration.h b/kdecoration/breezedecoration.h index 0abc3ac2..f331e2ae 100644 --- a/kdecoration/breezedecoration.h +++ b/kdecoration/breezedecoration.h @@ -112,7 +112,7 @@ private: void createButtons(); void paintTitleBar(QPainter *painter, const QRect &repaintRegion); void updateShadow(); - QSharedPointer createShadowObject(const float strengthScale); + QSharedPointer createShadowObject(const float strengthScale, const QColor &outlineColor); void setScaledCornerRadius(); //*@name border size