/* * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa * SPDX-FileCopyrightText: 2018, 2020 Vlad Zahorodnii * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "breezeshadowhelper.h" #include "breezemetrics.h" #include "breezeboxshadowrenderer.h" #include "breezehelper.h" #include "breezepropertynames.h" #include "breezestyleconfigdata.h" #include #include #include #include #include #include #include #include #include #include namespace { using Breeze::CompositeShadowParams; using Breeze::ShadowParams; const CompositeShadowParams s_shadowParams[] = { // None CompositeShadowParams(), // Small CompositeShadowParams( QPoint(0, 3), ShadowParams(QPoint(0, 0), 12, 0.26), ShadowParams(QPoint(0, -2), 6, 0.16)), // Medium CompositeShadowParams( QPoint(0, 4), ShadowParams(QPoint(0, 0), 16, 0.24), ShadowParams(QPoint(0, -2), 8, 0.14)), // Large CompositeShadowParams( QPoint(0, 5), ShadowParams(QPoint(0, 0), 20, 0.22), ShadowParams(QPoint(0, -3), 10, 0.12)), // Very Large CompositeShadowParams( QPoint(0, 6), ShadowParams(QPoint(0, 0), 24, 0.2), ShadowParams(QPoint(0, -3), 12, 0.1)) }; } namespace Breeze { //_____________________________________________________ CompositeShadowParams ShadowHelper::lookupShadowParams(int shadowSizeEnum) { switch (shadowSizeEnum) { case StyleConfigData::ShadowNone: return s_shadowParams[0]; case StyleConfigData::ShadowSmall: return s_shadowParams[1]; case StyleConfigData::ShadowMedium: return s_shadowParams[2]; case StyleConfigData::ShadowLarge: return s_shadowParams[3]; case StyleConfigData::ShadowVeryLarge: return s_shadowParams[4]; default: // Fallback to the Large size. return s_shadowParams[3]; } } //_____________________________________________________ ShadowHelper::ShadowHelper( QObject* parent, Helper& helper ): QObject( parent ), _helper( helper ) { } //_______________________________________________________ ShadowHelper::~ShadowHelper() { qDeleteAll( _shadows ); } //______________________________________________ void ShadowHelper::reset() { _tiles.clear(); _shadowTiles = TileSet(); } //_______________________________________________________ bool ShadowHelper::registerWidget( QWidget* widget, bool force ) { // make sure widget is not already registered if( _widgets.contains( widget ) ) return false; // check if widget qualifies if( !( force || acceptWidget( widget ) ) ) { return false; } // try create shadow directly installShadows( widget ); _widgets.insert( widget ); // install event filter widget->removeEventFilter( this ); widget->installEventFilter( this ); // connect destroy signal connect( widget, &QObject::destroyed, this, &ShadowHelper::widgetDeleted ); return true; } //_______________________________________________________ void ShadowHelper::unregisterWidget( QWidget* widget ) { if( _widgets.remove( widget ) ) { // uninstall the event filter widget->removeEventFilter( this ); // disconnect all signals disconnect( widget, nullptr, this, nullptr ); // uninstall the shadow uninstallShadows( widget ); } } //_______________________________________________________ void ShadowHelper::loadConfig() { // reset reset(); // update property for registered widgets for( QWidget* widget : _widgets) { installShadows( widget ); } } //_______________________________________________________ bool ShadowHelper::eventFilter( QObject* object, QEvent* event ) { if( Helper::isX11() ) { // check event type if( event->type() != QEvent::WinIdChange ) return false; // cast widget QWidget* widget( static_cast( object ) ); // install shadows and update winId installShadows( widget ); } else { if( event->type() != QEvent::PlatformSurface ) return false; QWidget* widget( static_cast( object ) ); QPlatformSurfaceEvent* surfaceEvent( static_cast( event ) ); switch( surfaceEvent->surfaceEventType() ) { case QPlatformSurfaceEvent::SurfaceCreated: installShadows( widget ); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: // Don't care. break; } } return false; } //_______________________________________________________ TileSet ShadowHelper::shadowTiles(QWidget *widget) { CompositeShadowParams params = lookupShadowParams(StyleConfigData::shadowSize()); if (params.isNone()) { return TileSet(); } else if (_shadowTiles.isValid()) { return _shadowTiles; } const qreal dpr = devicePixelRatio(widget); params *= dpr; auto withOpacity = [](const QColor &color, qreal opacity) -> QColor { QColor c(color); c.setAlphaF(opacity); return c; }; const QColor color = StyleConfigData::shadowColor(); const qreal strength = static_cast(StyleConfigData::shadowStrength()) / 255.0; const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius) .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius)); const qreal frameRadius = _helper.frameRadius(); BoxShadowRenderer shadowRenderer; shadowRenderer.setBorderRadius(frameRadius); shadowRenderer.setBoxSize(boxSize); shadowRenderer.addShadow(params.shadow1.offset, params.shadow1.radius, withOpacity(color, params.shadow1.opacity * strength)); shadowRenderer.addShadow(params.shadow2.offset, params.shadow2.radius, withOpacity(color, params.shadow2.opacity * strength)); QImage shadowTexture = shadowRenderer.render(); const QRect outerRect(QPoint(0, 0), shadowTexture.size()); QRect boxRect(QPoint(0, 0), boxSize); boxRect.moveCenter(outerRect.center()); // Mask out inner rect. QPainter painter(&shadowTexture); painter.setRenderHint(QPainter::Antialiasing); const QMargins margins = QMargins( boxRect.left() - outerRect.left() - Metrics::Shadow_Overlap - params.offset.x(), boxRect.top() - outerRect.top() - Metrics::Shadow_Overlap - params.offset.y(), outerRect.right() - boxRect.right() - Metrics::Shadow_Overlap + params.offset.x(), outerRect.bottom() - boxRect.bottom() - Metrics::Shadow_Overlap + params.offset.y()); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); painter.drawRoundedRect( outerRect - margins, frameRadius, frameRadius); // We're done. painter.end(); const QPoint innerRectTopLeft = outerRect.center(); _shadowTiles = TileSet( QPixmap::fromImage(shadowTexture), innerRectTopLeft.x(), innerRectTopLeft.y(), 1, 1); return _shadowTiles; } //_______________________________________________________ void ShadowHelper::widgetDeleted( QObject* object ) { QWidget* widget( static_cast( object ) ); _widgets.remove( widget ); } //_______________________________________________________ void ShadowHelper::windowDeleted( QObject* object ) { QWindow* window( static_cast( object ) ); _shadows.remove( window ); } //_______________________________________________________ bool ShadowHelper::isMenu( QWidget* widget ) const { return qobject_cast( widget ); } //_______________________________________________________ bool ShadowHelper::isToolTip( QWidget* widget ) const { return widget->inherits( "QTipLabel" ) || (widget->windowFlags() & Qt::WindowType_Mask) == Qt::ToolTip; } //_______________________________________________________ bool ShadowHelper::isDockWidget( QWidget* widget ) const { return qobject_cast( widget ); } //_______________________________________________________ bool ShadowHelper::isToolBar( QWidget* widget ) const { return qobject_cast( widget ); } //_______________________________________________________ bool ShadowHelper::acceptWidget( QWidget* widget ) const { // flags if( widget->property( PropertyNames::netWMSkipShadow ).toBool() ) return false; if( widget->property( PropertyNames::netWMForceShadow ).toBool() ) return true; // menus if( isMenu( widget ) ) return true; // combobox dropdown lists if( widget->inherits( "QComboBoxPrivateContainer" ) ) return true; // tooltips if( isToolTip( widget ) && !widget->inherits( "Plasma::ToolTip" ) ) { return true; } // detached widgets if( isDockWidget( widget ) || isToolBar( widget ) ) { return true; } // reject return false; } //______________________________________________ const QVector& ShadowHelper::createShadowTiles() { // make sure size is valid if( _tiles.isEmpty() ) { _tiles = { createTile( _shadowTiles.pixmap( 1 ) ), createTile( _shadowTiles.pixmap( 2 ) ), createTile( _shadowTiles.pixmap( 5 ) ), createTile( _shadowTiles.pixmap( 8 ) ), createTile( _shadowTiles.pixmap( 7 ) ), createTile( _shadowTiles.pixmap( 6 ) ), createTile( _shadowTiles.pixmap( 3 ) ), createTile( _shadowTiles.pixmap( 0 ) ) }; } // return relevant list of shadow tiles return _tiles; } //______________________________________________ KWindowShadowTile::Ptr ShadowHelper::createTile( const QPixmap& source ) { KWindowShadowTile::Ptr tile = KWindowShadowTile::Ptr::create(); tile->setImage( source.toImage() ); return tile; } //_______________________________________________________ void ShadowHelper::installShadows( QWidget* widget ) { if( !widget ) return; // only toplevel widgets can cast drop-shadows if( !widget->isWindow() ) return; // widget must have valid native window if( !widget->testAttribute( Qt::WA_WState_Created ) ) return; // create shadow tiles if needed shadowTiles(widget); if( !_shadowTiles.isValid() ) return; // create platform shadow tiles if needed const QVector& tiles = createShadowTiles(); if( tiles.count() != numTiles ) return; // get the underlying window for the widget QWindow* window = widget->windowHandle(); // find a shadow associated with the widget KWindowShadow*& shadow = _shadows[ window ]; if( !shadow ) { // if there is no shadow yet, create one shadow = new KWindowShadow( window ); // connect destroy signal connect( window, &QWindow::destroyed, this, &ShadowHelper::windowDeleted ); } if( shadow->isCreated() ) { shadow->destroy(); } shadow->setTopTile( tiles[ 0 ] ); shadow->setTopRightTile( tiles[ 1 ] ); shadow->setRightTile( tiles[ 2 ] ); shadow->setBottomRightTile( tiles[ 3 ] ); shadow->setBottomTile( tiles[ 4 ] ); shadow->setBottomLeftTile( tiles[ 5 ] ); shadow->setLeftTile( tiles[ 6 ] ); shadow->setTopLeftTile( tiles[ 7 ] ); shadow->setPadding( shadowMargins( widget ) ); shadow->setWindow( window ); shadow->create(); } //_______________________________________________________ QMargins ShadowHelper::shadowMargins( QWidget* widget ) const { CompositeShadowParams params = lookupShadowParams(StyleConfigData::shadowSize()); if (params.isNone()) { return QMargins(); } qreal dpr = devicePixelRatio(widget); params *= dpr; const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius) .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius)); const QSize shadowSize = BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow1.radius, params.shadow1.offset) .expandedTo(BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow2.radius, params.shadow2.offset)); const QRect shadowRect(QPoint(0, 0), shadowSize); QRect boxRect(QPoint(0, 0), boxSize); boxRect.moveCenter(shadowRect.center()); QMargins margins( boxRect.left() - shadowRect.left() - Metrics::Shadow_Overlap - params.offset.x(), boxRect.top() - shadowRect.top() - Metrics::Shadow_Overlap - params.offset.y(), shadowRect.right() - boxRect.right() - Metrics::Shadow_Overlap + params.offset.x(), shadowRect.bottom() - boxRect.bottom() - Metrics::Shadow_Overlap + params.offset.y()); if (widget->inherits("QBalloonTip")) { // Balloon tip needs special margins to deal with the arrow. int top = widget->contentsMargins().top(); int bottom = widget->contentsMargins().bottom(); // Need to decrement default size further due to extra hard coded round corner. margins -= 1; // Arrow can be either to the top or the bottom. Adjust margins accordingly. const int diff = qAbs(top - bottom); if (top > bottom) { margins.setTop(margins.top() - diff); } else { margins.setBottom(margins.bottom() - diff); } } return margins; } //_______________________________________________________ void ShadowHelper::uninstallShadows( QWidget* widget ) { delete _shadows.take( widget->windowHandle() ); } //_______________________________________________________ qreal ShadowHelper::devicePixelRatio(QWidget *widget) { // On Wayland, the compositor will upscale the shadow tiles if necessary. return Helper::isWayland() ? 1 : widget->devicePixelRatioF(); } }