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 }