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