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