File indexing completed on 2024-05-12 09:30:40

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "breezehelper.h"
0008 
0009 #include "breeze.h"
0010 #include "breezepropertynames.h"
0011 
0012 #include <KColorScheme>
0013 #include <KColorUtils>
0014 #include <KIconLoader>
0015 #include <KWindowSystem>
0016 #include <qobject.h>
0017 #if __has_include(<KX11Extras>)
0018 #include <KX11Extras>
0019 #endif
0020 
0021 #include <QApplication>
0022 #include <QDBusConnection>
0023 #include <QDockWidget>
0024 #include <QFileInfo>
0025 #include <QMainWindow>
0026 #include <QMdiArea>
0027 #include <QMenuBar>
0028 #include <QPainter>
0029 #include <QStyleOption>
0030 #include <QWindow>
0031 
0032 #include <QDialog>
0033 #include <algorithm>
0034 
0035 namespace Breeze
0036 {
0037 //* contrast for arrow and treeline rendering
0038 static const qreal arrowShade = 0.15;
0039 
0040 static const qreal highlightBackgroundAlpha = 0.33;
0041 
0042 static const auto radioCheckSunkenDarkeningFactor = 110;
0043 
0044 PaletteChangedEventFilter::PaletteChangedEventFilter(Helper *helper)
0045     : QObject(helper)
0046     , _helper(helper)
0047 {
0048 }
0049 
0050 bool PaletteChangedEventFilter::eventFilter(QObject *watched, QEvent *event)
0051 {
0052     if (event->type() != QEvent::ApplicationPaletteChange || watched != qApp) {
0053         return QObject::eventFilter(watched, event);
0054     }
0055     if (!qApp->property("KDE_COLOR_SCHEME_PATH").isValid()) {
0056         return QObject::eventFilter(watched, event);
0057     }
0058     const auto path = qApp->property("KDE_COLOR_SCHEME_PATH").toString();
0059     if (!path.isEmpty()) {
0060         KConfig config(path, KConfig::SimpleConfig);
0061         KConfigGroup group(config.group(QStringLiteral("WM")));
0062         const QPalette palette(QApplication::palette());
0063         _helper->_activeTitleBarColor = group.readEntry("activeBackground", palette.color(QPalette::Active, QPalette::Highlight));
0064         _helper->_activeTitleBarTextColor = group.readEntry("activeForeground", palette.color(QPalette::Active, QPalette::HighlightedText));
0065         _helper->_inactiveTitleBarColor = group.readEntry("inactiveBackground", palette.color(QPalette::Disabled, QPalette::Highlight));
0066         _helper->_inactiveTitleBarTextColor = group.readEntry("inactiveForeground", palette.color(QPalette::Disabled, QPalette::HighlightedText));
0067     }
0068     return QObject::eventFilter(watched, event);
0069 }
0070 
0071 //____________________________________________________________________
0072 Helper::Helper(KSharedConfig::Ptr config, QObject *parent)
0073     : QObject(parent)
0074     , _config(std::move(config))
0075     , _kwinConfig(KSharedConfig::openConfig("kwinrc"))
0076     , _decorationConfig(new InternalSettings())
0077     , _eventFilter(new PaletteChangedEventFilter(this))
0078 {
0079 }
0080 
0081 //____________________________________________________________________
0082 KSharedConfig::Ptr Helper::config() const
0083 {
0084     return _config;
0085 }
0086 
0087 //____________________________________________________________________
0088 QSharedPointer<InternalSettings> Helper::decorationConfig() const
0089 {
0090     return _decorationConfig;
0091 }
0092 
0093 //____________________________________________________________________
0094 void Helper::loadConfig()
0095 {
0096     _viewFocusBrush = KStatefulBrush(KColorScheme::View, KColorScheme::FocusColor);
0097     _viewHoverBrush = KStatefulBrush(KColorScheme::View, KColorScheme::HoverColor);
0098     _buttonFocusBrush = KStatefulBrush(KColorScheme::Button, KColorScheme::FocusColor);
0099     _buttonHoverBrush = KStatefulBrush(KColorScheme::Button, KColorScheme::HoverColor);
0100     _viewNegativeTextBrush = KStatefulBrush(KColorScheme::View, KColorScheme::NegativeText);
0101     _viewNeutralTextBrush = KStatefulBrush(KColorScheme::View, KColorScheme::NeutralText);
0102 
0103     const QPalette palette(QApplication::palette());
0104     _config->reparseConfiguration();
0105     _kwinConfig->reparseConfiguration();
0106     _cachedAutoValid = false;
0107     _decorationConfig->load();
0108 
0109     KConfigGroup globalGroup(_config->group(QStringLiteral("WM")));
0110     _activeTitleBarColor = globalGroup.readEntry("activeBackground", palette.color(QPalette::Active, QPalette::Highlight));
0111     _activeTitleBarTextColor = globalGroup.readEntry("activeForeground", palette.color(QPalette::Active, QPalette::HighlightedText));
0112     _inactiveTitleBarColor = globalGroup.readEntry("inactiveBackground", palette.color(QPalette::Disabled, QPalette::Highlight));
0113     _inactiveTitleBarTextColor = globalGroup.readEntry("inactiveForeground", palette.color(QPalette::Disabled, QPalette::HighlightedText));
0114 
0115     if (const QString colorSchemePath = qApp->property("KDE_COLOR_SCHEME_PATH").toString(); !colorSchemePath.isEmpty()) {
0116         KConfig config(colorSchemePath, KConfig::SimpleConfig);
0117         KConfigGroup appGroup(config.group(QStringLiteral("WM")));
0118         _activeTitleBarColor = appGroup.readEntry("activeBackground", _activeTitleBarColor);
0119         _activeTitleBarTextColor = appGroup.readEntry("activeForeground", _activeTitleBarTextColor);
0120         _inactiveTitleBarColor = appGroup.readEntry("inactiveBackground", _inactiveTitleBarColor);
0121         _inactiveTitleBarTextColor = appGroup.readEntry("inactiveForeground", _inactiveTitleBarTextColor);
0122     }
0123 }
0124 
0125 void Helper::installEventFilter(QApplication *app) const
0126 {
0127     if (app) {
0128         app->installEventFilter(_eventFilter);
0129     }
0130 }
0131 
0132 void Helper::removeEventFilter(QApplication *app) const
0133 {
0134     if (app) {
0135         app->removeEventFilter(_eventFilter);
0136     }
0137 }
0138 
0139 QColor transparentize(const QColor &color, qreal amount)
0140 {
0141     auto clone = color;
0142     clone.setAlphaF(amount);
0143     return clone;
0144 }
0145 
0146 //____________________________________________________________________
0147 QColor Helper::frameOutlineColor(const QPalette &palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode) const
0148 {
0149     QColor outline(KColorUtils::mix(palette.color(QPalette::Window), palette.color(QPalette::WindowText), Metrics::Bias_Default));
0150 
0151     // focus takes precedence over hover
0152     if (mode == AnimationFocus) {
0153         const QColor focus(focusColor(palette));
0154         const QColor hover(hoverColor(palette));
0155 
0156         if (mouseOver) {
0157             outline = KColorUtils::mix(hover, focus, opacity);
0158         } else {
0159             outline = KColorUtils::mix(outline, focus, opacity);
0160         }
0161 
0162     } else if (hasFocus) {
0163         outline = focusColor(palette);
0164 
0165     } else if (mode == AnimationHover) {
0166         const QColor hover(hoverColor(palette));
0167         outline = KColorUtils::mix(outline, hover, opacity);
0168 
0169     } else if (mouseOver) {
0170         outline = hoverColor(palette);
0171     }
0172 
0173     return outline;
0174 }
0175 
0176 //____________________________________________________________________
0177 QColor Helper::focusOutlineColor(const QPalette &palette) const
0178 {
0179     return KColorUtils::mix(focusColor(palette), palette.color(QPalette::WindowText), 0.15);
0180 }
0181 
0182 //____________________________________________________________________
0183 QColor Helper::hoverOutlineColor(const QPalette &palette) const
0184 {
0185     return KColorUtils::mix(hoverColor(palette), palette.color(QPalette::WindowText), 0.15);
0186 }
0187 
0188 //____________________________________________________________________
0189 QColor Helper::buttonFocusOutlineColor(const QPalette &palette) const
0190 {
0191     return KColorUtils::mix(buttonFocusColor(palette), palette.color(QPalette::ButtonText), 0.15);
0192 }
0193 
0194 //____________________________________________________________________
0195 QColor Helper::buttonHoverOutlineColor(const QPalette &palette) const
0196 {
0197     return KColorUtils::mix(buttonHoverColor(palette), palette.color(QPalette::ButtonText), 0.15);
0198 }
0199 
0200 //____________________________________________________________________
0201 QColor Helper::sidePanelOutlineColor(const QPalette &palette, bool hasFocus, qreal opacity, AnimationMode mode) const
0202 {
0203     QColor outline(palette.color(QPalette::Inactive, QPalette::Highlight));
0204     const QColor &focus = palette.color(QPalette::Active, QPalette::Highlight);
0205 
0206     if (mode == AnimationFocus) {
0207         outline = KColorUtils::mix(outline, focus, opacity);
0208 
0209     } else if (hasFocus) {
0210         outline = focus;
0211     }
0212 
0213     return outline;
0214 }
0215 
0216 //____________________________________________________________________
0217 QColor Helper::frameBackgroundColor(const QPalette &palette, QPalette::ColorGroup group) const
0218 {
0219     return KColorUtils::mix(palette.color(group, QPalette::Window), palette.color(group, QPalette::Base), 0.3);
0220 }
0221 
0222 //____________________________________________________________________
0223 QColor Helper::arrowColor(const QPalette &palette, QPalette::ColorGroup group, QPalette::ColorRole role) const
0224 {
0225     switch (role) {
0226     case QPalette::Text:
0227         return KColorUtils::mix(palette.color(group, QPalette::Text), palette.color(group, QPalette::Base), arrowShade);
0228     case QPalette::WindowText:
0229         return KColorUtils::mix(palette.color(group, QPalette::WindowText), palette.color(group, QPalette::Window), arrowShade);
0230     case QPalette::ButtonText:
0231         return KColorUtils::mix(palette.color(group, QPalette::ButtonText), palette.color(group, QPalette::Button), arrowShade);
0232     default:
0233         return palette.color(group, role);
0234     }
0235 }
0236 
0237 //____________________________________________________________________
0238 QColor Helper::arrowColor(const QPalette &palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode) const
0239 {
0240     QColor outline(arrowColor(palette, QPalette::WindowText));
0241     if (mode == AnimationHover) {
0242         const QColor focus(focusColor(palette));
0243         const QColor hover(hoverColor(palette));
0244         if (hasFocus) {
0245             outline = KColorUtils::mix(focus, hover, opacity);
0246         } else {
0247             outline = KColorUtils::mix(outline, hover, opacity);
0248         }
0249 
0250     } else if (mouseOver) {
0251         outline = hoverColor(palette);
0252 
0253     } else if (mode == AnimationFocus) {
0254         const QColor focus(focusColor(palette));
0255         outline = KColorUtils::mix(outline, focus, opacity);
0256 
0257     } else if (hasFocus) {
0258         outline = focusColor(palette);
0259     }
0260 
0261     return outline;
0262 }
0263 
0264 //____________________________________________________________________
0265 QColor Helper::sliderOutlineColor(const QPalette &palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode) const
0266 {
0267     QColor outline(KColorUtils::mix(palette.color(QPalette::Button), palette.color(QPalette::ButtonText), Metrics::Bias_Default));
0268 
0269     // hover takes precedence over focus
0270     if (mode == AnimationHover) {
0271         const QColor hover(hoverColor(palette));
0272         const QColor focus(focusColor(palette));
0273         if (hasFocus) {
0274             outline = KColorUtils::mix(focus, hover, opacity);
0275         } else {
0276             outline = KColorUtils::mix(outline, hover, opacity);
0277         }
0278 
0279     } else if (mouseOver) {
0280         outline = hoverColor(palette);
0281 
0282     } else if (mode == AnimationFocus) {
0283         const QColor focus(focusColor(palette));
0284         outline = KColorUtils::mix(outline, focus, opacity);
0285 
0286     } else if (hasFocus) {
0287         outline = focusColor(palette);
0288     }
0289 
0290     return outline;
0291 }
0292 
0293 //____________________________________________________________________
0294 QColor Helper::scrollBarHandleColor(const QPalette &palette, bool mouseOver, bool hasFocus, qreal opacity, AnimationMode mode) const
0295 {
0296     QColor color(alphaColor(palette.color(QPalette::WindowText), 0.5));
0297 
0298     // hover takes precedence over focus
0299     if (mode == AnimationHover) {
0300         const QColor hover(hoverColor(palette));
0301         const QColor focus(focusColor(palette));
0302         if (hasFocus) {
0303             color = KColorUtils::mix(focus, hover, opacity);
0304         } else {
0305             color = KColorUtils::mix(color, hover, opacity);
0306         }
0307 
0308     } else if (mouseOver) {
0309         color = hoverColor(palette);
0310 
0311     } else if (mode == AnimationFocus) {
0312         const QColor focus(focusColor(palette));
0313         color = KColorUtils::mix(color, focus, opacity);
0314 
0315     } else if (hasFocus) {
0316         color = focusColor(palette);
0317     }
0318 
0319     return color;
0320 }
0321 
0322 //______________________________________________________________________________
0323 QColor Helper::checkBoxIndicatorColor(const QPalette &palette, bool mouseOver, bool active, qreal opacity, AnimationMode mode) const
0324 {
0325     QColor color(KColorUtils::mix(palette.color(QPalette::Window), palette.color(QPalette::WindowText), 0.6));
0326     if (mode == AnimationHover) {
0327         const QColor focus(focusColor(palette));
0328         const QColor hover(hoverColor(palette));
0329         if (active) {
0330             color = KColorUtils::mix(focus, hover, opacity);
0331         } else {
0332             color = KColorUtils::mix(color, hover, opacity);
0333         }
0334 
0335     } else if (mouseOver) {
0336         color = hoverColor(palette);
0337 
0338     } else if (active) {
0339         color = focusColor(palette);
0340     }
0341 
0342     return color;
0343 }
0344 
0345 //______________________________________________________________________________
0346 QColor Helper::separatorColor(const QPalette &palette) const
0347 {
0348     return KColorUtils::mix(palette.color(QPalette::Window), palette.color(QPalette::WindowText), Metrics::Bias_Default);
0349 }
0350 
0351 //______________________________________________________________________________
0352 QPalette Helper::disabledPalette(const QPalette &source, qreal ratio) const
0353 {
0354     QPalette copy(source);
0355 
0356     const QList<QPalette::ColorRole> roles =
0357         {QPalette::Window, QPalette::Highlight, QPalette::WindowText, QPalette::ButtonText, QPalette::Text, QPalette::Button};
0358     for (const QPalette::ColorRole &role : roles) {
0359         copy.setColor(role, KColorUtils::mix(source.color(QPalette::Active, role), source.color(QPalette::Disabled, role), 1.0 - ratio));
0360     }
0361 
0362     return copy;
0363 }
0364 
0365 //____________________________________________________________________
0366 QColor Helper::alphaColor(QColor color, qreal alpha) const
0367 {
0368     if (alpha >= 0 && alpha < 1.0) {
0369         color.setAlphaF(alpha * color.alphaF());
0370     }
0371     return color;
0372 }
0373 
0374 //______________________________________________________________________________
0375 void Helper::renderDebugFrame(QPainter *painter, const QRectF &rect) const
0376 {
0377     painter->save();
0378     painter->setRenderHints(QPainter::Antialiasing);
0379     painter->setBrush(Qt::NoBrush);
0380     painter->setPen(Qt::red);
0381     painter->drawRect(strokedRect(rect));
0382     painter->restore();
0383 }
0384 
0385 //______________________________________________________________________________
0386 void Helper::renderFocusRect(QPainter *painter, const QRectF &rect, const QColor &color, const QColor &outline, Sides sides) const
0387 {
0388     if (!color.isValid()) {
0389         return;
0390     }
0391 
0392     painter->save();
0393     painter->setRenderHints(QPainter::Antialiasing);
0394     painter->setBrush(color);
0395 
0396     if (!(outline.isValid() && sides)) {
0397         painter->setPen(Qt::NoPen);
0398         painter->drawRect(rect);
0399 
0400     } else {
0401         painter->setClipRect(rect);
0402 
0403         QRectF copy(strokedRect(rect));
0404 
0405         const qreal radius(frameRadius(PenWidth::Frame));
0406         if (!(sides & SideTop)) {
0407             copy.adjust(0, -radius, 0, 0);
0408         }
0409         if (!(sides & SideBottom)) {
0410             copy.adjust(0, 0, 0, radius);
0411         }
0412         if (!(sides & SideLeft)) {
0413             copy.adjust(-radius, 0, 0, 0);
0414         }
0415         if (!(sides & SideRight)) {
0416             copy.adjust(0, 0, radius, 0);
0417         }
0418 
0419         painter->setPen(outline);
0420         // painter->setBrush( Qt::NoBrush );
0421         painter->drawRoundedRect(copy, radius, radius);
0422     }
0423 
0424     painter->restore();
0425 }
0426 
0427 //______________________________________________________________________________
0428 void Helper::renderFocusLine(QPainter *painter, const QRectF &rect, const QColor &color) const
0429 {
0430     if (!color.isValid()) {
0431         return;
0432     }
0433 
0434     painter->save();
0435     painter->setRenderHint(QPainter::Antialiasing, false);
0436     painter->setBrush(Qt::NoBrush);
0437     painter->setPen(color);
0438 
0439     painter->translate(0, 2);
0440     painter->drawLine(rect.bottomLeft(), rect.bottomRight());
0441     painter->restore();
0442 }
0443 
0444 //______________________________________________________________________________
0445 void Helper::renderFrameWithSides(QPainter *painter, const QRectF &rect, const QColor &color, Qt::Edges edges, const QColor &outline) const
0446 {
0447     painter->save();
0448 
0449     painter->setRenderHint(QPainter::Antialiasing);
0450 
0451     QRectF frameRect(rect);
0452 
0453     // set brush
0454     painter->setBrush(color);
0455     painter->setPen(Qt::NoPen);
0456 
0457     // render
0458     painter->drawRect(frameRect);
0459 
0460     // set brush again
0461     painter->setBrush(Qt::NoBrush);
0462     painter->setPen(outline);
0463 
0464     // manually apply the effects of StrokedRect here but only to the edges with a frame
0465     if (edges & Qt::LeftEdge) {
0466         frameRect.adjust(0.5, 0.0, 0.0, 0.0);
0467     }
0468     if (edges & Qt::RightEdge) {
0469         frameRect.adjust(0.0, 0, -0.5, 0.0);
0470     }
0471     if (edges & Qt::TopEdge) {
0472         frameRect.adjust(0.0, 0.5, 0.0, 0.0);
0473     }
0474     if (edges & Qt::BottomEdge) {
0475         frameRect.adjust(0.0, 0.0, 0.0, -0.5);
0476     }
0477 
0478     // draw lines
0479     if (edges & Qt::LeftEdge) {
0480         painter->drawLine(QLineF(frameRect.topLeft(), frameRect.bottomLeft()));
0481     }
0482     if (edges & Qt::RightEdge) {
0483         painter->drawLine(QLineF(frameRect.topRight(), frameRect.bottomRight()));
0484     }
0485     if (edges & Qt::TopEdge) {
0486         painter->drawLine(QLineF(frameRect.topLeft(), frameRect.topRight()));
0487     }
0488     if (edges & Qt::BottomEdge) {
0489         painter->drawLine(QLineF(frameRect.bottomLeft(), frameRect.bottomRight()));
0490     }
0491 
0492     painter->restore();
0493 }
0494 
0495 //______________________________________________________________________________
0496 void Helper::renderFrame(QPainter *painter, const QRectF &rect, const QColor &color, const QColor &outline) const
0497 {
0498     painter->setRenderHint(QPainter::Antialiasing);
0499 
0500     QRectF frameRect(rect);
0501     qreal radius(frameRadius(PenWidth::NoPen));
0502 
0503     // set pen
0504     if (outline.isValid()) {
0505         painter->setPen(outline);
0506         frameRect = strokedRect(frameRect);
0507         radius = frameRadiusForNewPenWidth(radius, PenWidth::Frame);
0508 
0509     } else {
0510         painter->setPen(Qt::NoPen);
0511     }
0512 
0513     // set brush
0514     if (color.isValid()) {
0515         painter->setBrush(color);
0516     } else {
0517         painter->setBrush(Qt::NoBrush);
0518     }
0519 
0520     // render
0521     painter->drawRoundedRect(frameRect, radius, radius);
0522 }
0523 
0524 //______________________________________________________________________________
0525 void Helper::renderSidePanelFrame(QPainter *painter, const QRectF &rect, const QColor &outline, Side side) const
0526 {
0527     // check color
0528     if (!outline.isValid()) {
0529         return;
0530     }
0531 
0532     // adjust rect
0533     QRectF frameRect(strokedRect(rect));
0534 
0535     // setup painter
0536     painter->setRenderHint(QPainter::Antialiasing);
0537     painter->setPen(outline);
0538 
0539     // render
0540     switch (side) {
0541     default:
0542     case SideLeft:
0543         painter->drawLine(frameRect.topRight(), frameRect.bottomRight());
0544         break;
0545 
0546     case SideTop:
0547         painter->drawLine(frameRect.topLeft(), frameRect.topRight());
0548         break;
0549 
0550     case SideRight:
0551         painter->drawLine(frameRect.topLeft(), frameRect.bottomLeft());
0552         break;
0553 
0554     case SideBottom:
0555         painter->drawLine(frameRect.bottomLeft(), frameRect.bottomRight());
0556         break;
0557 
0558     case AllSides: {
0559         const qreal radius(frameRadius(PenWidth::Frame));
0560         painter->drawRoundedRect(frameRect, radius, radius);
0561         break;
0562     }
0563     }
0564 }
0565 
0566 //______________________________________________________________________________
0567 void Helper::renderMenuFrame(QPainter *painter, const QRectF &rect, const QColor &color, const QColor &outline, bool roundCorners, Qt::Edges seamlessEdges)
0568     const
0569 {
0570     painter->save();
0571 
0572     // set brush
0573     if (color.isValid()) {
0574         painter->setBrush(color);
0575     } else {
0576         painter->setBrush(Qt::NoBrush);
0577     }
0578 
0579     // We simulate being able to independently adjust corner radii by
0580     // setting a clip region and then extending the rectangle beyond it.
0581     if (seamlessEdges != Qt::Edges()) {
0582         painter->setClipRect(rect);
0583     }
0584 
0585     if (roundCorners) {
0586         painter->setRenderHint(QPainter::Antialiasing);
0587         QRectF frameRect(rect);
0588         qreal radius(frameRadius(PenWidth::NoPen));
0589 
0590         frameRect.adjust( //
0591             seamlessEdges.testFlag(Qt::LeftEdge) ? -radius : 0,
0592             seamlessEdges.testFlag(Qt::TopEdge) ? -radius : 0,
0593             seamlessEdges.testFlag(Qt::RightEdge) ? radius : 0,
0594             seamlessEdges.testFlag(Qt::BottomEdge) ? radius : 0);
0595 
0596         // set pen
0597         if (outline.isValid()) {
0598             painter->setPen(outline);
0599             frameRect = strokedRect(frameRect);
0600             radius = frameRadiusForNewPenWidth(radius, PenWidth::Frame);
0601 
0602         } else {
0603             painter->setPen(Qt::NoPen);
0604         }
0605 
0606         // render
0607         painter->drawRoundedRect(frameRect, radius, radius);
0608 
0609     } else {
0610         painter->setRenderHint(QPainter::Antialiasing, false);
0611         QRectF frameRect(rect);
0612 
0613         frameRect.adjust( //
0614             seamlessEdges.testFlag(Qt::LeftEdge) ? 1 : 0,
0615             seamlessEdges.testFlag(Qt::TopEdge) ? 1 : 0,
0616             seamlessEdges.testFlag(Qt::RightEdge) ? -1 : 0,
0617             seamlessEdges.testFlag(Qt::BottomEdge) ? -1 : 0);
0618 
0619         if (outline.isValid()) {
0620             painter->setPen(outline);
0621             frameRect.adjust(0, 0, -1, -1);
0622 
0623         } else {
0624             painter->setPen(Qt::NoPen);
0625         }
0626 
0627         painter->drawRect(frameRect);
0628     }
0629 
0630     painter->restore();
0631 }
0632 
0633 //______________________________________________________________________________
0634 void Helper::renderButtonFrame(QPainter *painter,
0635                                const QRectF &rect,
0636                                const QPalette &palette,
0637                                const QHash<QByteArray, bool> &stateProperties,
0638                                qreal bgAnimation,
0639                                qreal penAnimation) const
0640 {
0641     bool enabled = stateProperties.value("enabled", true);
0642     bool visualFocus = stateProperties.value("visualFocus");
0643     bool hovered = stateProperties.value("hovered");
0644     bool down = stateProperties.value("down");
0645     bool checked = stateProperties.value("checked");
0646     bool flat = stateProperties.value("flat");
0647     bool defaultButton = stateProperties.value("defaultButton");
0648     bool hasNeutralHighlight = stateProperties.value("hasNeutralHighlight");
0649     bool isActiveWindow = stateProperties.value("isActiveWindow");
0650 
0651     // don't render background if flat and not hovered, down, checked, or given visual focus
0652     if (flat && !(hovered || down || checked || visualFocus) && bgAnimation == AnimationData::OpacityInvalid && penAnimation == AnimationData::OpacityInvalid) {
0653         return;
0654     }
0655 
0656     QRectF shadowedRect = this->shadowedRect(rect);
0657     QRectF frameRect = strokedRect(shadowedRect);
0658     qreal radius = frameRadius(PenWidth::Frame);
0659     // setting color group to work around KColorScheme feature
0660     const QColor &highlightColor = palette.color(!enabled ? QPalette::Disabled : QPalette::Active, QPalette::Highlight);
0661     QBrush bgBrush;
0662     QBrush penBrush;
0663 
0664     // Colors
0665     if (flat) {
0666         if (down && enabled) {
0667             bgBrush = alphaColor(highlightColor, highlightBackgroundAlpha);
0668         } else if (checked) {
0669             bgBrush = hasNeutralHighlight ? alphaColor(neutralText(palette), highlightBackgroundAlpha) : alphaColor(palette.buttonText().color(), 0.125);
0670             penBrush =
0671                 hasNeutralHighlight ? neutralText(palette) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), Metrics::Bias_Default);
0672         } else if (isActiveWindow && defaultButton) {
0673             bgBrush = alphaColor(highlightColor, 0.125);
0674             penBrush = KColorUtils::mix(highlightColor, KColorUtils::mix(palette.button().color(), palette.buttonText().color(), Metrics::Bias_Default), 0.5);
0675         } else {
0676             bgBrush = alphaColor(highlightColor, 0);
0677             penBrush = hasNeutralHighlight ? neutralText(palette) : bgBrush;
0678         }
0679     } else {
0680         if (down && enabled) {
0681             bgBrush = KColorUtils::mix(palette.button().color(), highlightColor, 0.333);
0682         } else if (checked) {
0683             bgBrush = hasNeutralHighlight ? KColorUtils::mix(palette.button().color(), neutralText(palette), 0.333)
0684                                           : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), 0.125);
0685             penBrush =
0686                 hasNeutralHighlight ? neutralText(palette) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), Metrics::Bias_Default);
0687         } else if (isActiveWindow && defaultButton) {
0688             bgBrush = KColorUtils::mix(palette.button().color(), highlightColor, 0.2);
0689             penBrush = KColorUtils::mix(highlightColor, KColorUtils::mix(palette.button().color(), palette.buttonText().color(), Metrics::Bias_Default), 0.5);
0690         } else {
0691             bgBrush = palette.button().color();
0692             penBrush =
0693                 hasNeutralHighlight ? neutralText(palette) : KColorUtils::mix(palette.button().color(), palette.buttonText().color(), Metrics::Bias_Default);
0694         }
0695     }
0696 
0697     if ((hovered || visualFocus || down) && enabled) {
0698         penBrush = highlightColor;
0699     }
0700 
0701     // Animations
0702     if (bgAnimation != AnimationData::OpacityInvalid && enabled) {
0703         QColor color1 = bgBrush.color();
0704         QColor color2 = flat ? alphaColor(highlightColor, highlightBackgroundAlpha) : KColorUtils::mix(palette.button().color(), highlightColor, 0.333);
0705         bgBrush = KColorUtils::mix(color1, color2, bgAnimation);
0706     }
0707     if (penAnimation != AnimationData::OpacityInvalid && enabled) {
0708         QColor color1 = penBrush.color();
0709         QColor color2 = highlightColor;
0710         penBrush = KColorUtils::mix(color1, color2, penAnimation);
0711     }
0712 
0713     // Shadow
0714     if (isActiveWindow && !(flat || down || checked) && enabled) {
0715         renderRoundedRectShadow(painter, shadowedRect, shadowColor(palette));
0716     }
0717 
0718     // Render button
0719     painter->setRenderHint(QPainter::Antialiasing, true);
0720     painter->setBrush(bgBrush);
0721     painter->setPen(QPen(penBrush, PenWidth::Frame));
0722     painter->drawRoundedRect(frameRect, radius, radius);
0723 }
0724 
0725 //______________________________________________________________________________
0726 void Helper::renderToolBoxFrame(QPainter *painter, const QRectF &rect, int tabWidth, const QColor &outline) const
0727 {
0728     if (!outline.isValid()) {
0729         return;
0730     }
0731 
0732     // round radius
0733     const qreal radius(frameRadius(PenWidth::Frame));
0734     const QSizeF cornerSize(2 * radius, 2 * radius);
0735 
0736     // if rect - tabwidth is even, need to increase tabWidth by 1 unit
0737     // for anti aliasing
0738     if (!((rect.toRect().width() - tabWidth) % 2)) {
0739         ++tabWidth;
0740     }
0741 
0742     // adjust rect for antialiasing
0743     QRectF baseRect(strokedRect(rect));
0744 
0745     // create path
0746     QPainterPath path;
0747     path.moveTo(0, baseRect.height() - 1);
0748     path.lineTo((baseRect.width() - tabWidth) / 2 - radius, baseRect.height() - 1);
0749     path.arcTo(QRectF(QPointF((baseRect.width() - tabWidth) / 2 - 2 * radius, baseRect.height() - 1 - 2 * radius), cornerSize), 270, 90);
0750     path.lineTo((baseRect.width() - tabWidth) / 2, radius);
0751     path.arcTo(QRectF(QPointF((baseRect.width() - tabWidth) / 2, 0), cornerSize), 180, -90);
0752     path.lineTo((baseRect.width() + tabWidth) / 2 - 1 - radius, 0);
0753     path.arcTo(QRectF(QPointF((baseRect.width() + tabWidth) / 2 - 1 - 2 * radius, 0), cornerSize), 90, -90);
0754     path.lineTo((baseRect.width() + tabWidth) / 2 - 1, baseRect.height() - 1 - radius);
0755     path.arcTo(QRectF(QPointF((baseRect.width() + tabWidth) / 2 - 1, baseRect.height() - 1 - 2 * radius), cornerSize), 180, 90);
0756     path.lineTo(baseRect.width() - 1, baseRect.height() - 1);
0757 
0758     // render
0759     painter->setRenderHints(QPainter::Antialiasing);
0760     painter->setBrush(Qt::NoBrush);
0761     painter->setPen(outline);
0762     painter->translate(baseRect.topLeft());
0763     painter->drawPath(path);
0764 }
0765 
0766 //______________________________________________________________________________
0767 void Helper::renderTabWidgetFrame(QPainter *painter, const QRectF &rect, const QColor &color, const QColor &outline, Corners corners) const
0768 {
0769     painter->setRenderHint(QPainter::Antialiasing);
0770 
0771     QRectF frameRect(rect.adjusted(1, 1, -1, -1));
0772     qreal radius(frameRadius(PenWidth::NoPen));
0773 
0774     // set pen
0775     if (outline.isValid()) {
0776         painter->setPen(outline);
0777         frameRect = strokedRect(frameRect);
0778         radius = frameRadiusForNewPenWidth(radius, PenWidth::Frame);
0779 
0780     } else {
0781         painter->setPen(Qt::NoPen);
0782     }
0783 
0784     // set brush
0785     if (color.isValid()) {
0786         painter->setBrush(color);
0787     } else {
0788         painter->setBrush(Qt::NoBrush);
0789     }
0790 
0791     // render
0792     QPainterPath path(roundedPath(frameRect, corners, radius));
0793     painter->drawPath(path);
0794 }
0795 
0796 //______________________________________________________________________________
0797 void Helper::renderSelection(QPainter *painter, const QRectF &rect, const QColor &color) const
0798 {
0799     painter->setRenderHint(QPainter::Antialiasing);
0800     painter->setPen(Qt::NoPen);
0801     painter->setBrush(color);
0802     painter->drawRect(rect);
0803 }
0804 
0805 //______________________________________________________________________________
0806 void Helper::renderSeparator(QPainter *painter, const QRectF &rect, const QColor &color, bool vertical) const
0807 {
0808     painter->setRenderHint(QPainter::Antialiasing, false);
0809     painter->setBrush(Qt::NoBrush);
0810     painter->setPen(color);
0811 
0812     if (vertical) {
0813         painter->translate(rect.width() / 2, 0);
0814         painter->drawLine(rect.topLeft(), rect.bottomLeft());
0815 
0816     } else {
0817         painter->translate(0, rect.height() / 2);
0818         painter->drawLine(rect.topLeft(), rect.topRight());
0819     }
0820 }
0821 
0822 //______________________________________________________________________________
0823 void Helper::renderCheckBoxBackground(QPainter *painter,
0824                                       const QRectF &rect,
0825                                       const QPalette &palette,
0826                                       CheckBoxState state,
0827                                       bool neutalHighlight,
0828                                       bool sunken,
0829                                       qreal animation) const
0830 {
0831     // setup painter
0832     painter->setRenderHint(QPainter::Antialiasing, true);
0833 
0834     // copy rect
0835     QRectF frameRect(rect);
0836     frameRect.adjust(2, 2, -2, -2);
0837     frameRect = strokedRect(frameRect);
0838 
0839     auto transparent = neutalHighlight ? neutralText(palette) : palette.highlight().color();
0840     transparent.setAlphaF(highlightBackgroundAlpha);
0841 
0842     QBrush penBrush;
0843     if (neutalHighlight) {
0844         penBrush = neutralText(palette);
0845     } else if (state == CheckOn || state == CheckPartial) {
0846         penBrush = palette.highlight().color();
0847     } else {
0848         penBrush = separatorColor(palette);
0849     }
0850     painter->setPen(QPen(penBrush, PenWidth::Frame));
0851 
0852     const auto radius = Metrics::CheckBox_Radius;
0853 
0854     switch (state) {
0855     case CheckOff:
0856         painter->setBrush(palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100));
0857         painter->drawRoundedRect(frameRect, radius, radius);
0858         break;
0859 
0860     case CheckPartial:
0861     case CheckOn:
0862         painter->setBrush(transparent.darker(sunken ? radioCheckSunkenDarkeningFactor : 100));
0863         painter->drawRoundedRect(frameRect, radius, radius);
0864         break;
0865 
0866     case CheckAnimated:
0867         painter->setBrush(palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100));
0868         painter->drawRoundedRect(frameRect, radius, radius);
0869         painter->setBrush(transparent);
0870         painter->setOpacity(animation);
0871         painter->drawRoundedRect(frameRect, radius, radius);
0872         break;
0873     }
0874 }
0875 
0876 //______________________________________________________________________________
0877 void Helper::renderCheckBox(QPainter *painter,
0878                             const QRectF &rect,
0879                             const QPalette &palette,
0880                             bool mouseOver,
0881                             CheckBoxState state,
0882                             CheckBoxState target,
0883                             bool neutalHighlight,
0884                             bool sunken,
0885                             qreal animation,
0886                             qreal hoverAnimation) const
0887 {
0888     Q_UNUSED(sunken)
0889 
0890     // setup painter
0891     painter->setRenderHint(QPainter::Antialiasing, true);
0892 
0893     // copy rect and radius
0894     QRectF frameRect(rect);
0895     frameRect.adjust(2, 2, -2, -2);
0896 
0897     if (mouseOver) {
0898         painter->save();
0899 
0900         if (hoverAnimation != AnimationData::OpacityInvalid) {
0901             painter->setOpacity(hoverAnimation);
0902         }
0903 
0904         painter->setPen(QPen(neutalHighlight ? neutralText(palette).lighter() : focusColor(palette), PenWidth::Frame));
0905         painter->setBrush(Qt::NoBrush);
0906 
0907         painter->drawRoundedRect(frameRect.adjusted(0.5, 0.5, -0.5, -0.5), Metrics::CheckBox_Radius, Metrics::CheckBox_Radius);
0908 
0909         painter->restore();
0910     }
0911 
0912     // check
0913     auto leftPoint = frameRect.center();
0914     leftPoint.setX(frameRect.left() + 4);
0915 
0916     auto bottomPoint = frameRect.center();
0917     bottomPoint.setX(bottomPoint.x() - 1);
0918     bottomPoint.setY(frameRect.bottom() - 5);
0919 
0920     auto rightPoint = frameRect.center();
0921     rightPoint.setX(rightPoint.x() + 4.5);
0922     rightPoint.setY(frameRect.top() + 5.5);
0923 
0924     QPainterPath path;
0925     path.moveTo(leftPoint);
0926     path.lineTo(bottomPoint);
0927     path.lineTo(rightPoint);
0928 
0929     // dots
0930     auto centerDot = QRectF(frameRect.center(), QSize(2, 2));
0931     centerDot.adjust(-1, -1, -1, -1);
0932     auto leftDot = centerDot.adjusted(-4, 0, -4, 0);
0933     auto rightDot = centerDot.adjusted(4, 0, 4, 0);
0934 
0935     painter->setPen(Qt::transparent);
0936     painter->setBrush(Qt::transparent);
0937 
0938     auto checkPen = QPen(palette.text(), PenWidth::Frame * 2);
0939     checkPen.setJoinStyle(Qt::MiterJoin);
0940 
0941     switch (state) {
0942     case CheckOff:
0943         break;
0944     case CheckOn:
0945         painter->setPen(checkPen);
0946         painter->drawPath(path);
0947         break;
0948     case CheckPartial:
0949         painter->setBrush(palette.text());
0950         painter->drawRect(leftDot);
0951         painter->drawRect(centerDot);
0952         painter->drawRect(rightDot);
0953         break;
0954     case CheckAnimated:
0955         checkPen.setDashPattern({path.length() * animation, path.length()});
0956 
0957         switch (target) {
0958         case CheckOff:
0959             break;
0960         case CheckOn:
0961             painter->setPen(checkPen);
0962             painter->drawPath(path);
0963             break;
0964         case CheckPartial:
0965             if (animation >= 3.0 / 3.0) {
0966                 painter->drawRect(rightDot);
0967             }
0968             if (animation >= 2.0 / 3.0) {
0969                 painter->drawRect(centerDot);
0970             }
0971             if (animation >= 1.0 / 3.0) {
0972                 painter->drawRect(leftDot);
0973             }
0974             break;
0975         case CheckAnimated:
0976             break;
0977         }
0978         break;
0979     }
0980 }
0981 
0982 //______________________________________________________________________________
0983 void Helper::renderRadioButtonBackground(QPainter *painter,
0984                                          const QRectF &rect,
0985                                          const QPalette &palette,
0986                                          RadioButtonState state,
0987                                          bool neutalHighlight,
0988                                          bool sunken,
0989                                          qreal animation) const
0990 {
0991     // setup painter
0992     painter->setRenderHint(QPainter::Antialiasing, true);
0993 
0994     // copy rect
0995     QRectF frameRect(rect);
0996     frameRect.adjust(2, 2, -2, -2);
0997     frameRect.adjust(0.5, 0.5, -0.5, -0.5);
0998 
0999     auto transparent = neutalHighlight ? neutralText(palette) : palette.highlight().color();
1000     transparent.setAlphaF(highlightBackgroundAlpha);
1001 
1002     QBrush penBrush;
1003     if (neutalHighlight) {
1004         penBrush = neutralText(palette);
1005     } else if (state == RadioOn) {
1006         penBrush = palette.highlight().color();
1007     } else {
1008         penBrush = separatorColor(palette);
1009     }
1010     painter->setPen(QPen(penBrush, PenWidth::Frame));
1011 
1012     switch (state) {
1013     case RadioOff:
1014         painter->setBrush(palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100));
1015         painter->drawEllipse(frameRect);
1016         break;
1017     case RadioOn:
1018         painter->setBrush(transparent.darker(sunken ? radioCheckSunkenDarkeningFactor : 100));
1019         painter->drawEllipse(frameRect);
1020         break;
1021     case RadioAnimated:
1022         painter->setBrush(palette.base().color().darker(sunken ? radioCheckSunkenDarkeningFactor : 100));
1023         painter->drawEllipse(frameRect);
1024         painter->setBrush(transparent);
1025         painter->setOpacity(animation);
1026         painter->drawEllipse(frameRect);
1027         break;
1028     }
1029 }
1030 
1031 //______________________________________________________________________________
1032 void Helper::renderRadioButton(QPainter *painter,
1033                                const QRectF &rect,
1034                                const QPalette &palette,
1035                                bool mouseOver,
1036                                RadioButtonState state,
1037                                bool neutralHighlight,
1038                                bool sunken,
1039                                qreal animation,
1040                                qreal animationHover) const
1041 {
1042     Q_UNUSED(sunken)
1043 
1044     // copy rect
1045     QRectF frameRect(rect);
1046     frameRect.adjust(1, 1, -1, -1);
1047 
1048     if (mouseOver) {
1049         painter->save();
1050 
1051         if (animationHover != AnimationData::OpacityInvalid) {
1052             painter->setOpacity(animationHover);
1053         }
1054 
1055         painter->setPen(QPen(neutralHighlight ? neutralText(palette).lighter() : focusColor(palette), PenWidth::Frame));
1056         painter->setBrush(Qt::NoBrush);
1057 
1058         const QRectF contentRect(frameRect.adjusted(1, 1, -1, -1).adjusted(0.5, 0.5, -0.5, -0.5));
1059         painter->drawEllipse(contentRect);
1060 
1061         painter->restore();
1062     }
1063 
1064     painter->setBrush(palette.text());
1065     painter->setPen(Qt::NoPen);
1066 
1067     QRectF markerRect;
1068     markerRect = frameRect.adjusted(6, 6, -6, -6);
1069 
1070     qreal adjustFactor;
1071 
1072     // mark
1073     switch (state) {
1074     case RadioOn:
1075         painter->drawEllipse(markerRect);
1076 
1077         break;
1078     case RadioAnimated:
1079         adjustFactor = markerRect.height() * (1 - animation);
1080         markerRect.adjust(adjustFactor, adjustFactor, -adjustFactor, -adjustFactor);
1081         painter->drawEllipse(markerRect);
1082 
1083         break;
1084     default:
1085         break;
1086     }
1087 }
1088 
1089 //______________________________________________________________________________
1090 void Helper::renderSliderGroove(QPainter *painter, const QRectF &rect, const QColor &fg, const QColor &bg) const
1091 {
1092     // setup painter
1093     painter->setRenderHint(QPainter::Antialiasing, true);
1094 
1095     QRectF baseRect(rect);
1096     baseRect.adjust(0.5, 0.5, -0.5, -0.5);
1097     const qreal radius(0.5 * Metrics::Slider_GrooveThickness);
1098 
1099     // content
1100     // content
1101     if (fg.isValid()) {
1102         painter->setPen(QPen(transparentize(fg, Metrics::Bias_Default), PenWidth::Frame));
1103         painter->setBrush(KColorUtils::overlayColors(bg, alphaColor(fg, 0.7)));
1104         painter->drawRoundedRect(baseRect, radius, radius);
1105     }
1106 }
1107 
1108 //______________________________________________________________________________
1109 void Helper::renderDialGroove(QPainter *painter, const QRectF &rect, const QColor &fg, const QColor &bg, qreal first, qreal last) const
1110 {
1111     // setup painter
1112     painter->setRenderHint(QPainter::Antialiasing, true);
1113 
1114     const QRectF baseRect(rect);
1115 
1116     // content
1117     if (fg.isValid()) {
1118         const qreal penWidth(Metrics::Slider_GrooveThickness);
1119         const QRectF grooveRect(rect.adjusted(penWidth / 2, penWidth / 2, -penWidth / 2, -penWidth / 2));
1120 
1121         // setup angles
1122         const int angleStart(first * 180 * 16 / M_PI);
1123         const int angleSpan((last - first) * 180 * 16 / M_PI);
1124         const QPen bgPen(fg, penWidth, Qt::SolidLine, Qt::RoundCap);
1125         const QPen fgPen(transparentize(KColorUtils::overlayColors(bg, alphaColor(fg, 0.5)), Metrics::Bias_Default), penWidth - 2, Qt::SolidLine, Qt::RoundCap);
1126 
1127         // setup pen
1128         if (angleSpan != 0) {
1129             painter->setPen(bgPen);
1130             painter->setBrush(Qt::NoBrush);
1131             painter->drawArc(grooveRect, angleStart, angleSpan);
1132             painter->setPen(fgPen);
1133             painter->drawArc(grooveRect, angleStart, angleSpan);
1134         }
1135     }
1136 }
1137 
1138 //______________________________________________________________________________
1139 void Helper::initSliderStyleOption(const QSlider *slider, QStyleOptionSlider *option) const
1140 {
1141     option->initFrom(slider);
1142     option->subControls = QStyle::SC_None;
1143     option->activeSubControls = QStyle::SC_None;
1144     option->orientation = slider->orientation();
1145     option->maximum = slider->maximum();
1146     option->minimum = slider->minimum();
1147     option->tickPosition = slider->tickPosition();
1148     option->tickInterval = slider->tickInterval();
1149     option->upsideDown = (slider->orientation() == Qt::Horizontal) //
1150         ? (slider->invertedAppearance() != (option->direction == Qt::RightToLeft))
1151         : (!slider->invertedAppearance());
1152     option->direction = Qt::LeftToRight; // we use the upsideDown option instead
1153     option->sliderPosition = slider->sliderPosition();
1154     option->sliderValue = slider->value();
1155     option->singleStep = slider->singleStep();
1156     option->pageStep = slider->pageStep();
1157     if (slider->orientation() == Qt::Horizontal) {
1158         option->state |= QStyle::State_Horizontal;
1159     }
1160     // Can't fetch activeSubControls, because it's private API
1161 }
1162 
1163 //______________________________________________________________________________
1164 QRectF Helper::pathForSliderHandleFocusFrame(QPainterPath &focusFramePath, const QRectF &rect, int hmargin, int vmargin) const
1165 {
1166     // Mimics path and adjustments of renderSliderHandle
1167     QRectF frameRect(rect);
1168     frameRect.translate(hmargin, vmargin);
1169     frameRect.adjust(1, 1, -1, -1);
1170     frameRect = strokedRect(frameRect);
1171     focusFramePath.addEllipse(frameRect);
1172     frameRect.adjust(-hmargin, -vmargin, hmargin, vmargin);
1173     focusFramePath.addEllipse(frameRect);
1174     return frameRect;
1175 }
1176 
1177 //______________________________________________________________________________
1178 void Helper::renderSliderHandle(QPainter *painter, const QRectF &rect, const QColor &color, const QColor &outline, const QColor &shadow, bool sunken) const
1179 {
1180     // setup painter
1181     painter->setRenderHint(QPainter::Antialiasing, true);
1182 
1183     // copy rect
1184     QRectF frameRect(rect);
1185     frameRect.adjust(1, 1, -1, -1);
1186 
1187     // shadow
1188     if (!sunken) {
1189         renderEllipseShadow(painter, frameRect, shadow);
1190     }
1191 
1192     // set pen
1193     if (outline.isValid()) {
1194         painter->setPen(QPen(outline, PenWidth::Frame));
1195         frameRect = strokedRect(frameRect);
1196 
1197     } else {
1198         painter->setPen(Qt::NoPen);
1199     }
1200 
1201     // set brush
1202     if (color.isValid()) {
1203         painter->setBrush(color);
1204     } else {
1205         painter->setBrush(Qt::NoBrush);
1206     }
1207 
1208     // render
1209     painter->drawEllipse(frameRect);
1210 }
1211 
1212 //______________________________________________________________________________
1213 void Helper::renderProgressBarGroove(QPainter *painter, const QRectF &rect, const QColor &fg, const QColor &bg) const
1214 {
1215     // setup painter
1216     painter->setRenderHint(QPainter::Antialiasing, true);
1217 
1218     QRectF baseRect(rect);
1219     baseRect.adjust(0.5, 0.5, -0.5, -0.5);
1220     const qreal radius(0.5 * Metrics::ProgressBar_Thickness);
1221 
1222     // content
1223     if (fg.isValid()) {
1224         painter->setPen(QPen(transparentize(fg, Metrics::Bias_Default), PenWidth::Frame));
1225         painter->setBrush(KColorUtils::overlayColors(bg, alphaColor(fg, 0.7)));
1226         painter->drawRoundedRect(baseRect, radius, radius);
1227     }
1228 }
1229 
1230 //______________________________________________________________________________
1231 void Helper::renderProgressBarBusyContents(QPainter *painter,
1232                                            const QRectF &rect,
1233                                            const QColor &first,
1234                                            const QColor &second,
1235                                            bool horizontal,
1236                                            bool reverse,
1237                                            int progress) const
1238 {
1239     // setup painter
1240     painter->setRenderHint(QPainter::Antialiasing, true);
1241 
1242     const QRectF baseRect(rect);
1243     const qreal radius(0.5 * Metrics::ProgressBar_Thickness);
1244 
1245     // setup brush
1246     QPixmap pixmap(horizontal ? 2 * Metrics::ProgressBar_BusyIndicatorSize : 1, horizontal ? 1 : 2 * Metrics::ProgressBar_BusyIndicatorSize);
1247     pixmap.fill(second);
1248     if (horizontal) {
1249         QPainter painter(&pixmap);
1250         painter.setBrush(first);
1251         painter.setPen(Qt::NoPen);
1252 
1253         progress %= 2 * Metrics::ProgressBar_BusyIndicatorSize;
1254         if (reverse) {
1255             progress = 2 * Metrics::ProgressBar_BusyIndicatorSize - progress - 1;
1256         }
1257         painter.drawRect(QRect(0, 0, Metrics::ProgressBar_BusyIndicatorSize, 1).translated(progress, 0));
1258 
1259         if (progress > Metrics::ProgressBar_BusyIndicatorSize) {
1260             painter.drawRect(QRect(0, 0, Metrics::ProgressBar_BusyIndicatorSize, 1).translated(progress - 2 * Metrics::ProgressBar_BusyIndicatorSize, 0));
1261         }
1262 
1263     } else {
1264         QPainter painter(&pixmap);
1265         painter.setBrush(first);
1266         painter.setPen(Qt::NoPen);
1267 
1268         progress %= 2 * Metrics::ProgressBar_BusyIndicatorSize;
1269         progress = 2 * Metrics::ProgressBar_BusyIndicatorSize - progress - 1;
1270         painter.drawRect(QRect(0, 0, 1, Metrics::ProgressBar_BusyIndicatorSize).translated(0, progress));
1271 
1272         if (progress > Metrics::ProgressBar_BusyIndicatorSize) {
1273             painter.drawRect(QRect(0, 0, 1, Metrics::ProgressBar_BusyIndicatorSize).translated(0, progress - 2 * Metrics::ProgressBar_BusyIndicatorSize));
1274         }
1275     }
1276 
1277     painter->setPen(Qt::NoPen);
1278     painter->setBrush(pixmap);
1279     painter->drawRoundedRect(baseRect, radius, radius);
1280 }
1281 
1282 //______________________________________________________________________________
1283 void Helper::renderScrollBarHandle(QPainter *painter, const QRectF &rect, const QColor &fg, const QColor &bg) const
1284 {
1285     // setup painter
1286     painter->setRenderHint(QPainter::Antialiasing, true);
1287 
1288     const QRectF baseRect(rect);
1289     const qreal radius(0.5 * std::min({baseRect.width(), baseRect.height(), (qreal)Metrics::ScrollBar_SliderWidth}));
1290 
1291     painter->setPen(Qt::NoPen);
1292     painter->setPen(QPen(transparentize(fg, Metrics::Bias_Default), 1.001));
1293     painter->setBrush(KColorUtils::overlayColors(bg, alphaColor(fg, 0.5)));
1294     painter->drawRoundedRect(strokedRect(baseRect), radius, radius);
1295 }
1296 
1297 void Helper::renderScrollBarGroove(QPainter *painter, const QRect &rect, const QColor &color) const
1298 {
1299     // check for negative size, possible with squeezed controls
1300     if (!rect.isValid()) {
1301         return;
1302     }
1303 
1304     // setup painter
1305     painter->setRenderHint(QPainter::Antialiasing, true);
1306 
1307     const QRectF baseRect(rect);
1308     const qreal radius(0.5 * std::min({baseRect.width(), baseRect.height(), (qreal)Metrics::ScrollBar_SliderWidth}));
1309 
1310     // content
1311     if (color.isValid()) {
1312         painter->setPen(Qt::NoPen);
1313         auto bg = color;
1314         bg.setAlphaF(bg.alphaF() / 2.0);
1315         painter->setBrush(bg);
1316         painter->setPen(QPen(color, 1.001));
1317         painter->drawRoundedRect(strokedRect(baseRect), radius, radius);
1318     }
1319 }
1320 
1321 //______________________________________________________________________________
1322 void Helper::renderScrollBarBorder(QPainter *painter, const QRectF &rect, const QColor &color) const
1323 {
1324     // content
1325     if (color.isValid()) {
1326         painter->setPen(Qt::NoPen);
1327         painter->setBrush(color);
1328         painter->drawRect(rect);
1329     }
1330 }
1331 
1332 //______________________________________________________________________________
1333 void Helper::renderTabBarTab(QPainter *painter,
1334                              const QRectF &rect,
1335                              const QPalette &palette,
1336                              const QHash<QByteArray, bool> &stateProperties,
1337                              Corners corners,
1338                              qreal animation) const
1339 {
1340     bool enabled = stateProperties.value("enabled", true);
1341     bool hovered = stateProperties.value("hovered");
1342     bool selected = stateProperties.value("selected");
1343     bool documentMode = stateProperties.value("documentMode");
1344     bool north = stateProperties.value("north");
1345     bool south = stateProperties.value("south");
1346     bool west = stateProperties.value("west");
1347     bool east = stateProperties.value("east");
1348     bool animated = animation != AnimationData::OpacityInvalid;
1349     bool isQtQuickControl = stateProperties.value("isQtQuickControl");
1350     bool hasAlteredBackground = stateProperties.value("hasAlteredBackground");
1351 
1352     // setup painter
1353     painter->setRenderHint(QPainter::Antialiasing, true);
1354     QRectF frameRect = rect;
1355     QColor bgBrush;
1356 
1357     if (selected) {
1358         // overlap border
1359         // This covers just enough of the border, so that both the border and it's
1360         // antialiasing effect is covered. On 100% scale it does nothing
1361         const qreal overlap = devicePixelRatio(painter) * devicePixelRatio(painter);
1362         frameRect.adjust(east ? -overlap : 0, south ? -overlap : 0, west ? overlap : 0, north ? overlap : 0);
1363 
1364         if (documentMode && !isQtQuickControl && !hasAlteredBackground) {
1365             bgBrush = palette.color(QPalette::Window);
1366         } else {
1367             bgBrush = frameBackgroundColor(palette);
1368         }
1369         QColor penBrush = KColorUtils::mix(bgBrush, palette.color(QPalette::WindowText), Metrics::Bias_Default);
1370         painter->setBrush(bgBrush);
1371         painter->setPen(QPen(penBrush, PenWidth::Frame));
1372         QRectF highlightRect = frameRect;
1373         if (north || south) {
1374             highlightRect.setHeight(Metrics::Frame_FrameRadius);
1375         } else if (west || east) {
1376             highlightRect.setWidth(Metrics::Frame_FrameRadius);
1377         }
1378         if (south) {
1379             highlightRect.moveBottom(frameRect.bottom());
1380         } else if (east) {
1381             highlightRect.moveRight(frameRect.right());
1382         }
1383         QPainterPath path = roundedPath(strokedRect(frameRect), corners, frameRadius(PenWidth::Frame));
1384         painter->drawPath(path);
1385         QPainterPath highlightPath = roundedPath(highlightRect, corners, Metrics::Frame_FrameRadius);
1386         painter->setBrush(palette.color(QPalette::Highlight));
1387         painter->setPen(Qt::NoPen);
1388         painter->drawPath(highlightPath);
1389     } else {
1390         // don't overlap border
1391         // Since we dont set the rectangle as strokedRect here, modify only one side of it
1392         // the same amount strokedRect method would, to make it snap next to the border
1393         const qreal overlap = PenWidth::Frame;
1394         frameRect.adjust(east ? overlap : 0, south ? overlap : 0, west ? -overlap : 0, north ? -overlap : 0);
1395 
1396         const auto windowColor = palette.color(QPalette::Window);
1397         bgBrush = windowColor.darker(120);
1398         const auto hover = alphaColor(hoverColor(palette), 0.2);
1399         if (animated) {
1400             bgBrush = KColorUtils::mix(bgBrush, hover, animation);
1401         } else if (enabled && hovered && !selected) {
1402             bgBrush = hover;
1403         }
1404         painter->setBrush(bgBrush);
1405         painter->setPen(Qt::NoPen);
1406         QPainterPath path = roundedPath(frameRect, corners, Metrics::Frame_FrameRadius);
1407         painter->drawPath(path);
1408     }
1409 }
1410 
1411 //______________________________________________________________________________
1412 void Helper::renderArrow(QPainter *painter, const QRectF &rect, const QColor &color, ArrowOrientation orientation) const
1413 {
1414     int size = std::min({rect.toRect().width(), rect.toRect().height(), Metrics::ArrowSize});
1415     // No point in trying to draw if it's too small
1416     if (size <= 0) {
1417         return;
1418     }
1419 
1420     qreal penOffset = PenWidth::Symbol / 2.0;
1421     qreal center = size / 2.0;
1422     qreal maxExtent = size * 0.75;
1423     qreal minExtent = size / 4.0;
1424     qreal sizeOffset = 0;
1425     int remainder = size % 4;
1426     if (remainder == 2) {
1427         sizeOffset = 0.5;
1428     } else if (remainder == 1) {
1429         sizeOffset = -0.25;
1430     } else if (remainder == 3) {
1431         sizeOffset = 0.25;
1432     }
1433 
1434     QPolygonF arrow;
1435     switch (orientation) {
1436     case ArrowUp:
1437         arrow = QVector<QPointF>{
1438             {penOffset, maxExtent - penOffset - sizeOffset}, // left
1439             {center, minExtent - sizeOffset}, // mid
1440             {size - penOffset, maxExtent - penOffset - sizeOffset} // right
1441         };
1442         break;
1443     case ArrowDown:
1444         arrow = QVector<QPointF>{
1445             {penOffset, minExtent + penOffset + sizeOffset}, // left
1446             {center, maxExtent + sizeOffset}, // mid
1447             {size - penOffset, minExtent + penOffset + sizeOffset} // right
1448         };
1449         break;
1450     case ArrowLeft:
1451         arrow = QVector<QPointF>{
1452             {maxExtent - penOffset - sizeOffset, penOffset}, // top
1453             {minExtent - sizeOffset, center}, // mid
1454             {maxExtent - penOffset - sizeOffset, size - penOffset}, // bottom
1455         };
1456         break;
1457     case ArrowRight:
1458         arrow = QVector<QPointF>{
1459             {minExtent + penOffset + sizeOffset, penOffset}, // top
1460             {maxExtent + sizeOffset, center}, // mid
1461             {minExtent + penOffset + sizeOffset, size - penOffset}, // bottom
1462         };
1463         break;
1464     default:
1465         break;
1466     }
1467 
1468     arrow.translate(rect.x() + (rect.width() - size) / 2.0, rect.y() + (rect.height() - size) / 2.0);
1469 
1470     painter->save();
1471     painter->setRenderHints(QPainter::Antialiasing);
1472     painter->setBrush(Qt::NoBrush);
1473     QPen pen(color, PenWidth::Symbol);
1474     pen.setCapStyle(Qt::SquareCap);
1475     pen.setJoinStyle(Qt::MiterJoin);
1476     painter->setPen(pen);
1477     painter->drawPolyline(arrow);
1478     painter->restore();
1479 }
1480 
1481 //______________________________________________________________________________
1482 void Helper::renderDecorationButton(QPainter *painter, const QRectF &rect, const QColor &color, ButtonType buttonType, bool inverted) const
1483 {
1484     painter->save();
1485     painter->setViewport(rect.toRect());
1486     painter->setWindow(0, 0, 18, 18);
1487     painter->setRenderHints(QPainter::Antialiasing);
1488 
1489     // initialize pen
1490     QPen pen;
1491     pen.setCapStyle(Qt::RoundCap);
1492     pen.setJoinStyle(Qt::MiterJoin);
1493 
1494     if (inverted) {
1495         // render circle
1496         painter->setPen(Qt::NoPen);
1497         painter->setBrush(color);
1498         painter->drawEllipse(QRectF(0, 0, 18, 18));
1499 
1500         // take out the inner part
1501         painter->setCompositionMode(QPainter::CompositionMode_DestinationOut);
1502         painter->setBrush(Qt::NoBrush);
1503         pen.setColor(Qt::black);
1504 
1505     } else {
1506         painter->setBrush(Qt::NoBrush);
1507         pen.setColor(color);
1508     }
1509 
1510     pen.setCapStyle(Qt::RoundCap);
1511     pen.setJoinStyle(Qt::MiterJoin);
1512     pen.setWidthF(PenWidth::Symbol * qMax(1.0, 18.0 / rect.width()));
1513     painter->setPen(pen);
1514 
1515     switch (buttonType) {
1516     case ButtonClose: {
1517         painter->drawLine(QPointF(5, 5), QPointF(13, 13));
1518         painter->drawLine(13, 5, 5, 13);
1519         break;
1520     }
1521 
1522     case ButtonMaximize: {
1523         painter->drawPolyline(QVector<QPointF>{QPointF(4, 11), QPointF(9, 6), QPointF(14, 11)});
1524         break;
1525     }
1526 
1527     case ButtonMinimize: {
1528         painter->drawPolyline(QVector<QPointF>{QPointF(4, 7), QPointF(9, 12), QPointF(14, 7)});
1529         break;
1530     }
1531 
1532     case ButtonRestore: {
1533         pen.setJoinStyle(Qt::RoundJoin);
1534         painter->setPen(pen);
1535         painter->drawPolygon(QVector<QPointF>{QPointF(4.5, 9), QPointF(9, 4.5), QPointF(13.5, 9), QPointF(9, 13.5)});
1536         break;
1537     }
1538 
1539     default:
1540         break;
1541     }
1542 
1543     painter->restore();
1544 }
1545 
1546 //______________________________________________________________________________
1547 void Helper::renderRoundedRectShadow(QPainter *painter, const QRectF &rect, const QColor &color, qreal radius) const
1548 {
1549     if (!color.isValid()) {
1550         return;
1551     }
1552 
1553     painter->setRenderHint(QPainter::Antialiasing, true);
1554 
1555     qreal adjustment = 0.5 * PenWidth::Shadow; // Translate for the pen
1556     QRectF shadowRect = rect.adjusted(adjustment, adjustment, -adjustment, adjustment);
1557 
1558     painter->setPen(QPen(color, PenWidth::Shadow));
1559     painter->setBrush(Qt::NoBrush);
1560     painter->drawRoundedRect(shadowRect, radius, radius);
1561 }
1562 
1563 //______________________________________________________________________________
1564 void Helper::renderEllipseShadow(QPainter *painter, const QRectF &rect, const QColor &color) const
1565 {
1566     if (!color.isValid()) {
1567         return;
1568     }
1569 
1570     painter->save();
1571 
1572     // Clipping does not improve performance here
1573 
1574     qreal adjustment = 0.5 * PenWidth::Shadow; // Adjust for the pen
1575 
1576     qreal radius = rect.width() / 2 - adjustment;
1577 
1578     /* The right side is offset by +0.5 for the visible part of the shadow.
1579      * The other sides are offset by +0.5 or -0.5 because of the pen.
1580      */
1581     QRectF shadowRect = rect.adjusted(adjustment, adjustment, adjustment, -adjustment);
1582 
1583     painter->translate(rect.center());
1584     painter->rotate(45);
1585     painter->translate(-rect.center());
1586     painter->setPen(color);
1587     painter->setBrush(Qt::NoBrush);
1588     painter->drawRoundedRect(shadowRect, radius, radius);
1589 
1590     painter->restore();
1591 }
1592 
1593 //______________________________________________________________________________
1594 bool Helper::isX11()
1595 {
1596     static const bool s_isX11 = KWindowSystem::isPlatformX11();
1597     return s_isX11;
1598 }
1599 
1600 //______________________________________________________________________________
1601 bool Helper::isWayland()
1602 {
1603     static const bool s_isWayland = KWindowSystem::isPlatformWayland();
1604     return s_isWayland;
1605 }
1606 
1607 //______________________________________________________________________________
1608 QRectF Helper::strokedRect(const QRectF &rect, const qreal penWidth) const
1609 {
1610     /* With a pen stroke width of 1, the rectangle should have each of its
1611      * sides moved inwards by half a pixel. This allows the stroke to be
1612      * pixel perfect instead of blurry from sitting between pixels and
1613      * prevents the rectangle with a stroke from becoming larger than the
1614      * original size of the rectangle.
1615      */
1616     qreal adjustment = 0.5 * penWidth;
1617     return rect.adjusted(adjustment, adjustment, -adjustment, -adjustment);
1618 }
1619 
1620 //______________________________________________________________________________
1621 QPainterPath Helper::roundedPath(const QRectF &rect, Corners corners, qreal radius) const
1622 {
1623     QPainterPath path;
1624 
1625     // simple cases
1626     if (corners == 0) {
1627         path.addRect(rect);
1628         return path;
1629     }
1630 
1631     if (corners == AllCorners) {
1632         path.addRoundedRect(rect, radius, radius);
1633         return path;
1634     }
1635 
1636     const QSizeF cornerSize(2 * radius, 2 * radius);
1637 
1638     // rotate counterclockwise
1639     // top left corner
1640     if (corners & CornerTopLeft) {
1641         path.moveTo(rect.topLeft() + QPointF(radius, 0));
1642         path.arcTo(QRectF(rect.topLeft(), cornerSize), 90, 90);
1643 
1644     } else {
1645         path.moveTo(rect.topLeft());
1646     }
1647 
1648     // bottom left corner
1649     if (corners & CornerBottomLeft) {
1650         path.lineTo(rect.bottomLeft() - QPointF(0, radius));
1651         path.arcTo(QRectF(rect.bottomLeft() - QPointF(0, 2 * radius), cornerSize), 180, 90);
1652 
1653     } else {
1654         path.lineTo(rect.bottomLeft());
1655     }
1656 
1657     // bottom right corner
1658     if (corners & CornerBottomRight) {
1659         path.lineTo(rect.bottomRight() - QPointF(radius, 0));
1660         path.arcTo(QRectF(rect.bottomRight() - QPointF(2 * radius, 2 * radius), cornerSize), 270, 90);
1661 
1662     } else {
1663         path.lineTo(rect.bottomRight());
1664     }
1665 
1666     // top right corner
1667     if (corners & CornerTopRight) {
1668         path.lineTo(rect.topRight() + QPointF(0, radius));
1669         path.arcTo(QRectF(rect.topRight() - QPointF(2 * radius, 0), cornerSize), 0, 90);
1670 
1671     } else {
1672         path.lineTo(rect.topRight());
1673     }
1674 
1675     path.closeSubpath();
1676     return path;
1677 }
1678 
1679 //________________________________________________________________________________________________________
1680 bool Helper::compositingActive() const
1681 {
1682     if (isX11()) {
1683 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1684         return KWindowSystem::compositingActive();
1685 #elif __has_include(<KX11Extras>)
1686         return KX11Extras::compositingActive();
1687 #endif
1688     }
1689 
1690     return true;
1691 }
1692 
1693 //____________________________________________________________________
1694 bool Helper::hasAlphaChannel(const QWidget *widget) const
1695 {
1696     return compositingActive() && widget && widget->testAttribute(Qt::WA_TranslucentBackground);
1697 }
1698 
1699 //______________________________________________________________________________________
1700 
1701 QPixmap Helper::coloredIcon(const QIcon &icon, const QPalette &palette, const QSize &size, qreal devicePixelRatio, QIcon::Mode mode, QIcon::State state)
1702 {
1703     const QPalette activePalette = KIconLoader::global()->customPalette();
1704     const bool changePalette = activePalette != palette;
1705     if (changePalette) {
1706         KIconLoader::global()->setCustomPalette(palette);
1707     }
1708 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1709     const QPixmap pixmap = icon.pixmap(size, devicePixelRatio, mode, state);
1710 #else
1711     Q_UNUSED(devicePixelRatio);
1712     const QPixmap pixmap = icon.pixmap(size, mode, state);
1713 #endif
1714     if (changePalette) {
1715         if (activePalette == QPalette()) {
1716             KIconLoader::global()->resetPalette();
1717         } else {
1718             KIconLoader::global()->setCustomPalette(activePalette);
1719         }
1720     }
1721     return pixmap;
1722 }
1723 
1724 bool Helper::shouldDrawToolsArea(const QWidget *widget) const
1725 {
1726     if (!widget) {
1727         return false;
1728     }
1729     static bool isAuto = false;
1730     static QString borderSize;
1731     if (!_cachedAutoValid) {
1732         KConfigGroup kdecorationGroup(_kwinConfig->group(QStringLiteral("org.kde.kdecoration2")));
1733         isAuto = kdecorationGroup.readEntry("BorderSizeAuto", true);
1734         borderSize = kdecorationGroup.readEntry("BorderSize", "Normal");
1735         _cachedAutoValid = true;
1736     }
1737     if (isAuto) {
1738         auto window = widget->window();
1739         if (qobject_cast<const QDialog *>(widget)) {
1740             return true;
1741         }
1742         if (window) {
1743             auto handle = window->windowHandle();
1744             if (handle) {
1745                 auto toolbar = qobject_cast<const QToolBar *>(widget);
1746                 if (toolbar) {
1747                     if (toolbar->isFloating()) {
1748                         return false;
1749                     }
1750                 }
1751                 return true;
1752             }
1753         } else {
1754             return false;
1755         }
1756     }
1757     if (borderSize != "None" && borderSize != "NoSides") {
1758         return false;
1759     }
1760     return true;
1761 }
1762 
1763 Qt::Edges Helper::menuSeamlessEdges(const QWidget *widget)
1764 {
1765     if (widget) {
1766         auto edges = widget->property(PropertyNames::menuSeamlessEdges).value<Qt::Edges>();
1767         // Fallback to older property
1768         if (edges == Qt::Edges() && widget->property(PropertyNames::isTopMenu).toBool()) {
1769             edges = Qt::TopEdge;
1770         }
1771         return edges;
1772     }
1773     return Qt::Edges();
1774 }
1775 
1776 qreal Helper::devicePixelRatio(QPainter *painter) const
1777 {
1778     return painter->device() ? painter->device()->devicePixelRatioF() : qApp->devicePixelRatio();
1779 }
1780 }