File indexing completed on 2024-05-05 05:28:58
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> 0003 * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 #include "breezebutton.h" 0008 0009 #include <KColorUtils> 0010 #include <KDecoration2/DecoratedClient> 0011 #include <KIconLoader> 0012 0013 #include <QPainter> 0014 #include <QPainterPath> 0015 #include <QVariantAnimation> 0016 0017 namespace Breeze 0018 { 0019 using KDecoration2::ColorGroup; 0020 using KDecoration2::ColorRole; 0021 using KDecoration2::DecorationButtonType; 0022 0023 //__________________________________________________________________ 0024 Button::Button(DecorationButtonType type, Decoration *decoration, QObject *parent) 0025 : DecorationButton(type, decoration, parent) 0026 , m_animation(new QVariantAnimation(this)) 0027 { 0028 // setup animation 0029 // It is important start and end value are of the same type, hence 0.0 and not just 0 0030 m_animation->setStartValue(0.0); 0031 m_animation->setEndValue(1.0); 0032 m_animation->setEasingCurve(QEasingCurve::InOutQuad); 0033 connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) { 0034 setOpacity(value.toReal()); 0035 }); 0036 0037 // connections 0038 connect(decoration->client(), SIGNAL(iconChanged(QIcon)), this, SLOT(update())); 0039 connect(decoration->settings().get(), &KDecoration2::DecorationSettings::reconfigured, this, &Button::reconfigure); 0040 connect(this, &KDecoration2::DecorationButton::hoveredChanged, this, &Button::updateAnimationState); 0041 0042 reconfigure(); 0043 } 0044 0045 //__________________________________________________________________ 0046 Button::Button(QObject *parent, const QVariantList &args) 0047 : Button(args.at(0).value<DecorationButtonType>(), args.at(1).value<Decoration *>(), parent) 0048 { 0049 setGeometry(QRectF(QPointF(0, 0), preferredSize())); 0050 } 0051 0052 //__________________________________________________________________ 0053 Button *Button::create(DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent) 0054 { 0055 if (auto d = qobject_cast<Decoration *>(decoration)) { 0056 Button *b = new Button(type, d, parent); 0057 const auto c = d->client(); 0058 switch (type) { 0059 case DecorationButtonType::Close: 0060 b->setVisible(c->isCloseable()); 0061 QObject::connect(c, &KDecoration2::DecoratedClient::closeableChanged, b, &Breeze::Button::setVisible); 0062 break; 0063 0064 case DecorationButtonType::Maximize: 0065 b->setVisible(c->isMaximizeable()); 0066 QObject::connect(c, &KDecoration2::DecoratedClient::maximizeableChanged, b, &Breeze::Button::setVisible); 0067 break; 0068 0069 case DecorationButtonType::Minimize: 0070 b->setVisible(c->isMinimizeable()); 0071 QObject::connect(c, &KDecoration2::DecoratedClient::minimizeableChanged, b, &Breeze::Button::setVisible); 0072 break; 0073 0074 case DecorationButtonType::ContextHelp: 0075 b->setVisible(c->providesContextHelp()); 0076 QObject::connect(c, &KDecoration2::DecoratedClient::providesContextHelpChanged, b, &Breeze::Button::setVisible); 0077 break; 0078 0079 case DecorationButtonType::Shade: 0080 b->setVisible(c->isShadeable()); 0081 QObject::connect(c, &KDecoration2::DecoratedClient::shadeableChanged, b, &Breeze::Button::setVisible); 0082 break; 0083 0084 case DecorationButtonType::Menu: 0085 QObject::connect(c, &KDecoration2::DecoratedClient::iconChanged, b, [b]() { 0086 b->update(); 0087 }); 0088 break; 0089 0090 default: 0091 break; 0092 } 0093 0094 return b; 0095 } 0096 0097 return nullptr; 0098 } 0099 0100 //__________________________________________________________________ 0101 void Button::paint(QPainter *painter, const QRect &repaintRegion) 0102 { 0103 Q_UNUSED(repaintRegion) 0104 0105 if (!decoration()) { 0106 return; 0107 } 0108 0109 switch (type()) { 0110 case KDecoration2::DecorationButtonType::Menu: { 0111 const QRectF iconRect = geometry().marginsRemoved(m_padding); 0112 const auto c = decoration()->client(); 0113 if (auto deco = qobject_cast<Decoration *>(decoration())) { 0114 const QPalette activePalette = KIconLoader::global()->customPalette(); 0115 QPalette palette = c->palette(); 0116 palette.setColor(QPalette::WindowText, deco->fontColor()); 0117 KIconLoader::global()->setCustomPalette(palette); 0118 c->icon().paint(painter, iconRect.toRect()); 0119 if (activePalette == QPalette()) { 0120 KIconLoader::global()->resetPalette(); 0121 } else { 0122 KIconLoader::global()->setCustomPalette(palette); 0123 } 0124 } else { 0125 c->icon().paint(painter, iconRect.toRect()); 0126 } 0127 break; 0128 } 0129 case KDecoration2::DecorationButtonType::Spacer: 0130 break; 0131 default: 0132 painter->save(); 0133 drawIcon(painter); 0134 painter->restore(); 0135 break; 0136 } 0137 } 0138 0139 //__________________________________________________________________ 0140 void Button::drawIcon(QPainter *painter) const 0141 { 0142 painter->setRenderHints(QPainter::Antialiasing); 0143 0144 /* 0145 scale painter so that its window matches QRect( -1, -1, 20, 20 ) 0146 this makes all further rendering and scaling simpler 0147 all further rendering is performed inside QRect( 0, 0, 18, 18 ) 0148 */ 0149 const QRectF rect = geometry().marginsRemoved(m_padding); 0150 painter->translate(rect.topLeft()); 0151 0152 const qreal width(rect.width()); 0153 painter->scale(width / 20, width / 20); 0154 painter->translate(1, 1); 0155 0156 // render background 0157 const QColor backgroundColor(this->backgroundColor()); 0158 if (backgroundColor.isValid()) { 0159 painter->setPen(Qt::NoPen); 0160 painter->setBrush(backgroundColor); 0161 painter->drawEllipse(QRectF(0, 0, 18, 18)); 0162 } 0163 0164 // render mark 0165 const QColor foregroundColor(this->foregroundColor()); 0166 if (foregroundColor.isValid()) { 0167 // setup painter 0168 QPen pen(foregroundColor); 0169 pen.setCapStyle(Qt::RoundCap); 0170 pen.setJoinStyle(Qt::MiterJoin); 0171 pen.setWidthF(PenWidth::Symbol * qMax((qreal)1.0, 20 / width)); 0172 0173 painter->setPen(pen); 0174 painter->setBrush(Qt::NoBrush); 0175 0176 switch (type()) { 0177 case DecorationButtonType::Close: { 0178 painter->drawLine(QPointF(5, 5), QPointF(13, 13)); 0179 painter->drawLine(13, 5, 5, 13); 0180 break; 0181 } 0182 0183 case DecorationButtonType::Maximize: { 0184 if (isChecked()) { 0185 pen.setJoinStyle(Qt::RoundJoin); 0186 painter->setPen(pen); 0187 0188 painter->drawPolygon(QVector<QPointF>{QPointF(4, 9), QPointF(9, 4), QPointF(14, 9), QPointF(9, 14)}); 0189 0190 } else { 0191 painter->drawPolyline(QVector<QPointF>{QPointF(4, 11), QPointF(9, 6), QPointF(14, 11)}); 0192 } 0193 break; 0194 } 0195 0196 case DecorationButtonType::Minimize: { 0197 painter->drawPolyline(QVector<QPointF>{QPointF(4, 7), QPointF(9, 12), QPointF(14, 7)}); 0198 break; 0199 } 0200 0201 case DecorationButtonType::OnAllDesktops: { 0202 painter->setPen(Qt::NoPen); 0203 painter->setBrush(foregroundColor); 0204 0205 if (isChecked()) { 0206 // outer ring 0207 painter->drawEllipse(QRectF(3, 3, 12, 12)); 0208 0209 // center dot 0210 QColor backgroundColor(this->backgroundColor()); 0211 auto d = qobject_cast<Decoration *>(decoration()); 0212 if (!backgroundColor.isValid() && d) { 0213 backgroundColor = d->titleBarColor(); 0214 } 0215 0216 if (backgroundColor.isValid()) { 0217 painter->setBrush(backgroundColor); 0218 painter->drawEllipse(QRectF(8, 8, 2, 2)); 0219 } 0220 0221 } else { 0222 painter->drawPolygon(QVector<QPointF>{QPointF(6.5, 8.5), QPointF(12, 3), QPointF(15, 6), QPointF(9.5, 11.5)}); 0223 0224 painter->setPen(pen); 0225 painter->drawLine(QPointF(5.5, 7.5), QPointF(10.5, 12.5)); 0226 painter->drawLine(QPointF(12, 6), QPointF(4.5, 13.5)); 0227 } 0228 break; 0229 } 0230 0231 case DecorationButtonType::Shade: { 0232 if (isChecked()) { 0233 painter->drawLine(QPointF(4, 5.5), QPointF(14, 5.5)); 0234 painter->drawPolyline(QVector<QPointF>{QPointF(4, 8), QPointF(9, 13), QPointF(14, 8)}); 0235 0236 } else { 0237 painter->drawLine(QPointF(4, 5.5), QPointF(14, 5.5)); 0238 painter->drawPolyline(QVector<QPointF>{QPointF(4, 13), QPointF(9, 8), QPointF(14, 13)}); 0239 } 0240 0241 break; 0242 } 0243 0244 case DecorationButtonType::KeepBelow: { 0245 painter->drawPolyline(QVector<QPointF>{QPointF(4, 5), QPointF(9, 10), QPointF(14, 5)}); 0246 0247 painter->drawPolyline(QVector<QPointF>{QPointF(4, 9), QPointF(9, 14), QPointF(14, 9)}); 0248 break; 0249 } 0250 0251 case DecorationButtonType::KeepAbove: { 0252 painter->drawPolyline(QVector<QPointF>{QPointF(4, 9), QPointF(9, 4), QPointF(14, 9)}); 0253 0254 painter->drawPolyline(QVector<QPointF>{QPointF(4, 13), QPointF(9, 8), QPointF(14, 13)}); 0255 break; 0256 } 0257 0258 case DecorationButtonType::ApplicationMenu: { 0259 painter->drawRect(QRectF(3.5, 4.5, 11, 1)); 0260 painter->drawRect(QRectF(3.5, 8.5, 11, 1)); 0261 painter->drawRect(QRectF(3.5, 12.5, 11, 1)); 0262 break; 0263 } 0264 0265 case DecorationButtonType::ContextHelp: { 0266 QPainterPath path; 0267 path.moveTo(5, 6); 0268 path.arcTo(QRectF(5, 3.5, 8, 5), 180, -180); 0269 path.cubicTo(QPointF(12.5, 9.5), QPointF(9, 7.5), QPointF(9, 11.5)); 0270 painter->drawPath(path); 0271 0272 painter->drawRect(QRectF(9, 15, 0.5, 0.5)); 0273 0274 break; 0275 } 0276 0277 default: 0278 break; 0279 } 0280 } 0281 } 0282 0283 //__________________________________________________________________ 0284 QColor Button::foregroundColor() const 0285 { 0286 auto d = qobject_cast<Decoration *>(decoration()); 0287 if (!d) { 0288 return QColor(); 0289 0290 } else if (isPressed()) { 0291 return d->titleBarColor(); 0292 0293 } else if (type() == DecorationButtonType::Close && d->internalSettings()->outlineCloseButton()) { 0294 return d->titleBarColor(); 0295 0296 } else if ((type() == DecorationButtonType::KeepBelow || type() == DecorationButtonType::KeepAbove || type() == DecorationButtonType::Shade) 0297 && isChecked()) { 0298 return d->titleBarColor(); 0299 0300 } else if (m_animation->state() == QAbstractAnimation::Running) { 0301 return KColorUtils::mix(d->fontColor(), d->titleBarColor(), m_opacity); 0302 0303 } else if (isHovered()) { 0304 return d->titleBarColor(); 0305 0306 } else { 0307 return d->fontColor(); 0308 } 0309 } 0310 0311 //__________________________________________________________________ 0312 QColor Button::backgroundColor() const 0313 { 0314 auto d = qobject_cast<Decoration *>(decoration()); 0315 if (!d) { 0316 return QColor(); 0317 } 0318 0319 auto c = d->client(); 0320 QColor redColor(c->color(ColorGroup::Warning, ColorRole::Foreground)); 0321 0322 if (isPressed()) { 0323 if (type() == DecorationButtonType::Close) { 0324 return redColor.darker(); 0325 } else { 0326 return KColorUtils::mix(d->titleBarColor(), d->fontColor(), 0.3); 0327 } 0328 0329 } else if ((type() == DecorationButtonType::KeepBelow || type() == DecorationButtonType::KeepAbove || type() == DecorationButtonType::Shade) 0330 && isChecked()) { 0331 return d->fontColor(); 0332 0333 } else if (m_animation->state() == QAbstractAnimation::Running) { 0334 if (type() == DecorationButtonType::Close) { 0335 if (d->internalSettings()->outlineCloseButton()) { 0336 return c->isActive() ? KColorUtils::mix(redColor, redColor.lighter(), m_opacity) : KColorUtils::mix(redColor.lighter(), redColor, m_opacity); 0337 0338 } else { 0339 QColor color(redColor.lighter()); 0340 color.setAlpha(color.alpha() * m_opacity); 0341 return color; 0342 } 0343 0344 } else { 0345 QColor color(d->fontColor()); 0346 color.setAlpha(color.alpha() * m_opacity); 0347 return color; 0348 } 0349 0350 } else if (isHovered()) { 0351 if (type() == DecorationButtonType::Close) { 0352 return c->isActive() ? redColor.lighter() : redColor; 0353 } else { 0354 return d->fontColor(); 0355 } 0356 0357 } else if (type() == DecorationButtonType::Close && d->internalSettings()->outlineCloseButton()) { 0358 return c->isActive() ? redColor : d->fontColor(); 0359 0360 } else { 0361 return QColor(); 0362 } 0363 } 0364 0365 //________________________________________________________________ 0366 void Button::reconfigure() 0367 { 0368 // animation 0369 auto d = qobject_cast<Decoration *>(decoration()); 0370 if (!d) { 0371 return; 0372 } 0373 0374 switch (type()) { 0375 case KDecoration2::DecorationButtonType::Spacer: 0376 setPreferredSize(QSizeF(d->buttonSize() * 0.5, d->captionHeight())); 0377 break; 0378 default: 0379 setPreferredSize(QSizeF(d->buttonSize(), d->captionHeight())); 0380 break; 0381 } 0382 0383 m_animation->setDuration(d->animationsDuration()); 0384 } 0385 0386 //__________________________________________________________________ 0387 void Button::updateAnimationState(bool hovered) 0388 { 0389 auto d = qobject_cast<Decoration *>(decoration()); 0390 if (!(d && d->animationsDuration() > 0)) { 0391 return; 0392 } 0393 0394 m_animation->setDirection(hovered ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); 0395 if (m_animation->state() != QAbstractAnimation::Running) { 0396 m_animation->start(); 0397 } 0398 } 0399 0400 } // namespace