/* * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "breezehelper.h" #include "breeze.h" #include "breezestyleconfigdata.h" #include #include #include #include #include #include #include #include #include #include #include #include #if BREEZE_HAVE_QTX11EXTRAS #include #endif #include #include namespace Breeze { //* contrast for arrow and treeline rendering static const qreal arrowShade = 0.15; static const auto radioCheckSunkenDarkeningFactor = 110; //____________________________________________________________________ Helper::Helper( KSharedConfig::Ptr config, QObject *parent ) : QObject ( parent ), _config( std::move( config ) ), _kwinConfig( KSharedConfig::openConfig("kwinrc") ), _decorationConfig( new InternalSettings() ) { if (qApp) { connect(qApp, &QApplication::paletteChanged, this, [=]() { if (qApp->property("KDE_COLOR_SCHEME_PATH").isValid()) { const auto path = qApp->property("KDE_COLOR_SCHEME_PATH").toString(); KConfig config(path, KConfig::SimpleConfig); KConfigGroup group( config.group("WM") ); const QPalette palette( QApplication::palette() ); _activeTitleBarColor = group.readEntry( "activeBackground", palette.color( QPalette::Active, QPalette::Highlight ) ); _activeTitleBarTextColor = group.readEntry( "activeForeground", palette.color( QPalette::Active, QPalette::HighlightedText ) ); _inactiveTitleBarColor = group.readEntry( "inactiveBackground", palette.color( QPalette::Disabled, QPalette::Highlight ) ); _inactiveTitleBarTextColor = group.readEntry( "inactiveForeground", palette.color( QPalette::Disabled, QPalette::HighlightedText ) ); } }); } } //____________________________________________________________________ KSharedConfig::Ptr Helper::config() const { return _config; } //____________________________________________________________________ QSharedPointer Helper::decorationConfig() const { return _decorationConfig; } //____________________________________________________________________ void Helper::loadConfig() { _viewFocusBrush = KStatefulBrush( KColorScheme::View, KColorScheme::FocusColor ); _viewHoverBrush = KStatefulBrush( KColorScheme::View, KColorScheme::HoverColor ); _buttonFocusBrush = KStatefulBrush( KColorScheme::Button, KColorScheme::FocusColor ); _buttonHoverBrush = KStatefulBrush( KColorScheme::Button, KColorScheme::HoverColor ); _viewNegativeTextBrush = KStatefulBrush( KColorScheme::View, KColorScheme::NegativeText ); _viewNeutralTextBrush = KStatefulBrush( KColorScheme::View, KColorScheme::NeutralText ); const QPalette palette( QApplication::palette() ); _config->reparseConfiguration(); _kwinConfig->reparseConfiguration(); _cachedAutoValid = false; _decorationConfig->load(); KConfig config(qApp->property("KDE_COLOR_SCHEME_PATH").toString(), KConfig::SimpleConfig); KConfigGroup appGroup( config.group("WM") ); KConfigGroup globalGroup( _config->group("WM") ); _activeTitleBarColor = appGroup.readEntry( "activeBackground", globalGroup.readEntry( "activeBackground", palette.color( QPalette::Active, QPalette::Highlight ) ) ); _activeTitleBarTextColor = appGroup.readEntry( "activeForeground", globalGroup.readEntry( "activeForeground", palette.color( QPalette::Active, QPalette::HighlightedText ) ) ); _inactiveTitleBarColor = appGroup.readEntry( "inactiveBackground", globalGroup.readEntry( "inactiveBackground", palette.color( QPalette::Disabled, QPalette::Highlight ) ) ); _inactiveTitleBarTextColor = appGroup.readEntry( "inactiveForeground", globalGroup.readEntry( "inactiveForeground", palette.color( QPalette::Disabled, QPalette::HighlightedText ) ) ); } QColor transparentize(const QColor& color, qreal amount) { auto clone = color; clone.setAlphaF(amount); return clone; } //____________________________________________________________________ QColor Helper::frameOutlineColor( const QPalette& palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode ) const { QColor outline( KColorUtils::mix( palette.color( QPalette::Window ), palette.color( QPalette::WindowText ), 0.25 ) ); // focus takes precedence over hover if( mode == AnimationFocus ) { const QColor focus( focusColor( palette ) ); const QColor hover( hoverColor( palette ) ); if( mouseOver ) outline = KColorUtils::mix( hover, focus, opacity ); else outline = KColorUtils::mix( outline, focus, opacity ); } else if( hasFocus ) { outline = focusColor( palette ); } else if( mode == AnimationHover ) { const QColor hover( hoverColor( palette ) ); outline = KColorUtils::mix( outline, hover, opacity ); } else if( mouseOver ) { outline = hoverColor( palette ); } return outline; } //____________________________________________________________________ QColor Helper::focusOutlineColor( const QPalette& palette ) const { return KColorUtils::mix( focusColor( palette ), palette.color( QPalette::WindowText ), 0.15 ); } //____________________________________________________________________ QColor Helper::hoverOutlineColor( const QPalette& palette ) const { return KColorUtils::mix( hoverColor( palette ), palette.color( QPalette::WindowText ), 0.15 ); } //____________________________________________________________________ QColor Helper::buttonFocusOutlineColor( const QPalette& palette ) const { return KColorUtils::mix( buttonFocusColor( palette ), palette.color( QPalette::ButtonText ), 0.15 ); } //____________________________________________________________________ QColor Helper::buttonHoverOutlineColor( const QPalette& palette ) const { return KColorUtils::mix( buttonHoverColor( palette ), palette.color( QPalette::ButtonText ), 0.15 ); } //____________________________________________________________________ QColor Helper::sidePanelOutlineColor( const QPalette& palette, bool hasFocus, qreal opacity, AnimationMode mode ) const { QColor outline( palette.color( QPalette::Inactive, QPalette::Highlight ) ); const QColor &focus = palette.color( QPalette::Active, QPalette::Highlight ); if( mode == AnimationFocus ) { outline = KColorUtils::mix( outline, focus, opacity ); } else if( hasFocus ) { outline = focus; } return outline; } //____________________________________________________________________ QColor Helper::frameBackgroundColor( const QPalette& palette, QPalette::ColorGroup group ) const { return KColorUtils::mix( palette.color( group, QPalette::Window ), palette.color( group, QPalette::Base ), 0.3 ); } //____________________________________________________________________ QColor Helper::arrowColor( const QPalette& palette, QPalette::ColorGroup group, QPalette::ColorRole role ) const { switch( role ) { case QPalette::Text: return KColorUtils::mix( palette.color( group, QPalette::Text ), palette.color( group, QPalette::Base ), arrowShade ); case QPalette::WindowText: return KColorUtils::mix( palette.color( group, QPalette::WindowText ), palette.color( group, QPalette::Window ), arrowShade ); case QPalette::ButtonText: return KColorUtils::mix( palette.color( group, QPalette::ButtonText ), palette.color( group, QPalette::Button ), arrowShade ); default: return palette.color( group, role ); } } //____________________________________________________________________ QColor Helper::arrowColor( const QPalette& palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode ) const { QColor outline( arrowColor( palette, QPalette::WindowText ) ); if( mode == AnimationHover ) { const QColor focus( focusColor( palette ) ); const QColor hover( hoverColor( palette ) ); if( hasFocus ) outline = KColorUtils::mix( focus, hover, opacity ); else outline = KColorUtils::mix( outline, hover, opacity ); } else if( mouseOver ) { outline = hoverColor( palette ); } else if( mode == AnimationFocus ) { const QColor focus( focusColor( palette ) ); outline = KColorUtils::mix( outline, focus, opacity ); } else if( hasFocus ) { outline = focusColor( palette ); } return outline; } //____________________________________________________________________ QColor Helper::sliderOutlineColor( const QPalette& palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode ) const { QColor outline( KColorUtils::mix( palette.color( QPalette::Window ), palette.color( QPalette::WindowText ), 0.4 ) ); // hover takes precedence over focus if( mode == AnimationHover ) { const QColor hover( hoverColor( palette ) ); const QColor focus( focusColor( palette ) ); if( hasFocus ) outline = KColorUtils::mix( focus, hover, opacity ); else outline = KColorUtils::mix( outline, hover, opacity ); } else if( mouseOver ) { outline = hoverColor( palette ); } else if( mode == AnimationFocus ) { const QColor focus( focusColor( palette ) ); outline = KColorUtils::mix( outline, focus, opacity ); } else if( hasFocus ) { outline = focusColor( palette ); } return outline; } //____________________________________________________________________ QColor Helper::scrollBarHandleColor( const QPalette& palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode ) const { QColor color( alphaColor( palette.color( QPalette::WindowText ), 0.5 ) ); // hover takes precedence over focus if( mode == AnimationHover ) { const QColor hover( hoverColor( palette ) ); const QColor focus( focusColor( palette ) ); if( hasFocus ) color = KColorUtils::mix( focus, hover, opacity ); else color = KColorUtils::mix( color, hover, opacity ); } else if( mouseOver ) { color = hoverColor( palette ); } else if( mode == AnimationFocus ) { const QColor focus( focusColor( palette ) ); color = KColorUtils::mix( color, focus, opacity ); } else if( hasFocus ) { color = focusColor( palette ); } return color; } //______________________________________________________________________________ QColor Helper::checkBoxIndicatorColor( const QPalette& palette, bool mouseOver, bool active, qreal opacity, AnimationMode mode ) const { QColor color( KColorUtils::mix( palette.color( QPalette::Window ), palette.color( QPalette::WindowText ), 0.6 ) ); if( mode == AnimationHover ) { const QColor focus( focusColor( palette ) ); const QColor hover( hoverColor( palette ) ); if( active ) color = KColorUtils::mix( focus, hover, opacity ); else color = KColorUtils::mix( color, hover, opacity ); } else if( mouseOver ) { color = hoverColor( palette ); } else if( active ) { color = focusColor( palette ); } return color; } //______________________________________________________________________________ QColor Helper::separatorColor( const QPalette& palette ) const { return KColorUtils::mix( palette.color( QPalette::Window ), palette.color( QPalette::WindowText ), 0.25 ); } //______________________________________________________________________________ QPalette Helper::disabledPalette( const QPalette& source, qreal ratio ) const { QPalette copy( source ); const QList roles = { QPalette::Background, QPalette::Highlight, QPalette::WindowText, QPalette::ButtonText, QPalette::Text, QPalette::Button }; foreach( const QPalette::ColorRole& role, roles ) { copy.setColor( role, KColorUtils::mix( source.color( QPalette::Active, role ), source.color( QPalette::Disabled, role ), 1.0-ratio ) ); } return copy; } //____________________________________________________________________ QColor Helper::alphaColor( QColor color, qreal alpha ) const { if( alpha >= 0 && alpha < 1.0 ) { color.setAlphaF( alpha*color.alphaF() ); } return color; } //______________________________________________________________________________ void Helper::renderDebugFrame( QPainter* painter, const QRect& rect ) const { painter->save(); painter->setRenderHints( QPainter::Antialiasing ); painter->setBrush( Qt::NoBrush ); painter->setPen( Qt::red ); painter->drawRect( strokedRect( rect ) ); painter->restore(); } //______________________________________________________________________________ void Helper::renderFocusRect( QPainter* painter, const QRect& rect, const QColor& color, const QColor& outline, Sides sides ) const { if( !color.isValid() ) return; painter->save(); painter->setRenderHints( QPainter::Antialiasing ); painter->setBrush( color ); if( !( outline.isValid() && sides ) ) { painter->setPen( Qt::NoPen ); painter->drawRect( rect ); } else { painter->setClipRect( rect ); QRectF copy( strokedRect( rect ) ); const qreal radius( frameRadius( PenWidth::Frame ) ); if( !(sides&SideTop) ) copy.adjust( 0, -radius, 0, 0 ); if( !(sides&SideBottom) ) copy.adjust( 0, 0, 0, radius ); if( !(sides&SideLeft) ) copy.adjust( -radius, 0, 0, 0 ); if( !(sides&SideRight) ) copy.adjust( 0, 0, radius, 0 ); painter->setPen( outline ); // painter->setBrush( Qt::NoBrush ); painter->drawRoundedRect( copy, radius, radius ); } painter->restore(); } //______________________________________________________________________________ void Helper::renderFocusLine( QPainter* painter, const QRect& rect, const QColor& color ) const { if( !color.isValid() ) return; painter->save(); painter->setRenderHint( QPainter::Antialiasing, false ); painter->setBrush( Qt::NoBrush ); painter->setPen( color ); painter->translate( 0, 2 ); painter->drawLine( rect.bottomLeft(), rect.bottomRight() ); painter->restore(); } //______________________________________________________________________________ void Helper::renderFrame( QPainter* painter, const QRect& rect, const QColor& color, const QColor& outline ) const { painter->setRenderHint( QPainter::Antialiasing ); QRectF frameRect( rect.adjusted( 1, 1, -1, -1 ) ); qreal radius( frameRadius( PenWidth::NoPen ) ); // set pen if( outline.isValid() ) { painter->setPen( outline ); frameRect = strokedRect( frameRect ); radius = frameRadiusForNewPenWidth( radius, PenWidth::Frame ); } else { painter->setPen( Qt::NoPen ); } // set brush if( color.isValid() ) painter->setBrush( color ); else painter->setBrush( Qt::NoBrush ); // render painter->drawRoundedRect( frameRect, radius, radius ); } //______________________________________________________________________________ void Helper::renderSidePanelFrame( QPainter* painter, const QRect& rect, const QColor& outline, Side side ) const { // check color if( !outline.isValid() ) return; // adjust rect QRectF frameRect( strokedRect( rect ) ); // setup painter painter->setRenderHint( QPainter::Antialiasing ); painter->setPen( outline ); // render switch( side ) { default: case SideLeft: painter->drawLine( frameRect.topRight(), frameRect.bottomRight() ); break; case SideTop: painter->drawLine( frameRect.topLeft(), frameRect.topRight() ); break; case SideRight: painter->drawLine( frameRect.topLeft(), frameRect.bottomLeft() ); break; case SideBottom: painter->drawLine( frameRect.bottomLeft(), frameRect.bottomRight() ); break; case AllSides: { const qreal radius( frameRadius( PenWidth::Frame ) ); painter->drawRoundedRect( frameRect, radius, radius ); break; } } } //______________________________________________________________________________ void Helper::renderMenuFrame( QPainter* painter, const QRect& rect, const QColor& color, const QColor& outline, bool roundCorners, bool isTopMenu ) const { painter->save(); // set brush if( color.isValid() ) painter->setBrush( color ); else painter->setBrush( Qt::NoBrush ); // We simulate being able to independently adjust corner radii by // setting a clip region and then extending the rectangle beyond it. if ( isTopMenu ) { painter->setClipRect( rect ); } if( roundCorners ) { painter->setRenderHint( QPainter::Antialiasing ); QRectF frameRect( rect ); qreal radius( frameRadius( PenWidth::NoPen ) ); if( isTopMenu ) frameRect.adjust(0, -radius, 0, 0); // set pen if( outline.isValid() ) { painter->setPen( outline ); frameRect = strokedRect( frameRect ); radius = frameRadiusForNewPenWidth( radius, PenWidth::Frame ); } else painter->setPen( Qt::NoPen ); // render painter->drawRoundedRect( frameRect, radius, radius ); } else { painter->setRenderHint( QPainter::Antialiasing, false ); QRect frameRect( rect ); if( isTopMenu ) frameRect.adjust(0, 1, 0, 0); if( outline.isValid() ) { painter->setPen( outline ); frameRect.adjust( 0, 0, -1, -1 ); } else painter->setPen( Qt::NoPen ); painter->drawRect( frameRect ); } painter->restore(); } //______________________________________________________________________________ void Helper::renderButtonFrame(QPainter* painter, const QRect& rect, const QPalette& palette, const QHash& stateProperties, qreal bgAnimation, qreal penAnimation) const { bool enabled = stateProperties.value("enabled", true); bool visualFocus = stateProperties.value("visualFocus"); bool hovered = stateProperties.value("hovered"); bool down = stateProperties.value("down"); bool checked = stateProperties.value("checked"); bool flat = stateProperties.value("flat"); bool defaultButton = stateProperties.value("defaultButton"); bool hasNeutralHighlight = stateProperties.value("hasNeutralHighlight"); // don't render background if flat and not hovered, down, checked, or given visual focus if (flat && !(hovered || down || checked || visualFocus) && bgAnimation == AnimationData::OpacityInvalid && penAnimation == AnimationData::OpacityInvalid ) { return; } QRectF shadowedRect = this->shadowedRect(rect); QRectF frameRect = strokedRect(shadowedRect); qreal radius = frameRadius( PenWidth::Frame ); // setting color group to work around KColorScheme feature const QColor &highlightColor = palette.color(!enabled ? QPalette::Disabled : QPalette::Active, QPalette::Highlight); QBrush bgBrush; QBrush penBrush; // Colors if (flat) { if (down && enabled) { bgBrush = alphaColor(highlightColor, 0.333); } else if (checked) { bgBrush = hasNeutralHighlight ? alphaColor(neutralText(palette), 0.333) : alphaColor(palette.buttonText().color(), 0.125); penBrush = hasNeutralHighlight ? neutralText(palette) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.3); } else if (defaultButton) { bgBrush = alphaColor(highlightColor, 0.125); penBrush = KColorUtils::mix( highlightColor, KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.333), 0.5 ); } else { bgBrush = alphaColor(highlightColor, 0); penBrush = hasNeutralHighlight ? neutralText(palette) : bgBrush; } } else { if (down && enabled) { bgBrush = KColorUtils::mix(palette.button().color(), highlightColor, 0.333); } else if (checked) { bgBrush = hasNeutralHighlight ? KColorUtils::mix(palette.button().color(), neutralText(palette), 0.333) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.125); penBrush = hasNeutralHighlight ? neutralText(palette) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.3); } else if (defaultButton) { bgBrush = KColorUtils::mix(palette.button().color(), highlightColor, 0.2); penBrush = KColorUtils::mix( highlightColor, KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.333), 0.5 ); } else { bgBrush = palette.button().color(); penBrush = hasNeutralHighlight ? neutralText(palette) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.3); } } if ((hovered || visualFocus || down) && enabled) { penBrush = highlightColor; } // Animations if (bgAnimation != AnimationData::OpacityInvalid && enabled) { QColor color1 = bgBrush.color(); QColor color2 = flat ? alphaColor(highlightColor, 0.333) : KColorUtils::mix(palette.button().color(), highlightColor, 0.333); bgBrush = KColorUtils::mix(color1, color2, bgAnimation); } if (penAnimation != AnimationData::OpacityInvalid && enabled) { QColor color1 = penBrush.color(); QColor color2 = highlightColor; penBrush = KColorUtils::mix(color1, color2, penAnimation); } // Gradient if (!(flat || down || hovered || checked) && enabled) { QLinearGradient bgGradient(frameRect.topLeft(), frameRect.bottomLeft()); bgGradient.setColorAt(0, KColorUtils::mix(bgBrush.color(), Qt::white, 0.03125)); bgGradient.setColorAt(0.5, bgBrush.color()); bgGradient.setColorAt(1, KColorUtils::mix(bgBrush.color(), Qt::black, 0.03125)); QLinearGradient penGradient(frameRect.topLeft(), frameRect.bottomLeft()); penGradient.setColorAt(0, KColorUtils::mix(penBrush.color(), Qt::white, 0.03125)); penGradient.setColorAt(1, KColorUtils::mix(penBrush.color(), Qt::black, 0.0625)); bgBrush = bgGradient; penBrush = penGradient; } // Shadow if (!(flat || down || checked) && enabled) { renderRoundedRectShadow(painter, shadowedRect, shadowColor(palette)); } // Render button painter->setRenderHint( QPainter::Antialiasing, true ); painter->setBrush(bgBrush); painter->setPen(QPen(penBrush, PenWidth::Frame)); painter->drawRoundedRect(frameRect, radius, radius); } //______________________________________________________________________________ void Helper::renderToolBoxFrame( QPainter* painter, const QRect& rect, int tabWidth, const QColor& outline ) const { if( !outline.isValid() ) return; // round radius const qreal radius( frameRadius( PenWidth::Frame ) ); const QSizeF cornerSize( 2*radius, 2*radius ); // if rect - tabwidth is even, need to increase tabWidth by 1 unit // for anti aliasing if( !((rect.width() - tabWidth)%2) ) ++tabWidth; // adjust rect for antialiasing QRectF baseRect( strokedRect( rect ) ); // create path QPainterPath path; path.moveTo( 0, baseRect.height()-1 ); path.lineTo( ( baseRect.width() - tabWidth )/2 - radius, baseRect.height()-1 ); path.arcTo( QRectF( QPointF( ( baseRect.width() - tabWidth )/2 - 2*radius, baseRect.height()-1 - 2*radius ), cornerSize ), 270, 90 ); path.lineTo( ( baseRect.width() - tabWidth )/2, radius ); path.arcTo( QRectF( QPointF( ( baseRect.width() - tabWidth )/2, 0 ), cornerSize ), 180, -90 ); path.lineTo( ( baseRect.width() + tabWidth )/2 -1 - radius, 0 ); path.arcTo( QRectF( QPointF( ( baseRect.width() + tabWidth )/2 - 1 - 2*radius, 0 ), cornerSize ), 90, -90 ); path.lineTo( ( baseRect.width() + tabWidth )/2 -1, baseRect.height()-1 - radius ); path.arcTo( QRectF( QPointF( ( baseRect.width() + tabWidth )/2 -1, baseRect.height()-1 - 2*radius ), cornerSize ), 180, 90 ); path.lineTo( baseRect.width()-1, baseRect.height()-1 ); // render painter->setRenderHints( QPainter::Antialiasing ); painter->setBrush( Qt::NoBrush ); painter->setPen( outline ); painter->translate( baseRect.topLeft() ); painter->drawPath( path ); } //______________________________________________________________________________ void Helper::renderTabWidgetFrame( QPainter* painter, const QRect& rect, const QColor& color, const QColor& outline, Corners corners ) const { painter->setRenderHint( QPainter::Antialiasing ); QRectF frameRect( rect.adjusted( 1, 1, -1, -1 ) ); qreal radius( frameRadius( PenWidth::NoPen ) ); // set pen if( outline.isValid() ) { painter->setPen( outline ); frameRect = strokedRect( frameRect ); radius = frameRadiusForNewPenWidth( radius, PenWidth::Frame ); } else painter->setPen( Qt::NoPen ); // set brush if( color.isValid() ) painter->setBrush( color ); else painter->setBrush( Qt::NoBrush ); // render QPainterPath path( roundedPath( frameRect, corners, radius ) ); painter->drawPath( path ); } //______________________________________________________________________________ void Helper::renderSelection( QPainter* painter, const QRect& rect, const QColor& color ) const { painter->setRenderHint( QPainter::Antialiasing ); painter->setPen( Qt::NoPen ); painter->setBrush( color ); painter->drawRect( rect ); } //______________________________________________________________________________ void Helper::renderSeparator( QPainter* painter, const QRect& rect, const QColor& color, bool vertical ) const { painter->setRenderHint( QPainter::Antialiasing, false ); painter->setBrush( Qt::NoBrush ); painter->setPen( color ); if( vertical ) { painter->translate( rect.width()/2, 0 ); painter->drawLine( rect.topLeft(), rect.bottomLeft() ); } else { painter->translate( 0, rect.height()/2 ); painter->drawLine( rect.topLeft(), rect.topRight() ); } } //______________________________________________________________________________ void Helper::renderCheckBoxBackground( QPainter* painter, const QRect& rect, const QPalette& palette, CheckBoxState state, bool neutalHighlight, bool sunken, qreal animation ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); // copy rect QRectF frameRect( rect ); frameRect.adjust( 2, 2, -2, -2 ); frameRect = strokedRect(frameRect); auto transparent = neutalHighlight ? neutralText(palette) : palette.highlight().color(); transparent.setAlphaF(0.50); painter->setPen( transparentize( palette.text().color(), 0.5 ) ); if (state == CheckOn || state == CheckPartial) { painter->setPen( neutalHighlight ? neutralText(palette) : palette.highlight().color() ); } const auto radius = Metrics::CheckBox_Radius; switch (state) { case CheckOff: painter->setBrush( palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100) ); painter->drawRoundedRect( frameRect, radius, radius ); break; case CheckPartial: case CheckOn: painter->setBrush( transparent ); painter->drawRoundedRect( frameRect, radius, radius ); break; case CheckAnimated: painter->setBrush( palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100) ); painter->drawRoundedRect( frameRect, radius, radius ); painter->setBrush( transparent ); painter->setOpacity( animation ); painter->drawRoundedRect( frameRect, radius, radius ); break; } } //______________________________________________________________________________ void Helper::renderCheckBox( QPainter* painter, const QRect& rect, const QPalette& palette, bool mouseOver, CheckBoxState state, CheckBoxState target, bool neutalHighlight, bool sunken, qreal animation, qreal hoverAnimation ) const { Q_UNUSED(sunken) // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); // copy rect and radius QRectF frameRect( rect ); frameRect.adjust( 2, 2, -2, -2 ); if ( mouseOver ) { painter->save(); if (hoverAnimation != AnimationData::OpacityInvalid) { painter->setOpacity(hoverAnimation); } painter->setPen( QPen( neutalHighlight ? neutralText(palette).lighter() : focusColor(palette), PenWidth::Frame ) ); painter->setBrush( Qt::NoBrush ); painter->drawRoundedRect( frameRect.adjusted(0.5, 0.5, -0.5, -0.5), Metrics::CheckBox_Radius, Metrics::CheckBox_Radius ); painter->restore(); } // check auto leftPoint = frameRect.center(); leftPoint.setX(frameRect.left()+4); auto bottomPoint = frameRect.center(); bottomPoint.setX(bottomPoint.x() - 1); bottomPoint.setY(frameRect.bottom() - 5); auto rightPoint = frameRect.center(); rightPoint.setX(rightPoint.x() + 4.5); rightPoint.setY(frameRect.top() + 5.5); QPainterPath path; path.moveTo(leftPoint); path.lineTo(bottomPoint); path.lineTo(rightPoint); // dots auto centerDot = QRectF(frameRect.center(), QSize(2, 2)); centerDot.adjust(-1, -1, -1, -1); auto leftDot = centerDot.adjusted(-4, 0, -4, 0); auto rightDot = centerDot.adjusted(4, 0, 4, 0); painter->setPen(Qt::transparent); painter->setBrush(Qt::transparent); auto checkPen = QPen( palette.text(), PenWidth::Frame * 2 ); checkPen.setJoinStyle(Qt::MiterJoin); switch (state) { case CheckOff: break; case CheckOn: painter->setPen( checkPen ); painter->drawPath( path ); break; case CheckPartial: painter->setBrush( palette.text() ); painter->drawRect(leftDot); painter->drawRect(centerDot); painter->drawRect(rightDot); break; case CheckAnimated: checkPen.setDashPattern({ path.length() * animation, path.length() }); switch (target) { case CheckOff: break; case CheckOn: painter->setPen( checkPen ); painter->drawPath( path ); break; case CheckPartial: if (animation >= 3/3) painter->drawRect(rightDot); if (animation >= 2/3) painter->drawRect(centerDot); if (animation >= 1/3) painter->drawRect(leftDot); break; case CheckAnimated: break; } break; } } //______________________________________________________________________________ void Helper::renderRadioButtonBackground( QPainter* painter, const QRect& rect, const QPalette& palette, RadioButtonState state, bool neutalHighlight, bool sunken, qreal animation ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); // copy rect QRectF frameRect( rect ); frameRect.adjust( 2, 2, -2, -2 ); frameRect.adjust( 0.5, 0.5, -0.5, -0.5 ); auto transparent = neutalHighlight ? neutralText(palette) : palette.highlight().color(); transparent.setAlphaF(0.50); painter->setPen( QPen(transparentize( palette.text().color(), 0.5 ), PenWidth::Frame) ); if (state == RadioOn) { painter->setPen( QPen(neutalHighlight ? neutralText(palette) : palette.highlight().color(), PenWidth::Frame) ); } switch (state) { case RadioOff: painter->setBrush( palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100) ); painter->drawEllipse( frameRect ); break; case RadioOn: painter->setBrush( transparent ); painter->drawEllipse( frameRect ); break; case RadioAnimated: painter->setBrush( palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100) ); painter->drawEllipse( frameRect ); painter->setBrush( transparent ); painter->setOpacity( animation ); painter->drawEllipse( frameRect ); break; } } //______________________________________________________________________________ void Helper::renderRadioButton( QPainter* painter, const QRect& rect, const QPalette& palette, bool mouseOver, RadioButtonState state, bool neutralHighlight, bool sunken, qreal animation, qreal animationHover ) const { Q_UNUSED(sunken) // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); // copy rect QRectF frameRect( rect ); frameRect.adjust( 1, 1, -1, -1 ); if ( mouseOver ) { painter->save(); if (animationHover != AnimationData::OpacityInvalid) { painter->setOpacity(animationHover); } painter->setPen( QPen( neutralHighlight ? neutralText(palette).lighter() : focusColor(palette), PenWidth::Frame ) ); painter->setBrush( Qt::NoBrush ); const QRectF contentRect( frameRect.adjusted( 1, 1, -1, -1 ).adjusted( 0.5 , 0.5, -0.5, -0.5 ) ); painter->drawEllipse( contentRect ); painter->restore(); } painter->setBrush( palette.text() ); painter->setPen( Qt::NoPen ); QRectF markerRect; markerRect = frameRect.adjusted( 6, 6, -6, -6 ); qreal adjustFactor; // mark switch (state) { case RadioOn: painter->drawEllipse( markerRect ); break; case RadioAnimated: adjustFactor = markerRect.height() * (1 - animation); markerRect.adjust(adjustFactor, adjustFactor, -adjustFactor, -adjustFactor); painter->drawEllipse( markerRect ); break; default: break; } } //______________________________________________________________________________ void Helper::renderSliderGroove( QPainter* painter, const QRect& rect, const QColor& color ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); QRectF baseRect( rect ); baseRect.adjust(0.5, 0.5, -0.5, -0.5); const qreal radius( 0.5*Metrics::Slider_GrooveThickness ); // content if( color.isValid() ) { painter->setPen( QPen(color, PenWidth::Frame) ); auto bg = color; bg.setAlphaF(bg.alphaF() / 2); painter->setBrush( bg ); painter->drawRoundedRect( baseRect, radius, radius ); } } //______________________________________________________________________________ void Helper::renderDialGroove( QPainter* painter, const QRect& rect, const QColor& fg, const QColor& bg, qreal first, qreal last ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); const QRectF baseRect( rect ); // content if( fg.isValid() ) { const qreal penWidth( Metrics::Slider_GrooveThickness ); const QRectF grooveRect( rect.adjusted( penWidth/2, penWidth/2, -penWidth/2, -penWidth/2 ) ); // setup angles const int angleStart( first * 180 * 16 / M_PI ); const int angleSpan( (last - first ) * 180 * 16 / M_PI ); const QPen bgPen( fg, penWidth, Qt::SolidLine, Qt::RoundCap ); const QPen fgPen( KColorUtils::overlayColors( bg, alphaColor( fg, 0.5) ), penWidth - 2, Qt::SolidLine, Qt::RoundCap ); // setup pen if( angleSpan != 0 ) { painter->setPen( bgPen ); painter->setBrush( Qt::NoBrush ); painter->drawArc( grooveRect, angleStart, angleSpan ); painter->setPen( fgPen ); painter->drawArc( grooveRect, angleStart, angleSpan ); } } } //______________________________________________________________________________ void Helper::renderSliderHandle( QPainter* painter, const QRect& rect, const QColor& color, const QColor& outline, const QColor& shadow, bool sunken ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); // copy rect QRectF frameRect( rect ); frameRect.adjust( 1, 1, -1, -1 ); // shadow if( !sunken ) { renderEllipseShadow( painter, frameRect, shadow ); } // set pen if( outline.isValid() ) { painter->setPen( QPen(outline, PenWidth::Frame) ); frameRect = strokedRect( frameRect ); } else painter->setPen( Qt::NoPen ); // set brush if( color.isValid() ) painter->setBrush( color ); else painter->setBrush( Qt::NoBrush ); // render painter->drawEllipse( frameRect ); } //______________________________________________________________________________ void Helper::renderProgressBarGroove( QPainter* painter, const QRect& rect, const QColor& fg, const QColor& bg ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); QRectF baseRect( rect ); baseRect.adjust(0.5,0.5,-0.5,-0.5); const qreal radius( 0.5*Metrics::ProgressBar_Thickness ); // content if( fg.isValid() ) { painter->setPen( QPen(fg, PenWidth::Frame) ); painter->setBrush( KColorUtils::overlayColors(bg, alphaColor(fg, 0.5)) ); painter->drawRoundedRect( baseRect, radius, radius ); } } //______________________________________________________________________________ void Helper::renderProgressBarBusyContents( QPainter* painter, const QRect& rect, const QColor& first, const QColor& second, bool horizontal, bool reverse, int progress ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); const QRectF baseRect( rect ); const qreal radius( 0.5*Metrics::ProgressBar_Thickness ); // setup brush QPixmap pixmap( horizontal ? 2*Metrics::ProgressBar_BusyIndicatorSize : 1, horizontal ? 1:2*Metrics::ProgressBar_BusyIndicatorSize ); pixmap.fill( second ); if( horizontal ) { QPainter painter( &pixmap ); painter.setBrush( first ); painter.setPen( Qt::NoPen ); progress %= 2*Metrics::ProgressBar_BusyIndicatorSize; if( reverse ) progress = 2*Metrics::ProgressBar_BusyIndicatorSize - progress - 1; painter.drawRect( QRect( 0, 0, Metrics::ProgressBar_BusyIndicatorSize, 1 ).translated( progress, 0 ) ); if( progress > Metrics::ProgressBar_BusyIndicatorSize ) { painter.drawRect( QRect( 0, 0, Metrics::ProgressBar_BusyIndicatorSize, 1 ).translated( progress - 2*Metrics::ProgressBar_BusyIndicatorSize, 0 ) ); } } else { QPainter painter( &pixmap ); painter.setBrush( first ); painter.setPen( Qt::NoPen ); progress %= 2*Metrics::ProgressBar_BusyIndicatorSize; progress = 2*Metrics::ProgressBar_BusyIndicatorSize - progress - 1; painter.drawRect( QRect( 0, 0, 1, Metrics::ProgressBar_BusyIndicatorSize ).translated( 0, progress ) ); if( progress > Metrics::ProgressBar_BusyIndicatorSize ) { painter.drawRect( QRect( 0, 0, 1, Metrics::ProgressBar_BusyIndicatorSize ).translated( 0, progress - 2*Metrics::ProgressBar_BusyIndicatorSize ) ); } } painter->setPen( Qt::NoPen ); painter->setBrush( pixmap ); painter->drawRoundedRect( baseRect, radius, radius ); } //______________________________________________________________________________ void Helper::renderScrollBarHandle( QPainter* painter, const QRect& rect, const QColor& fg, const QColor& bg ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); const QRectF baseRect( rect ); const qreal radius( 0.5 * std::min({baseRect.width(), baseRect.height(), (qreal)Metrics::ScrollBar_SliderWidth}) ); painter->setPen( Qt::NoPen ); painter->setPen( QPen(fg, 1.001) ); painter->setBrush( KColorUtils::overlayColors(bg, alphaColor(fg, 0.5)) ); painter->drawRoundedRect( strokedRect(baseRect), radius, radius ); } void Helper::renderScrollBarGroove( QPainter* painter, const QRect& rect, const QColor& color ) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); const QRectF baseRect( rect ); const qreal radius( 0.5 * std::min({baseRect.width(), baseRect.height(), (qreal)Metrics::ScrollBar_SliderWidth}) ); // content if( color.isValid() ) { painter->setPen( Qt::NoPen ); auto bg = color; bg.setAlphaF(bg.alphaF() / 2.0); painter->setBrush( bg ); painter->setPen( QPen(color, 1.001) ); painter->drawRoundedRect( strokedRect(baseRect), radius, radius ); } } //______________________________________________________________________________ void Helper::renderScrollBarBorder( QPainter* painter, const QRect& rect, const QColor& color ) const { // content if( color.isValid() ) { painter->setPen( Qt::NoPen ); painter->setBrush( color ); painter->drawRect( rect ); } } //______________________________________________________________________________ void Helper::renderTabBarTab(QPainter *painter, const QRect &rect, const QColor &color, const QColor &highlight, const QColor &outline, Corners corners, bool document, bool bottom) const { // setup painter painter->setRenderHint( QPainter::Antialiasing, true ); QRectF frameRect( rect ); qreal radius( frameRadius( PenWidth::NoPen ) ); // pen if( outline.isValid() ) { painter->setPen( outline ); frameRect = strokedRect( frameRect ); radius = frameRadiusForNewPenWidth( radius, PenWidth::Frame ); } else painter->setPen( Qt::NoPen ); // brush if( color.isValid() ) painter->setBrush( color ); else painter->setBrush( Qt::NoBrush ); // render QPainterPath path( roundedPath( frameRect, corners, radius ) ); painter->drawPath( path ); if (highlight.isValid() && document) { auto rect = frameRect; rect.setHeight(2); if (bottom) { rect.setTop(frameRect.bottom()-2); rect.setBottom(frameRect.bottom()); } QPainterPath rectAsPath; rectAsPath.addRect(rect); painter->setClipPath(path.intersected(rectAsPath)); painter->setBrush(highlight); painter->drawRect(frameRect); } } //______________________________________________________________________________ void Helper::renderArrow( QPainter* painter, const QRect& rect, const QColor& color, ArrowOrientation orientation ) const { // define polygon QPolygonF arrow; switch( orientation ) { /* The inner points of the normal arrows are not on half pixels because * they need to have an even width (up/down) or height (left/right). * An even width/height makes them easier to align with other UI elements. */ case ArrowUp: arrow = QVector{QPointF( -4.5, 1.5 ), QPointF( 0, -3 ), QPointF( 4.5, 1.5 )}; break; case ArrowDown: arrow = QVector{QPointF( -4.5, -1.5 ), QPointF( 0, 3 ), QPointF( 4.5, -1.5 )}; break; case ArrowLeft: arrow = QVector{QPointF( 1.5, -4.5 ), QPointF( -3, 0 ), QPointF( 1.5, 4.5 )}; break; case ArrowRight: arrow = QVector{QPointF( -1.5, -4.5 ), QPointF( 3, 0 ), QPointF( -1.5, 4.5 )}; break; case ArrowDown_Small: arrow = QVector{QPointF( 1.5, 3.5 ), QPointF( 3.5, 5.5 ), QPointF( 5.5, 3.5 )}; break; default: break; } painter->save(); painter->setRenderHints( QPainter::Antialiasing ); painter->translate( QRectF( rect ).center() ); painter->setBrush( Qt::NoBrush ); QPen pen( color, PenWidth::Symbol ); pen.setCapStyle(Qt::SquareCap); pen.setJoinStyle(Qt::MiterJoin); painter->setPen( pen ); painter->drawPolyline( arrow ); painter->restore(); } //______________________________________________________________________________ void Helper::renderDecorationButton( QPainter* painter, const QRect& rect, const QColor& color, ButtonType buttonType, bool inverted ) const { painter->save(); painter->setViewport( rect ); painter->setWindow( 0, 0, 18, 18 ); painter->setRenderHints( QPainter::Antialiasing ); // initialize pen QPen pen; pen.setCapStyle( Qt::RoundCap ); pen.setJoinStyle( Qt::MiterJoin ); if( inverted ) { // render circle painter->setPen( Qt::NoPen ); painter->setBrush( color ); painter->drawEllipse( QRectF( 0, 0, 18, 18 ) ); // take out the inner part painter->setCompositionMode( QPainter::CompositionMode_DestinationOut ); painter->setBrush( Qt::NoBrush ); pen.setColor( Qt::black ); } else { painter->setBrush( Qt::NoBrush ); pen.setColor( color ); } pen.setCapStyle( Qt::RoundCap ); pen.setJoinStyle( Qt::MiterJoin ); pen.setWidthF( PenWidth::Symbol*qMax(1.0, 18.0/rect.width() ) ); painter->setPen( pen ); switch( buttonType ) { case ButtonClose: { painter->drawLine( QPointF( 5, 5 ), QPointF( 13, 13 ) ); painter->drawLine( 13, 5, 5, 13 ); break; } case ButtonMaximize: { painter->drawPolyline( QVector{ QPointF( 4, 11 ), QPointF( 9, 6 ), QPointF( 14, 11 )}); break; } case ButtonMinimize: { painter->drawPolyline(QVector{ QPointF( 4, 7 ), QPointF( 9, 12 ), QPointF( 14, 7 )} ); break; } case ButtonRestore: { pen.setJoinStyle( Qt::RoundJoin ); painter->setPen( pen ); painter->drawPolygon( QVector{ QPointF( 4.5, 9 ), QPointF( 9, 4.5 ), QPointF( 13.5, 9 ), QPointF( 9, 13.5 )}); break; } default: break; } painter->restore(); } //______________________________________________________________________________ void Helper::renderRoundedRectShadow( QPainter* painter, const QRectF& rect, const QColor& color, qreal radius ) const { if( !color.isValid() ) return; painter->setRenderHint( QPainter::Antialiasing, true ); qreal adjustment = 0.5 * PenWidth::Shadow; // Translate for the pen QRectF shadowRect = rect.adjusted( adjustment, adjustment, -adjustment, adjustment ); painter->setPen( QPen(color, PenWidth::Shadow) ); painter->setBrush( Qt::NoBrush ); painter->drawRoundedRect( shadowRect, radius, radius ); } //______________________________________________________________________________ void Helper::renderEllipseShadow( QPainter* painter, const QRectF& rect, const QColor& color ) const { if( !color.isValid() ) return; painter->save(); // Clipping does not improve performance here qreal adjustment = 0.5 * PenWidth::Shadow; // Adjust for the pen qreal radius = rect.width() / 2 - adjustment; /* The right side is offset by +0.5 for the visible part of the shadow. * The other sides are offset by +0.5 or -0.5 because of the pen. */ QRectF shadowRect = rect.adjusted( adjustment, adjustment, adjustment, -adjustment ); painter->translate( rect.center() ); painter->rotate( 45 ); painter->translate( -rect.center() ); painter->setPen( color ); painter->setBrush( Qt::NoBrush ); painter->drawRoundedRect( shadowRect, radius, radius ); painter->restore(); } //______________________________________________________________________________ bool Helper::isX11() { static const bool s_isX11 = KWindowSystem::isPlatformX11(); return s_isX11; } //______________________________________________________________________________ bool Helper::isWayland() { static const bool s_isWayland = KWindowSystem::isPlatformWayland(); return s_isWayland; } //______________________________________________________________________________ QRectF Helper::strokedRect( const QRectF &rect, const qreal penWidth ) const { /* With a pen stroke width of 1, the rectangle should have each of its * sides moved inwards by half a pixel. This allows the stroke to be * pixel perfect instead of blurry from sitting between pixels and * prevents the rectangle with a stroke from becoming larger than the * original size of the rectangle. */ qreal adjustment = 0.5 * penWidth; return rect.adjusted( adjustment, adjustment, -adjustment, -adjustment ); } //______________________________________________________________________________ QPainterPath Helper::roundedPath( const QRectF& rect, Corners corners, qreal radius ) const { QPainterPath path; // simple cases if( corners == 0 ) { path.addRect( rect ); return path; } if( corners == AllCorners ) { path.addRoundedRect( rect, radius, radius ); return path; } const QSizeF cornerSize( 2*radius, 2*radius ); // rotate counterclockwise // top left corner if( corners & CornerTopLeft ) { path.moveTo( rect.topLeft() + QPointF( radius, 0 ) ); path.arcTo( QRectF( rect.topLeft(), cornerSize ), 90, 90 ); } else path.moveTo( rect.topLeft() ); // bottom left corner if( corners & CornerBottomLeft ) { path.lineTo( rect.bottomLeft() - QPointF( 0, radius ) ); path.arcTo( QRectF( rect.bottomLeft() - QPointF( 0, 2*radius ), cornerSize ), 180, 90 ); } else path.lineTo( rect.bottomLeft() ); // bottom right corner if( corners & CornerBottomRight ) { path.lineTo( rect.bottomRight() - QPointF( radius, 0 ) ); path.arcTo( QRectF( rect.bottomRight() - QPointF( 2*radius, 2*radius ), cornerSize ), 270, 90 ); } else path.lineTo( rect.bottomRight() ); // top right corner if( corners & CornerTopRight ) { path.lineTo( rect.topRight() + QPointF( 0, radius ) ); path.arcTo( QRectF( rect.topRight() - QPointF( 2*radius, 0 ), cornerSize ), 0, 90 ); } else path.lineTo( rect.topRight() ); path.closeSubpath(); return path; } //________________________________________________________________________________________________________ bool Helper::compositingActive() const { #if BREEZE_HAVE_QTX11EXTRAS if( isX11() ) { return QX11Info::isCompositingManagerRunning( QX11Info::appScreen() ); } #endif // use KWindowSystem return KWindowSystem::compositingActive(); } //____________________________________________________________________ bool Helper::hasAlphaChannel( const QWidget* widget ) const { return compositingActive() && widget && widget->testAttribute( Qt::WA_TranslucentBackground ); } //______________________________________________________________________________________ qreal Helper::devicePixelRatio( const QPixmap& pixmap ) const { return pixmap.devicePixelRatio(); } QPixmap Helper::coloredIcon(const QIcon& icon, const QPalette& palette, const QSize &size, QIcon::Mode mode, QIcon::State state) { const QPalette activePalette = KIconLoader::global()->customPalette(); const bool changePalette = activePalette != palette; if (changePalette) { KIconLoader::global()->setCustomPalette(palette); } const QPixmap pixmap = icon.pixmap(size, mode, state); if (changePalette) { if (activePalette == QPalette()) { KIconLoader::global()->resetPalette(); } else { KIconLoader::global()->setCustomPalette(activePalette); } } return pixmap; } bool Helper::shouldDrawToolsArea(const QWidget* widget) const { if (!widget) { return false; } static bool isAuto = false; static QString borderSize; if (!_cachedAutoValid) { KConfigGroup kdecorationGroup(_kwinConfig->group("org.kde.kdecoration2")); isAuto = kdecorationGroup.readEntry("BorderSizeAuto", true); borderSize = kdecorationGroup.readEntry("BorderSize", "Normal"); _cachedAutoValid = true; } if (isAuto) { auto window = widget->window(); if (qobject_cast(widget)) { return true; } if (window) { auto handle = window->windowHandle(); if (handle) { auto toolbar = qobject_cast(widget); if (toolbar) { if (toolbar->isFloating()) { return false; } } return true; } } else { return false; } } if (borderSize != "None" && borderSize != "NoSides") { return false; } return true; } }