File indexing completed on 2024-04-28 05:26:21

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 //______________________________________________________________________________
1298 void Helper::renderScrollBarBorder(QPainter *painter, const QRectF &rect, const QColor &color) const
1299 {
1300     // content
1301     if (color.isValid()) {
1302         painter->setPen(Qt::NoPen);
1303         painter->setBrush(color);
1304         painter->drawRect(rect);
1305     }
1306 }
1307 
1308 //______________________________________________________________________________
1309 void Helper::renderTabBarTab(QPainter *painter,
1310                              const QRectF &rect,
1311                              const QPalette &palette,
1312                              const QHash<QByteArray, bool> &stateProperties,
1313                              Corners corners,
1314                              qreal animation) const
1315 {
1316     bool enabled = stateProperties.value("enabled", true);
1317     bool hovered = stateProperties.value("hovered");
1318     bool selected = stateProperties.value("selected");
1319     bool documentMode = stateProperties.value("documentMode");
1320     bool north = stateProperties.value("north");
1321     bool south = stateProperties.value("south");
1322     bool west = stateProperties.value("west");
1323     bool east = stateProperties.value("east");
1324     bool animated = animation != AnimationData::OpacityInvalid;
1325     bool isQtQuickControl = stateProperties.value("isQtQuickControl");
1326     bool hasAlteredBackground = stateProperties.value("hasAlteredBackground");
1327 
1328     // setup painter
1329     painter->setRenderHint(QPainter::Antialiasing, true);
1330     QRectF frameRect = rect;
1331     QColor bgBrush;
1332 
1333     if (selected) {
1334         // overlap border
1335         // This covers just enough of the border, so that both the border and it's
1336         // antialiasing effect is covered. On 100% scale it does nothing
1337         const qreal overlap = devicePixelRatio(painter) * devicePixelRatio(painter);
1338         frameRect.adjust(east ? -overlap : 0, south ? -overlap : 0, west ? overlap : 0, north ? overlap : 0);
1339 
1340         if (documentMode && !isQtQuickControl && !hasAlteredBackground) {
1341             bgBrush = palette.color(QPalette::Window);
1342         } else {
1343             bgBrush = frameBackgroundColor(palette);
1344         }
1345         QColor penBrush = KColorUtils::mix(bgBrush, palette.color(QPalette::WindowText), Metrics::Bias_Default);
1346         painter->setBrush(bgBrush);
1347         painter->setPen(QPen(penBrush, PenWidth::Frame));
1348         QRectF highlightRect = frameRect;
1349         if (north || south) {
1350             highlightRect.setHeight(Metrics::Frame_FrameRadius);
1351         } else if (west || east) {
1352             highlightRect.setWidth(Metrics::Frame_FrameRadius);
1353         }
1354         if (south) {
1355             highlightRect.moveBottom(frameRect.bottom());
1356         } else if (east) {
1357             highlightRect.moveRight(frameRect.right());
1358         }
1359         QPainterPath path = roundedPath(strokedRect(frameRect), corners, frameRadius(PenWidth::Frame));
1360         painter->drawPath(path);
1361         QPainterPath highlightPath = roundedPath(highlightRect, corners, Metrics::Frame_FrameRadius);
1362         painter->setBrush(palette.color(QPalette::Highlight));
1363         painter->setPen(Qt::NoPen);
1364         painter->drawPath(highlightPath);
1365     } else {
1366         // don't overlap border
1367         // Since we dont set the rectangle as strokedRect here, modify only one side of it
1368         // the same amount strokedRect method would, to make it snap next to the border
1369         const qreal overlap = PenWidth::Frame;
1370         frameRect.adjust(east ? overlap : 0, south ? overlap : 0, west ? -overlap : 0, north ? -overlap : 0);
1371 
1372         const auto windowColor = palette.color(QPalette::Window);
1373         bgBrush = windowColor.darker(120);
1374         const auto hover = alphaColor(hoverColor(palette), 0.2);
1375         if (animated) {
1376             bgBrush = KColorUtils::mix(bgBrush, hover, animation);
1377         } else if (enabled && hovered && !selected) {
1378             bgBrush = hover;
1379         }
1380         painter->setBrush(bgBrush);
1381         painter->setPen(Qt::NoPen);
1382         QPainterPath path = roundedPath(frameRect, corners, Metrics::Frame_FrameRadius);
1383         painter->drawPath(path);
1384     }
1385 }
1386 
1387 //______________________________________________________________________________
1388 void Helper::renderArrow(QPainter *painter, const QRectF &rect, const QColor &color, ArrowOrientation orientation) const
1389 {
1390     int size = std::min({rect.toRect().width(), rect.toRect().height(), Metrics::ArrowSize});
1391     // No point in trying to draw if it's too small
1392     if (size <= 0) {
1393         return;
1394     }
1395 
1396     qreal penOffset = PenWidth::Symbol / 2.0;
1397     qreal center = size / 2.0;
1398     qreal maxExtent = size * 0.75;
1399     qreal minExtent = size / 4.0;
1400     qreal sizeOffset = 0;
1401     int remainder = size % 4;
1402     if (remainder == 2) {
1403         sizeOffset = 0.5;
1404     } else if (remainder == 1) {
1405         sizeOffset = -0.25;
1406     } else if (remainder == 3) {
1407         sizeOffset = 0.25;
1408     }
1409 
1410     QPolygonF arrow;
1411     switch (orientation) {
1412     case ArrowUp:
1413         arrow = QVector<QPointF>{
1414             {penOffset, maxExtent - penOffset - sizeOffset}, // left
1415             {center, minExtent - sizeOffset}, // mid
1416             {size - penOffset, maxExtent - penOffset - sizeOffset} // right
1417         };
1418         break;
1419     case ArrowDown:
1420         arrow = QVector<QPointF>{
1421             {penOffset, minExtent + penOffset + sizeOffset}, // left
1422             {center, maxExtent + sizeOffset}, // mid
1423             {size - penOffset, minExtent + penOffset + sizeOffset} // right
1424         };
1425         break;
1426     case ArrowLeft:
1427         arrow = QVector<QPointF>{
1428             {maxExtent - penOffset - sizeOffset, penOffset}, // top
1429             {minExtent - sizeOffset, center}, // mid
1430             {maxExtent - penOffset - sizeOffset, size - penOffset}, // bottom
1431         };
1432         break;
1433     case ArrowRight:
1434         arrow = QVector<QPointF>{
1435             {minExtent + penOffset + sizeOffset, penOffset}, // top
1436             {maxExtent + sizeOffset, center}, // mid
1437             {minExtent + penOffset + sizeOffset, size - penOffset}, // bottom
1438         };
1439         break;
1440     default:
1441         break;
1442     }
1443 
1444     arrow.translate(rect.x() + (rect.width() - size) / 2.0, rect.y() + (rect.height() - size) / 2.0);
1445 
1446     painter->save();
1447     painter->setRenderHints(QPainter::Antialiasing);
1448     painter->setBrush(Qt::NoBrush);
1449     QPen pen(color, PenWidth::Symbol);
1450     pen.setCapStyle(Qt::SquareCap);
1451     pen.setJoinStyle(Qt::MiterJoin);
1452     painter->setPen(pen);
1453     painter->drawPolyline(arrow);
1454     painter->restore();
1455 }
1456 
1457 //______________________________________________________________________________
1458 void Helper::renderDecorationButton(QPainter *painter, const QRectF &rect, const QColor &color, ButtonType buttonType, bool inverted) const
1459 {
1460     painter->save();
1461     painter->setViewport(rect.toRect());
1462     painter->setWindow(0, 0, 18, 18);
1463     painter->setRenderHints(QPainter::Antialiasing);
1464 
1465     // initialize pen
1466     QPen pen;
1467     pen.setCapStyle(Qt::RoundCap);
1468     pen.setJoinStyle(Qt::MiterJoin);
1469 
1470     if (inverted) {
1471         // render circle
1472         painter->setPen(Qt::NoPen);
1473         painter->setBrush(color);
1474         painter->drawEllipse(QRectF(0, 0, 18, 18));
1475 
1476         // take out the inner part
1477         painter->setCompositionMode(QPainter::CompositionMode_DestinationOut);
1478         painter->setBrush(Qt::NoBrush);
1479         pen.setColor(Qt::black);
1480 
1481     } else {
1482         painter->setBrush(Qt::NoBrush);
1483         pen.setColor(color);
1484     }
1485 
1486     pen.setCapStyle(Qt::RoundCap);
1487     pen.setJoinStyle(Qt::MiterJoin);
1488     pen.setWidthF(PenWidth::Symbol * qMax(1.0, 18.0 / rect.width()));
1489     painter->setPen(pen);
1490 
1491     switch (buttonType) {
1492     case ButtonClose: {
1493         painter->drawLine(QPointF(5, 5), QPointF(13, 13));
1494         painter->drawLine(13, 5, 5, 13);
1495         break;
1496     }
1497 
1498     case ButtonMaximize: {
1499         painter->drawPolyline(QVector<QPointF>{QPointF(4, 11), QPointF(9, 6), QPointF(14, 11)});
1500         break;
1501     }
1502 
1503     case ButtonMinimize: {
1504         painter->drawPolyline(QVector<QPointF>{QPointF(4, 7), QPointF(9, 12), QPointF(14, 7)});
1505         break;
1506     }
1507 
1508     case ButtonRestore: {
1509         pen.setJoinStyle(Qt::RoundJoin);
1510         painter->setPen(pen);
1511         painter->drawPolygon(QVector<QPointF>{QPointF(4.5, 9), QPointF(9, 4.5), QPointF(13.5, 9), QPointF(9, 13.5)});
1512         break;
1513     }
1514 
1515     default:
1516         break;
1517     }
1518 
1519     painter->restore();
1520 }
1521 
1522 //______________________________________________________________________________
1523 void Helper::renderRoundedRectShadow(QPainter *painter, const QRectF &rect, const QColor &color, qreal radius) const
1524 {
1525     if (!color.isValid()) {
1526         return;
1527     }
1528 
1529     painter->setRenderHint(QPainter::Antialiasing, true);
1530 
1531     qreal adjustment = 0.5 * PenWidth::Shadow; // Translate for the pen
1532     QRectF shadowRect = rect.adjusted(adjustment, adjustment, -adjustment, adjustment);
1533 
1534     painter->setPen(QPen(color, PenWidth::Shadow));
1535     painter->setBrush(Qt::NoBrush);
1536     painter->drawRoundedRect(shadowRect, radius, radius);
1537 }
1538 
1539 //______________________________________________________________________________
1540 void Helper::renderEllipseShadow(QPainter *painter, const QRectF &rect, const QColor &color) const
1541 {
1542     if (!color.isValid()) {
1543         return;
1544     }
1545 
1546     painter->save();
1547 
1548     // Clipping does not improve performance here
1549 
1550     qreal adjustment = 0.5 * PenWidth::Shadow; // Adjust for the pen
1551 
1552     qreal radius = rect.width() / 2 - adjustment;
1553 
1554     /* The right side is offset by +0.5 for the visible part of the shadow.
1555      * The other sides are offset by +0.5 or -0.5 because of the pen.
1556      */
1557     QRectF shadowRect = rect.adjusted(adjustment, adjustment, adjustment, -adjustment);
1558 
1559     painter->translate(rect.center());
1560     painter->rotate(45);
1561     painter->translate(-rect.center());
1562     painter->setPen(color);
1563     painter->setBrush(Qt::NoBrush);
1564     painter->drawRoundedRect(shadowRect, radius, radius);
1565 
1566     painter->restore();
1567 }
1568 
1569 //______________________________________________________________________________
1570 bool Helper::isX11()
1571 {
1572     static const bool s_isX11 = KWindowSystem::isPlatformX11();
1573     return s_isX11;
1574 }
1575 
1576 //______________________________________________________________________________
1577 bool Helper::isWayland()
1578 {
1579     static const bool s_isWayland = KWindowSystem::isPlatformWayland();
1580     return s_isWayland;
1581 }
1582 
1583 //______________________________________________________________________________
1584 QRectF Helper::strokedRect(const QRectF &rect, const qreal penWidth) const
1585 {
1586     /* With a pen stroke width of 1, the rectangle should have each of its
1587      * sides moved inwards by half a pixel. This allows the stroke to be
1588      * pixel perfect instead of blurry from sitting between pixels and
1589      * prevents the rectangle with a stroke from becoming larger than the
1590      * original size of the rectangle.
1591      */
1592     qreal adjustment = 0.5 * penWidth;
1593     return rect.adjusted(adjustment, adjustment, -adjustment, -adjustment);
1594 }
1595 
1596 //______________________________________________________________________________
1597 QPainterPath Helper::roundedPath(const QRectF &rect, Corners corners, qreal radius) const
1598 {
1599     QPainterPath path;
1600 
1601     // simple cases
1602     if (corners == 0) {
1603         path.addRect(rect);
1604         return path;
1605     }
1606 
1607     if (corners == AllCorners) {
1608         path.addRoundedRect(rect, radius, radius);
1609         return path;
1610     }
1611 
1612     const QSizeF cornerSize(2 * radius, 2 * radius);
1613 
1614     // rotate counterclockwise
1615     // top left corner
1616     if (corners & CornerTopLeft) {
1617         path.moveTo(rect.topLeft() + QPointF(radius, 0));
1618         path.arcTo(QRectF(rect.topLeft(), cornerSize), 90, 90);
1619 
1620     } else {
1621         path.moveTo(rect.topLeft());
1622     }
1623 
1624     // bottom left corner
1625     if (corners & CornerBottomLeft) {
1626         path.lineTo(rect.bottomLeft() - QPointF(0, radius));
1627         path.arcTo(QRectF(rect.bottomLeft() - QPointF(0, 2 * radius), cornerSize), 180, 90);
1628 
1629     } else {
1630         path.lineTo(rect.bottomLeft());
1631     }
1632 
1633     // bottom right corner
1634     if (corners & CornerBottomRight) {
1635         path.lineTo(rect.bottomRight() - QPointF(radius, 0));
1636         path.arcTo(QRectF(rect.bottomRight() - QPointF(2 * radius, 2 * radius), cornerSize), 270, 90);
1637 
1638     } else {
1639         path.lineTo(rect.bottomRight());
1640     }
1641 
1642     // top right corner
1643     if (corners & CornerTopRight) {
1644         path.lineTo(rect.topRight() + QPointF(0, radius));
1645         path.arcTo(QRectF(rect.topRight() - QPointF(2 * radius, 0), cornerSize), 0, 90);
1646 
1647     } else {
1648         path.lineTo(rect.topRight());
1649     }
1650 
1651     path.closeSubpath();
1652     return path;
1653 }
1654 
1655 //________________________________________________________________________________________________________
1656 bool Helper::compositingActive() const
1657 {
1658     if (isX11()) {
1659 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1660         return KWindowSystem::compositingActive();
1661 #elif __has_include(<KX11Extras>)
1662         return KX11Extras::compositingActive();
1663 #endif
1664     }
1665 
1666     return true;
1667 }
1668 
1669 //____________________________________________________________________
1670 bool Helper::hasAlphaChannel(const QWidget *widget) const
1671 {
1672     return compositingActive() && widget && widget->testAttribute(Qt::WA_TranslucentBackground);
1673 }
1674 
1675 //______________________________________________________________________________________
1676 
1677 QPixmap Helper::coloredIcon(const QIcon &icon, const QPalette &palette, const QSize &size, qreal devicePixelRatio, QIcon::Mode mode, QIcon::State state)
1678 {
1679     const QPalette activePalette = KIconLoader::global()->customPalette();
1680     const bool changePalette = activePalette != palette;
1681     if (changePalette) {
1682         KIconLoader::global()->setCustomPalette(palette);
1683     }
1684 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1685     const QPixmap pixmap = icon.pixmap(size, devicePixelRatio, mode, state);
1686 #else
1687     Q_UNUSED(devicePixelRatio);
1688     const QPixmap pixmap = icon.pixmap(size, mode, state);
1689 #endif
1690     if (changePalette) {
1691         if (activePalette == QPalette()) {
1692             KIconLoader::global()->resetPalette();
1693         } else {
1694             KIconLoader::global()->setCustomPalette(activePalette);
1695         }
1696     }
1697     return pixmap;
1698 }
1699 
1700 bool Helper::shouldDrawToolsArea(const QWidget *widget) const
1701 {
1702     if (!widget) {
1703         return false;
1704     }
1705     static bool isAuto = false;
1706     static QString borderSize;
1707     if (!_cachedAutoValid) {
1708         KConfigGroup kdecorationGroup(_kwinConfig->group(QStringLiteral("org.kde.kdecoration2")));
1709         isAuto = kdecorationGroup.readEntry("BorderSizeAuto", true);
1710         borderSize = kdecorationGroup.readEntry("BorderSize", "Normal");
1711         _cachedAutoValid = true;
1712     }
1713     if (isAuto) {
1714         auto window = widget->window();
1715         if (qobject_cast<const QDialog *>(widget)) {
1716             return true;
1717         }
1718         if (window) {
1719             auto handle = window->windowHandle();
1720             if (handle) {
1721                 auto toolbar = qobject_cast<const QToolBar *>(widget);
1722                 if (toolbar) {
1723                     if (toolbar->isFloating()) {
1724                         return false;
1725                     }
1726                 }
1727                 return true;
1728             }
1729         } else {
1730             return false;
1731         }
1732     }
1733     if (borderSize != "None" && borderSize != "NoSides") {
1734         return false;
1735     }
1736     return true;
1737 }
1738 
1739 Qt::Edges Helper::menuSeamlessEdges(const QWidget *widget)
1740 {
1741     if (widget) {
1742         auto edges = widget->property(PropertyNames::menuSeamlessEdges).value<Qt::Edges>();
1743         // Fallback to older property
1744         if (edges == Qt::Edges() && widget->property(PropertyNames::isTopMenu).toBool()) {
1745             edges = Qt::TopEdge;
1746         }
1747         return edges;
1748     }
1749     return Qt::Edges();
1750 }
1751 
1752 qreal Helper::devicePixelRatio(QPainter *painter) const
1753 {
1754     return painter->device() ? painter->device()->devicePixelRatioF() : qApp->devicePixelRatio();
1755 }
1756 }