diff --git a/kdecoration/CMakeLists.txt b/kdecoration/CMakeLists.txt index 5f8a873d..21ad5684 100644 --- a/kdecoration/CMakeLists.txt +++ b/kdecoration/CMakeLists.txt @@ -1,91 +1,96 @@ add_definitions(-DTRANSLATION_DOMAIN="breeze_kwin_deco") find_package(KF5 REQUIRED COMPONENTS CoreAddons GuiAddons ConfigWidgets WindowSystem I18n) find_package(Qt5 CONFIG REQUIRED COMPONENTS DBus) ### XCB find_package(XCB COMPONENTS XCB) set_package_properties(XCB PROPERTIES DESCRIPTION "X protocol C-language Binding" URL "http://xcb.freedesktop.org" TYPE OPTIONAL PURPOSE "Required to pass style properties to native Windows on X11 Platform" ) if(UNIX AND NOT APPLE) set(BREEZE_HAVE_X11 ${XCB_XCB_FOUND}) if (XCB_XCB_FOUND) find_package(Qt5 REQUIRED CONFIG COMPONENTS X11Extras) endif() else() set(BREEZE_HAVE_X11 FALSE) endif() ################# configuration ################# configure_file(config-breeze.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-breeze.h ) +################# includes ################# +include_directories(${CMAKE_SOURCE_DIR}/libbreezecommon) +include_directories(${CMAKE_BINARY_DIR}/libbreezecommon) + ################# newt target ################# ### plugin classes set(breezedecoration_SRCS breezebutton.cpp breezedecoration.cpp breezeexceptionlist.cpp breezesettingsprovider.cpp breezesizegrip.cpp) kconfig_add_kcfg_files(breezedecoration_SRCS breezesettings.kcfgc) ### config classes ### they are kept separately because they might move in a separate library in the future set(breezedecoration_config_SRCS config/breezeconfigwidget.cpp config/breezedetectwidget.cpp config/breezeexceptiondialog.cpp config/breezeexceptionlistwidget.cpp config/breezeexceptionmodel.cpp config/breezeitemmodel.cpp ) set(breezedecoration_config_PART_FORMS config/ui/breezeconfigurationui.ui config/ui/breezedetectwidget.ui config/ui/breezeexceptiondialog.ui config/ui/breezeexceptionlistwidget.ui ) ki18n_wrap_ui(breezedecoration_config_PART_FORMS_HEADERS ${breezedecoration_config_PART_FORMS}) ### build library add_library(breezedecoration MODULE ${breezedecoration_SRCS} ${breezedecoration_config_SRCS} ${breezedecoration_config_PART_FORMS_HEADERS}) target_link_libraries(breezedecoration PUBLIC Qt5::Core Qt5::Gui Qt5::DBus PRIVATE + breezecommon KDecoration2::KDecoration KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::GuiAddons KF5::I18n KF5::WindowSystem) if(BREEZE_HAVE_X11) target_link_libraries(breezedecoration PUBLIC Qt5::X11Extras XCB::XCB) endif() install(TARGETS breezedecoration DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) install(FILES config/breezedecorationconfig.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/kdecoration/breezedecoration.cpp b/kdecoration/breezedecoration.cpp index 71cdab70..630ce1ba 100644 --- a/kdecoration/breezedecoration.cpp +++ b/kdecoration/breezedecoration.cpp @@ -1,764 +1,838 @@ /* * Copyright 2014 Martin Gräßlin * Copyright 2014 Hugo Pereira Da Costa * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "breezedecoration.h" #include "breeze.h" #include "breezesettingsprovider.h" #include "config-breeze.h" #include "config/breezeconfigwidget.h" #include "breezebutton.h" #include "breezesizegrip.h" +#include "breezeboxshadowhelper.h" + #include #include #include #include #include #include #include #include +#include #include +#include #include #include #if BREEZE_HAVE_X11 #include #endif #include K_PLUGIN_FACTORY_WITH_JSON( BreezeDecoFactory, "breeze.json", registerPlugin(); registerPlugin(QStringLiteral("button")); registerPlugin(QStringLiteral("kcmodule")); ) +namespace +{ + struct ShadowParams { + ShadowParams() + : offset(QPoint(0, 0)) + , radius(0) + , opacity(0) {} + + ShadowParams(const QPoint &offset, int radius, qreal opacity) + : offset(offset) + , radius(radius) + , opacity(opacity) {} + + QPoint offset; + int radius; + qreal opacity; + }; + + struct CompositeShadowParams { + CompositeShadowParams() = default; + + CompositeShadowParams( + const QPoint &offset, + const ShadowParams &shadow1, + const ShadowParams &shadow2) + : offset(offset) + , shadow1(shadow1) + , shadow2(shadow2) {} + + bool isNone() const { + return qMax(shadow1.radius, shadow2.radius) == 0; + } + + QPoint offset; + ShadowParams shadow1; + ShadowParams shadow2; + }; + + const CompositeShadowParams s_shadowParams[] = { + // None + CompositeShadowParams(), + // Small + CompositeShadowParams( + QPoint(0, 6), + ShadowParams(QPoint(0, 0), 16, 0.9), + ShadowParams(QPoint(0, 0), 4, 0.1)), + // Medium + CompositeShadowParams( + QPoint(0, 12), + ShadowParams(QPoint(0, 0), 32, 0.94), + ShadowParams(QPoint(0, 0), 16, 0.1)), + // Large + CompositeShadowParams( + QPoint(0, 20), + ShadowParams(QPoint(0, 0), 64, 1.0), + ShadowParams(QPoint(0, 0), 24, 0.1)), + // Very large + CompositeShadowParams( + QPoint(0, 34), + ShadowParams(QPoint(0, 0), 96, 1.0), + ShadowParams(QPoint(0, 0), 28, 0.1)) + }; + + inline CompositeShadowParams lookupShadowParams(int size) + { + switch (size) { + case Breeze::InternalSettings::ShadowNone: + return s_shadowParams[0]; + case Breeze::InternalSettings::ShadowSmall: + return s_shadowParams[1]; + case Breeze::InternalSettings::ShadowMedium: + return s_shadowParams[2]; + case Breeze::InternalSettings::ShadowLarge: + return s_shadowParams[3]; + case Breeze::InternalSettings::ShadowVeryLarge: + return s_shadowParams[4]; + default: + // Fallback to the Large size. + return s_shadowParams[3]; + } + } +} + namespace Breeze { using KDecoration2::ColorRole; using KDecoration2::ColorGroup; //________________________________________________________________ static int g_sDecoCount = 0; static int g_shadowSizeEnum = InternalSettings::ShadowLarge; - static int g_shadowStrength = 90; + static int g_shadowStrength = 255; static QColor g_shadowColor = Qt::black; static QSharedPointer g_sShadow; //________________________________________________________________ Decoration::Decoration(QObject *parent, const QVariantList &args) : KDecoration2::Decoration(parent, args) , m_animation( new QPropertyAnimation( this ) ) { g_sDecoCount++; } //________________________________________________________________ Decoration::~Decoration() { g_sDecoCount--; if (g_sDecoCount == 0) { // last deco destroyed, clean up shadow g_sShadow.clear(); } deleteSizeGrip(); } //________________________________________________________________ void Decoration::setOpacity( qreal value ) { if( m_opacity == value ) return; m_opacity = value; update(); if( m_sizeGrip ) m_sizeGrip->update(); } //________________________________________________________________ QColor Decoration::titleBarColor() const { auto c = client().data(); if( hideTitleBar() ) return c->color( ColorGroup::Inactive, ColorRole::TitleBar ); else if( m_animation->state() == QPropertyAnimation::Running ) { return KColorUtils::mix( c->color( ColorGroup::Inactive, ColorRole::TitleBar ), c->color( ColorGroup::Active, ColorRole::TitleBar ), m_opacity ); } else return c->color( c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::TitleBar ); } //________________________________________________________________ QColor Decoration::outlineColor() const { auto c( client().data() ); if( !m_internalSettings->drawTitleBarSeparator() ) return QColor(); if( m_animation->state() == QPropertyAnimation::Running ) { QColor color( c->palette().color( QPalette::Highlight ) ); color.setAlpha( color.alpha()*m_opacity ); return color; } else if( c->isActive() ) return c->palette().color( QPalette::Highlight ); else return QColor(); } //________________________________________________________________ QColor Decoration::fontColor() const { auto c = client().data(); if( m_animation->state() == QPropertyAnimation::Running ) { return KColorUtils::mix( c->color( ColorGroup::Inactive, ColorRole::Foreground ), c->color( ColorGroup::Active, ColorRole::Foreground ), m_opacity ); } else return c->color( c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Foreground ); } //________________________________________________________________ void Decoration::init() { auto c = client().data(); // active state change animation m_animation->setStartValue( 0 ); m_animation->setEndValue( 1.0 ); m_animation->setTargetObject( this ); m_animation->setPropertyName( "opacity" ); m_animation->setEasingCurve( QEasingCurve::InOutQuad ); reconfigure(); updateTitleBar(); auto s = settings(); connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, this, &Decoration::recalculateBorders); // a change in font might cause the borders to change connect(s.data(), &KDecoration2::DecorationSettings::fontChanged, this, &Decoration::recalculateBorders); connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::recalculateBorders); // buttons connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::updateButtonsGeometryDelayed); connect(s.data(), &KDecoration2::DecorationSettings::decorationButtonsLeftChanged, this, &Decoration::updateButtonsGeometryDelayed); connect(s.data(), &KDecoration2::DecorationSettings::decorationButtonsRightChanged, this, &Decoration::updateButtonsGeometryDelayed); // full reconfiguration connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::reconfigure); connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, SettingsProvider::self(), &SettingsProvider::reconfigure, Qt::UniqueConnection ); connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::updateButtonsGeometryDelayed); connect(c, &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::recalculateBorders); connect(c, &KDecoration2::DecoratedClient::maximizedHorizontallyChanged, this, &Decoration::recalculateBorders); connect(c, &KDecoration2::DecoratedClient::maximizedVerticallyChanged, this, &Decoration::recalculateBorders); connect(c, &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::recalculateBorders); connect(c, &KDecoration2::DecoratedClient::captionChanged, this, [this]() { // update the caption area update(titleBar()); } ); connect(c, &KDecoration2::DecoratedClient::activeChanged, this, &Decoration::updateAnimationState); connect(c, &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateTitleBar); connect(c, &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateTitleBar); connect(c, &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::setOpaque); connect(c, &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateButtonsGeometry); connect(c, &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateButtonsGeometry); connect(c, &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::updateButtonsGeometry); connect(c, &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateButtonsGeometry); createButtons(); createShadow(); } //________________________________________________________________ void Decoration::updateTitleBar() { auto s = settings(); auto c = client().data(); const bool maximized = isMaximized(); const int width = maximized ? c->width() : c->width() - 2*s->largeSpacing()*Metrics::TitleBar_SideMargin; const int height = maximized ? borderTop() : borderTop() - s->smallSpacing()*Metrics::TitleBar_TopMargin; const int x = maximized ? 0 : s->largeSpacing()*Metrics::TitleBar_SideMargin; const int y = maximized ? 0 : s->smallSpacing()*Metrics::TitleBar_TopMargin; setTitleBar(QRect(x, y, width, height)); } //________________________________________________________________ void Decoration::updateAnimationState() { if( m_internalSettings->animationsEnabled() ) { auto c = client().data(); m_animation->setDirection( c->isActive() ? QPropertyAnimation::Forward : QPropertyAnimation::Backward ); if( m_animation->state() != QPropertyAnimation::Running ) m_animation->start(); } else { update(); } } //________________________________________________________________ void Decoration::updateSizeGripVisibility() { auto c = client().data(); if( m_sizeGrip ) { m_sizeGrip->setVisible( c->isResizeable() && !isMaximized() && !c->isShaded() ); } } //________________________________________________________________ int Decoration::borderSize(bool bottom) const { const int baseSize = settings()->smallSpacing(); if( m_internalSettings && (m_internalSettings->mask() & BorderSize ) ) { switch (m_internalSettings->borderSize()) { case InternalSettings::BorderNone: return 0; case InternalSettings::BorderNoSides: return bottom ? qMax(4, baseSize) : 0; default: case InternalSettings::BorderTiny: return bottom ? qMax(4, baseSize) : baseSize; case InternalSettings::BorderNormal: return baseSize*2; case InternalSettings::BorderLarge: return baseSize*3; case InternalSettings::BorderVeryLarge: return baseSize*4; case InternalSettings::BorderHuge: return baseSize*5; case InternalSettings::BorderVeryHuge: return baseSize*6; case InternalSettings::BorderOversized: return baseSize*10; } } else { switch (settings()->borderSize()) { case KDecoration2::BorderSize::None: return 0; case KDecoration2::BorderSize::NoSides: return bottom ? qMax(4, baseSize) : 0; default: case KDecoration2::BorderSize::Tiny: return bottom ? qMax(4, baseSize) : baseSize; case KDecoration2::BorderSize::Normal: return baseSize*2; case KDecoration2::BorderSize::Large: return baseSize*3; case KDecoration2::BorderSize::VeryLarge: return baseSize*4; case KDecoration2::BorderSize::Huge: return baseSize*5; case KDecoration2::BorderSize::VeryHuge: return baseSize*6; case KDecoration2::BorderSize::Oversized: return baseSize*10; } } } //________________________________________________________________ void Decoration::reconfigure() { m_internalSettings = SettingsProvider::self()->internalSettings( this ); // animation m_animation->setDuration( m_internalSettings->animationsDuration() ); // borders recalculateBorders(); // shadow createShadow(); // size grip if( hasNoBorders() && m_internalSettings->drawSizeGrip() ) createSizeGrip(); else deleteSizeGrip(); } //________________________________________________________________ void Decoration::recalculateBorders() { auto c = client().data(); auto s = settings(); // left, right and bottom borders const int left = isLeftEdge() ? 0 : borderSize(); const int right = isRightEdge() ? 0 : borderSize(); const int bottom = (c->isShaded() || isBottomEdge()) ? 0 : borderSize(true); int top = 0; if( hideTitleBar() ) top = bottom; else { QFontMetrics fm(s->font()); top += qMax(fm.height(), buttonHeight() ); // padding below // extra pixel is used for the active window outline const int baseSize = s->smallSpacing(); top += baseSize*Metrics::TitleBar_BottomMargin + 1; // padding above top += baseSize*TitleBar_TopMargin; } setBorders(QMargins(left, top, right, bottom)); // extended sizes const int extSize = s->largeSpacing(); int extSides = 0; int extBottom = 0; if( hasNoBorders() ) { extSides = extSize; extBottom = extSize; } else if( hasNoSideBorders() ) { extSides = extSize; } setResizeOnlyBorders(QMargins(extSides, 0, extSides, extBottom)); } //________________________________________________________________ void Decoration::createButtons() { m_leftButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Left, this, &Button::create); m_rightButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Right, this, &Button::create); updateButtonsGeometry(); } //________________________________________________________________ void Decoration::updateButtonsGeometryDelayed() { QTimer::singleShot( 0, this, &Decoration::updateButtonsGeometry ); } //________________________________________________________________ void Decoration::updateButtonsGeometry() { const auto s = settings(); // adjust button position const int bHeight = captionHeight() + (isTopEdge() ? s->smallSpacing()*Metrics::TitleBar_TopMargin:0); const int bWidth = buttonHeight(); const int verticalOffset = (isTopEdge() ? s->smallSpacing()*Metrics::TitleBar_TopMargin:0) + (captionHeight()-buttonHeight())/2; foreach( const QPointer& button, m_leftButtons->buttons() + m_rightButtons->buttons() ) { button.data()->setGeometry( QRectF( QPoint( 0, 0 ), QSizeF( bWidth, bHeight ) ) ); static_cast( button.data() )->setOffset( QPointF( 0, verticalOffset ) ); static_cast( button.data() )->setIconSize( QSize( bWidth, bWidth ) ); } // left buttons if( !m_leftButtons->buttons().isEmpty() ) { // spacing m_leftButtons->setSpacing(s->smallSpacing()*Metrics::TitleBar_ButtonSpacing); // padding const int vPadding = isTopEdge() ? 0 : s->smallSpacing()*Metrics::TitleBar_TopMargin; const int hPadding = s->smallSpacing()*Metrics::TitleBar_SideMargin; if( isLeftEdge() ) { // add offsets on the side buttons, to preserve padding, but satisfy Fitts law auto button = static_cast( m_leftButtons->buttons().front().data() ); button->setGeometry( QRectF( QPoint( 0, 0 ), QSizeF( bWidth + hPadding, bHeight ) ) ); button->setFlag( Button::FlagFirstInList ); button->setHorizontalOffset( hPadding ); m_leftButtons->setPos(QPointF(0, vPadding)); } else m_leftButtons->setPos(QPointF(hPadding + borderLeft(), vPadding)); } // right buttons if( !m_rightButtons->buttons().isEmpty() ) { // spacing m_rightButtons->setSpacing(s->smallSpacing()*Metrics::TitleBar_ButtonSpacing); // padding const int vPadding = isTopEdge() ? 0 : s->smallSpacing()*Metrics::TitleBar_TopMargin; const int hPadding = s->smallSpacing()*Metrics::TitleBar_SideMargin; if( isRightEdge() ) { auto button = static_cast( m_rightButtons->buttons().back().data() ); button->setGeometry( QRectF( QPoint( 0, 0 ), QSizeF( bWidth + hPadding, bHeight ) ) ); button->setFlag( Button::FlagLastInList ); m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width(), vPadding)); } else m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - hPadding - borderRight(), vPadding)); } update(); } //________________________________________________________________ void Decoration::paint(QPainter *painter, const QRect &repaintRegion) { // TODO: optimize based on repaintRegion auto c = client().data(); auto s = settings(); // paint background if( !c->isShaded() ) { painter->fillRect(rect(), Qt::transparent); painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->setPen(Qt::NoPen); painter->setBrush( c->color( c->isActive() ? ColorGroup::Active : ColorGroup::Inactive, ColorRole::Frame ) ); // clip away the top part if( !hideTitleBar() ) painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip); if( s->isAlphaChannelSupported() ) painter->drawRoundedRect(rect(), Metrics::Frame_FrameRadius, Metrics::Frame_FrameRadius); else painter->drawRect( rect() ); painter->restore(); } if( !hideTitleBar() ) paintTitleBar(painter, repaintRegion); if( hasBorders() && !s->isAlphaChannelSupported() ) { painter->save(); painter->setRenderHint(QPainter::Antialiasing, false); painter->setBrush( Qt::NoBrush ); painter->setPen( c->isActive() ? c->color( ColorGroup::Active, ColorRole::TitleBar ): c->color( ColorGroup::Inactive, ColorRole::Foreground ) ); painter->drawRect( rect().adjusted( 0, 0, -1, -1 ) ); painter->restore(); } } //________________________________________________________________ void Decoration::paintTitleBar(QPainter *painter, const QRect &repaintRegion) { const auto c = client().data(); const QRect titleRect(QPoint(0, 0), QSize(size().width(), borderTop())); if ( !titleRect.intersects(repaintRegion) ) return; painter->save(); painter->setPen(Qt::NoPen); // render a linear gradient on title area if( c->isActive() && m_internalSettings->drawBackgroundGradient() ) { const QColor titleBarColor( this->titleBarColor() ); QLinearGradient gradient( 0, 0, 0, titleRect.height() ); gradient.setColorAt(0.0, titleBarColor.lighter( 120 ) ); gradient.setColorAt(0.8, titleBarColor); painter->setBrush(gradient); } else { painter->setBrush( titleBarColor() ); } auto s = settings(); if( isMaximized() || !s->isAlphaChannelSupported() ) { painter->drawRect(titleRect); } else if( c->isShaded() ) { painter->drawRoundedRect(titleRect, Metrics::Frame_FrameRadius, Metrics::Frame_FrameRadius); } else { painter->setClipRect(titleRect, Qt::IntersectClip); // the rect is made a little bit larger to be able to clip away the rounded corners at the bottom and sides painter->drawRoundedRect(titleRect.adjusted( isLeftEdge() ? -Metrics::Frame_FrameRadius:0, isTopEdge() ? -Metrics::Frame_FrameRadius:0, isRightEdge() ? Metrics::Frame_FrameRadius:0, Metrics::Frame_FrameRadius), Metrics::Frame_FrameRadius, Metrics::Frame_FrameRadius); } const QColor outlineColor( this->outlineColor() ); if( !c->isShaded() && outlineColor.isValid() ) { // outline painter->setRenderHint( QPainter::Antialiasing, false ); painter->setBrush( Qt::NoBrush ); painter->setPen( outlineColor ); painter->drawLine( titleRect.bottomLeft(), titleRect.bottomRight() ); } painter->restore(); // draw caption painter->setFont(s->font()); painter->setPen( fontColor() ); const auto cR = captionRect(); const QString caption = painter->fontMetrics().elidedText(c->caption(), Qt::ElideMiddle, cR.first.width()); painter->drawText(cR.first, cR.second | Qt::TextSingleLine, caption); // draw all buttons m_leftButtons->paint(painter, repaintRegion); m_rightButtons->paint(painter, repaintRegion); } //________________________________________________________________ int Decoration::buttonHeight() const { const int baseSize = settings()->gridUnit(); switch( m_internalSettings->buttonSize() ) { case InternalSettings::ButtonTiny: return baseSize; case InternalSettings::ButtonSmall: return baseSize*1.5; default: case InternalSettings::ButtonDefault: return baseSize*2; case InternalSettings::ButtonLarge: return baseSize*2.5; case InternalSettings::ButtonVeryLarge: return baseSize*3.5; } } //________________________________________________________________ int Decoration::captionHeight() const { return hideTitleBar() ? borderTop() : borderTop() - settings()->smallSpacing()*(Metrics::TitleBar_BottomMargin + Metrics::TitleBar_TopMargin ) - 1; } //________________________________________________________________ QPair Decoration::captionRect() const { if( hideTitleBar() ) return qMakePair( QRect(), Qt::AlignCenter ); else { auto c = client().data(); const int leftOffset = m_leftButtons->buttons().isEmpty() ? Metrics::TitleBar_SideMargin*settings()->smallSpacing(): m_leftButtons->geometry().x() + m_leftButtons->geometry().width() + Metrics::TitleBar_SideMargin*settings()->smallSpacing(); const int rightOffset = m_rightButtons->buttons().isEmpty() ? Metrics::TitleBar_SideMargin*settings()->smallSpacing() : size().width() - m_rightButtons->geometry().x() + Metrics::TitleBar_SideMargin*settings()->smallSpacing(); const int yOffset = settings()->smallSpacing()*Metrics::TitleBar_TopMargin; const QRect maxRect( leftOffset, yOffset, size().width() - leftOffset - rightOffset, captionHeight() ); switch( m_internalSettings->titleAlignment() ) { case InternalSettings::AlignLeft: return qMakePair( maxRect, Qt::AlignVCenter|Qt::AlignLeft ); case InternalSettings::AlignRight: return qMakePair( maxRect, Qt::AlignVCenter|Qt::AlignRight ); case InternalSettings::AlignCenter: return qMakePair( maxRect, Qt::AlignCenter ); default: case InternalSettings::AlignCenterFullWidth: { // full caption rect const QRect fullRect = QRect( 0, yOffset, size().width(), captionHeight() ); QRect boundingRect( settings()->fontMetrics().boundingRect( c->caption()).toRect() ); // text bounding rect boundingRect.setTop( yOffset ); boundingRect.setHeight( captionHeight() ); boundingRect.moveLeft( ( size().width() - boundingRect.width() )/2 ); if( boundingRect.left() < leftOffset ) return qMakePair( maxRect, Qt::AlignVCenter|Qt::AlignLeft ); else if( boundingRect.right() > size().width() - rightOffset ) return qMakePair( maxRect, Qt::AlignVCenter|Qt::AlignRight ); else return qMakePair(fullRect, Qt::AlignCenter); } } } } //________________________________________________________________ void Decoration::createShadow() { - - // assign global shadow if exists and parameters match - if( - !g_sShadow || - g_shadowSizeEnum != m_internalSettings->shadowSize() || - g_shadowStrength != m_internalSettings->shadowStrength() || - g_shadowColor != m_internalSettings->shadowColor() - ) + if (!g_sShadow + ||g_shadowSizeEnum != m_internalSettings->shadowSize() + || g_shadowStrength != m_internalSettings->shadowStrength() + || g_shadowColor != m_internalSettings->shadowColor()) { - // assign parameters g_shadowSizeEnum = m_internalSettings->shadowSize(); g_shadowStrength = m_internalSettings->shadowStrength(); g_shadowColor = m_internalSettings->shadowColor(); - // shadow size from enum - int shadowSize = 0; - switch( g_shadowSizeEnum ) - { - default: - case InternalSettings::ShadowLarge: shadowSize = 64; break; - - case InternalSettings::ShadowNone: shadowSize = Metrics::Shadow_Overlap + 1; break; - case InternalSettings::ShadowSmall: shadowSize = 16; break; - case InternalSettings::ShadowMedium: shadowSize = 32; break; - case InternalSettings::ShadowVeryLarge: shadowSize = 96; break; + const CompositeShadowParams params = lookupShadowParams(g_shadowSizeEnum); + if (params.isNone()) { + g_sShadow.clear(); + setShadow(g_sShadow); + return; } - // offset - int shadowOffset = (g_shadowSizeEnum == InternalSettings::ShadowNone) ? 0 : qMax( 6*shadowSize/16, Metrics::Shadow_Overlap*2 ); - - // create image - QImage image(2*shadowSize, 2*shadowSize, QImage::Format_ARGB32_Premultiplied); - image.fill(Qt::transparent); - - // painter - QPainter painter(&image); - painter.setRenderHint( QPainter::Antialiasing, true ); - - // color calculation delta function - auto gradientStopColor = [](QColor color, int alpha) - { - color.setAlpha(alpha); - return color; + auto withOpacity = [](const QColor &color, qreal opacity) -> QColor { + QColor c(color); + c.setAlphaF(opacity); + return c; }; - // create gradient - if( g_shadowSizeEnum != InternalSettings::ShadowNone ) - { - - // gaussian lambda function - auto alpha = [](qreal x) { return std::exp( -x*x/0.15 ); }; - - QRadialGradient radialGradient( shadowSize, shadowSize, shadowSize ); - for( int i = 0; i < 10; ++i ) - { - const qreal x( qreal( i )/9 ); - radialGradient.setColorAt(x, gradientStopColor( g_shadowColor, alpha(x)*g_shadowStrength ) ); - } - - radialGradient.setColorAt(1, gradientStopColor( g_shadowColor, 0 ) ); - - // fill - painter.fillRect( image.rect(), radialGradient); - - } - - // contrast pixel - QRectF innerRect = QRectF( - shadowSize - Metrics::Shadow_Overlap, shadowSize - shadowOffset - Metrics::Shadow_Overlap, - 2*Metrics::Shadow_Overlap, shadowOffset + 2*Metrics::Shadow_Overlap ); - - painter.setPen( gradientStopColor( g_shadowColor, (g_shadowSizeEnum == InternalSettings::ShadowNone) ? g_shadowStrength:(g_shadowStrength*0.5) ) ); - painter.setBrush( Qt::NoBrush ); - painter.drawRoundedRect( innerRect, -0.5 + Metrics::Frame_FrameRadius, -0.5 + Metrics::Frame_FrameRadius ); - - // mask out inner rect - painter.setPen( Qt::NoPen ); - painter.setBrush( Qt::black ); - painter.setCompositionMode(QPainter::CompositionMode_DestinationOut ); - painter.drawRoundedRect( innerRect, 0.5 + Metrics::Frame_FrameRadius, 0.5 + Metrics::Frame_FrameRadius ); + // In order to properly render a box shadow with a given radius `shadowSize`, + // the box size should be at least `2 * QSize(shadowSize, shadowSize)`. + const int shadowSize = qMax(params.shadow1.radius, params.shadow2.radius); + const QRect box(shadowSize, shadowSize, 2 * shadowSize + 1, 2 * shadowSize + 1); + const QRect rect = box.adjusted(-shadowSize, -shadowSize, shadowSize, shadowSize); + + QImage shadow(rect.size(), QImage::Format_ARGB32_Premultiplied); + shadow.fill(Qt::transparent); + + QPainter painter(&shadow); + painter.setRenderHint(QPainter::Antialiasing); + + const qreal strength = static_cast(g_shadowStrength) / 255.0; + + // Draw the "shape" shadow. + BoxShadowHelper::boxShadow( + &painter, + box, + params.shadow1.offset, + params.shadow1.radius, + withOpacity(g_shadowColor, params.shadow1.opacity * strength)); + + // Draw the "contrast" shadow. + BoxShadowHelper::boxShadow( + &painter, + box, + params.shadow2.offset, + params.shadow2.radius, + withOpacity(g_shadowColor, params.shadow2.opacity * strength)); + + // Mask out inner rect. + const QMargins padding = QMargins( + shadowSize - Metrics::Shadow_Overlap - params.offset.x(), + shadowSize - Metrics::Shadow_Overlap - params.offset.y(), + shadowSize - Metrics::Shadow_Overlap + params.offset.x(), + shadowSize - Metrics::Shadow_Overlap + params.offset.y()); + const QRect innerRect = rect - padding; + + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::black); + painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); + painter.drawRoundedRect( + innerRect, + Metrics::Frame_FrameRadius + 0.5, + Metrics::Frame_FrameRadius + 0.5); + + // Draw outline. + painter.setPen(withOpacity(g_shadowColor, 0.2 * strength)); + painter.setBrush(Qt::NoBrush); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + painter.drawRoundedRect( + innerRect, + Metrics::Frame_FrameRadius - 0.5, + Metrics::Frame_FrameRadius - 0.5); painter.end(); g_sShadow = QSharedPointer::create(); - g_sShadow->setPadding( QMargins( - shadowSize - Metrics::Shadow_Overlap, - shadowSize - shadowOffset - Metrics::Shadow_Overlap, - shadowSize - Metrics::Shadow_Overlap, - shadowSize - Metrics::Shadow_Overlap ) ); - - g_sShadow->setInnerShadowRect(QRect( shadowSize, shadowSize, 1, 1) ); - - // assign image - g_sShadow->setShadow(image); - + g_sShadow->setPadding(padding); + g_sShadow->setInnerShadowRect(QRect(shadow.rect().center(), QSize(1, 1))); + g_sShadow->setShadow(shadow); } setShadow(g_sShadow); - } //_________________________________________________________________ void Decoration::createSizeGrip() { // do nothing if size grip already exist if( m_sizeGrip ) return; #if BREEZE_HAVE_X11 if( !QX11Info::isPlatformX11() ) return; // access client auto c = client().data(); if( !c ) return; if( c->windowId() != 0 ) { m_sizeGrip = new SizeGrip( this ); connect( c, &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateSizeGripVisibility ); connect( c, &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateSizeGripVisibility ); connect( c, &KDecoration2::DecoratedClient::resizeableChanged, this, &Decoration::updateSizeGripVisibility ); } #endif } //_________________________________________________________________ void Decoration::deleteSizeGrip() { if( m_sizeGrip ) { m_sizeGrip->deleteLater(); m_sizeGrip = nullptr; } } } // namespace #include "breezedecoration.moc" diff --git a/kdecoration/breezesettingsdata.kcfg b/kdecoration/breezesettingsdata.kcfg index 4b4ddf76..28cef717 100644 --- a/kdecoration/breezesettingsdata.kcfg +++ b/kdecoration/breezesettingsdata.kcfg @@ -1,137 +1,137 @@ - 90 + 255 25 255 ShadowLarge 35, 38, 41 true - BorderNoSides AlignCenterFullWidth ButtonDefault false true true false true 150 false ExceptionWindowClassName true 0