You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

434 lines
15 KiB

/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "breezebuttons.h"
#include <KDecoration2/DecoratedClient>
#include <QPainter>
uint qHash(const QPalette &pal)
{
QByteArray byteArray;
QDataStream stream(&byteArray, QIODevice::WriteOnly);
stream << pal;
return byteArray.toBase64().toUInt();
}
namespace Breeze
{
ImageProvider *ImageProvider::s_self = nullptr;
ImageProvider::ImageProvider() = default;
ImageProvider::~ImageProvider()
{
s_self = nullptr;
}
ImageProvider *ImageProvider::self()
{
// TODO: this is not thread safe!
if (!s_self) {
s_self = new ImageProvider();
}
return s_self;
}
void ImageProvider::invalidate()
{
m_images.clear();
}
static ButtonState stateForButton(Button *decorationButton)
{
if (!decorationButton->isEnabled()) {
return ButtonState::Disabled;
}
if (decorationButton->isChecked()) {
if (decorationButton->isPressed()) {
return ButtonState::CheckedPressed;
}
if (decorationButton->isHovered()) {
return ButtonState::CheckedHovered;
}
return ButtonState::Checked;
}
if (decorationButton->isPressed()) {
return ButtonState::Pressed;
}
if (decorationButton->isHovered()) {
return ButtonState::Hovered;
}
if (decorationButton->isStandAlone()) {
return ButtonState::Preview;
}
return ButtonState::Normal;
}
QImage ImageProvider::button(Breeze::Button *decorationButton)
{
auto paletteIt = m_images.begin();
if (!decorationButton->decoration()) {
return QImage();
}
auto client = decorationButton->decoration()->client().data();
if (paletteIt == m_images.end() || paletteIt.key() != client->palette()) {
paletteIt = m_images.find(client->palette());
}
if (paletteIt == m_images.end()) {
const QPalette pal = client->palette();
m_images.insert(pal, ImagesForButton());
m_colorSettings.append(ColorSettings(pal));
paletteIt = m_images.find(client->palette());
}
Q_ASSERT(paletteIt != m_images.end());
auto it = paletteIt.value().find(decorationButton->type());
if (it == paletteIt.value().end()) {
auto hash = ImagesForDecoState();
hash.insert(true, ImagesForButtonState());
hash.insert(false, ImagesForButtonState());
paletteIt.value().insert(decorationButton->type(), hash);
it = paletteIt.value().find(decorationButton->type());
}
Q_ASSERT(it != paletteIt.value().end());
auto it2 = it.value().find(client->isActive());
Q_ASSERT(it2 != it.value().end());
const ButtonState state = stateForButton(decorationButton);
auto it3 = it2.value().find(state);
if (it3 == it2.value().end()) {
QImage image = renderButton(decorationButton);
it2.value().insert(state, image);
it3 = it2.value().find(state);
}
Q_ASSERT(it3 != it2.value().end());
return it3.value();
}
void ImageProvider::clearCache(Breeze::Button *decorationButton)
{
auto paletteIt = m_images.begin();
if (!decorationButton->decoration()) {
return;
}
const QPalette &palette = decorationButton->decoration()->client().data()->palette();
if (paletteIt == m_images.end() || paletteIt.key() != palette) {
paletteIt = m_images.find(palette);
}
if (paletteIt == m_images.end()) {
return;
}
auto it = paletteIt.value().find(decorationButton->type());
if (it == paletteIt.value().end()) {
return;
}
paletteIt.value().erase(it);
}
ColorSettings ImageProvider::colorSettings(const QPalette &pal) const
{
for (const ColorSettings &colorSettings : m_colorSettings) {
if (colorSettings.palette() == pal) {
return colorSettings;
}
}
Q_ASSERT(false);
return ColorSettings(pal);
}
ColorSettings ImageProvider::colorSettings(Breeze::Button *decorationButton) const
{
if (!decorationButton->decoration()) {
return colorSettings(QPalette());
}
return colorSettings(decorationButton->decoration()->client().data()->palette());
}
QImage ImageProvider::renderButton(Breeze::Button *decorationButton) const
{
QImage image(decorationButton->size().toSize(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter p(&image);
p.setRenderHint(QPainter::Antialiasing);
switch (decorationButton->type()) {
case KDecoration2::DecorationButtonType::Close:
renderCloseButton(&p, decorationButton);
break;
case KDecoration2::DecorationButtonType::Maximize:
renderMaximizeButton(&p, decorationButton);
break;
case KDecoration2::DecorationButtonType::OnAllDesktops:
renderOnAllDesktopsButton(&p, decorationButton);
break;
case KDecoration2::DecorationButtonType::Shade:
renderShadeButton(&p, decorationButton);
break;
case KDecoration2::DecorationButtonType::Minimize:
drawGenericButtonBackground(&p, decorationButton);
drawDownArrow(&p, decorationButton);
break;
case KDecoration2::DecorationButtonType::KeepBelow:
// TODO: Needs a checked state
drawGenericButtonBackground(&p, decorationButton);
drawDownArrow(&p, decorationButton, QPointF(0.0, -2.0));
drawDownArrow(&p, decorationButton, QPointF(0.0, 2.0));
break;
case KDecoration2::DecorationButtonType::KeepAbove:
// TODO: Needs a checked state
drawGenericButtonBackground(&p, decorationButton);
drawUpArrow(&p, decorationButton, QPointF(0.0, -2.0));
drawUpArrow(&p, decorationButton, QPointF(0.0, 2.0));
break;
default:
break;
}
return image;
}
void ImageProvider::renderCloseButton(QPainter *painter, Breeze::Button *decorationButton) const
{
if (!decorationButton->decoration()) {
return;
}
auto client = decorationButton->decoration()->client().data();
const bool active = client->isActive();
const QPalette &pal = client->palette();
const bool pressed = decorationButton->isPressed();
const bool hovered = decorationButton->isHovered();
const QSize &size = decorationButton->size().toSize();
const QColor pressedColor = QColor(237, 21, 21);
const QColor backgroundColor = pressed ? pressedColor : hovered ? pressedColor.lighter() : colorSettings(pal).font(active);
drawBackground(painter, decorationButton, backgroundColor);
// draw the X
QPen pen(hovered || pressed ? colorSettings(pal).font(active) : colorSettings(pal).titleBarColor(active));
pen.setWidth(2);
painter->setPen(pen);
painter->translate(size.width() / 2.0, size.height() / 2.0);
painter->rotate(45.0);
painter->drawLine(0, -size.height() / 4, 0, size.height() / 4);
painter->drawLine(-size.width() / 4, 0, size.width() / 4, 0);
}
void ImageProvider::renderMaximizeButton(QPainter *painter, Breeze::Button *decorationButton) const
{
painter->save();
drawGenericButtonBackground(painter, decorationButton);
QPen pen(foregroundColor(decorationButton));
if (decorationButton->isChecked()) {
// restore button
const qreal width = (decorationButton->size().height() - 5) / 4;
painter->translate(decorationButton->size().width() / 2.0, decorationButton->size().height() / 2.0);
painter->rotate(45.0);
pen.setWidth(2);
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(-width, -width, width * 2, width * 2);
} else {
// maximize button
drawUpArrow(painter, decorationButton);
}
painter->restore();
}
void ImageProvider::renderOnAllDesktopsButton(QPainter *painter, Breeze::Button *decorationButton) const
{
if (!decorationButton->decoration()) {
return;
}
const bool active = decorationButton->decoration()->client().data()->isActive();
painter->save();
drawGenericButtonBackground(painter, decorationButton);
if (decorationButton->isChecked()) {
// on all desktops
painter->setPen(Qt::NoPen);
painter->setBrush(foregroundColor(decorationButton));
painter->translate(decorationButton->size().width() / 2.0, decorationButton->size().height() / 2.0);
const int radius = decorationButton->size().width() / 2 - 3;
painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);
painter->setBrush(decorationButton->isHovered() ? colorSettings(decorationButton).font(active) : colorSettings(decorationButton).titleBarColor(active));
painter->drawEllipse(-1, -1, 2, 2);
} else {
// not on all desktops
// TODO: implement the pin
}
painter->restore();
}
void ImageProvider::renderShadeButton(QPainter *painter, Breeze::Button *decorationButton) const
{
drawGenericButtonBackground(painter, decorationButton);
painter->save();
QPen pen(foregroundColor(decorationButton));
pen.setWidth(2);
painter->setPen(pen);
painter->translate(decorationButton->size().width() / 2.0, decorationButton->size().height() / 2.0);
const qreal width = decorationButton->size().width() /2.0 - 3.0;
painter->drawLine(-width, -4.0, width, -4.0);
painter->restore();
if (decorationButton->isChecked()) {
drawDownArrow(painter, decorationButton, QPointF(0.0, 2.0));
} else {
drawUpArrow(painter, decorationButton, QPointF(0.0, 2.0));
}
}
void ImageProvider::drawGenericButtonBackground(QPainter *painter, Breeze::Button *decorationButton) const
{
if (!decorationButton->decoration()) {
return;
}
const bool standAlone = decorationButton->isStandAlone();
if (!decorationButton->isPressed() && !decorationButton->isHovered() && !standAlone) {
return;
}
const QColor baseBackgroundColor = colorSettings(decorationButton).font(decorationButton->decoration()->client().data()->isActive());
drawBackground(painter, decorationButton, QColor(baseBackgroundColor.red(),
baseBackgroundColor.green(),
baseBackgroundColor.blue(),
decorationButton->isPressed() ? 50 : standAlone ? 255 : 127));
}
void ImageProvider::drawBackground(QPainter *painter, Breeze::Button *decorationButton, const QColor &color) const
{
painter->save();
painter->setPen(Qt::NoPen);
painter->setBrush(decorationButton->isEnabled() ? color : QColor(color.red(), color.green(), color.blue(), color.alpha() * 0.6));
painter->drawEllipse(QRectF(QPointF(0, 0), decorationButton->size()));
painter->restore();
}
void ImageProvider::drawDownArrow(QPainter *painter, Breeze::Button *decorationButton, const QPointF &offset) const
{
painter->save();
QPen pen(foregroundColor(decorationButton));
// TODO: where do the magic values come from?
const qreal width = (decorationButton->size().height() - 5) / 2;
pen.setWidth(2);
painter->setPen(pen);
painter->translate(QPointF(decorationButton->size().width() / 2.0, decorationButton->size().height() / 2.0) + offset + QPointF(0.0, 0.707*width/2.0));
painter->rotate(45.0);
painter->drawLine(0, -width, 0, 0);
painter->rotate(-90.0);
painter->drawLine(0, -width, 0, 0);
painter->restore();
}
void ImageProvider::drawUpArrow(QPainter *painter, Breeze::Button *decorationButton, const QPointF &offset) const
{
painter->save();
QPen pen(foregroundColor(decorationButton));
// TODO: where do the magic values come from?
const qreal width = (decorationButton->size().height() - 5) / 2;
pen.setWidth(2);
painter->setPen(pen);
painter->translate(QPointF(decorationButton->size().width() / 2.0, decorationButton->size().height() / 2.0) + offset - QPointF(0.0, 0.707*width/2.0));
painter->rotate(225.0);
painter->drawLine(0, 0, 0, -width);
painter->rotate(-90.0);
painter->drawLine(0, 0, 0, -width);
painter->restore();
}
QColor ImageProvider::foregroundColor(Breeze::Button *decorationButton) const
{
if (!decorationButton->decoration()) {
return QColor();
}
const auto client = decorationButton->decoration()->client().data();
const ColorSettings &colors = colorSettings(client->palette());
const bool active = client->isActive();
if (decorationButton->isStandAlone()) {
return colors.titleBarColor(active);
}
if (decorationButton->isHovered()) {
return colors.titleBarColor(active);
}
QColor c = colors.font(active);
if (!decorationButton->isEnabled()) {
c.setAlphaF(c.alphaF() * 0.6);
}
return c;
}
Button::Button(KDecoration2::DecorationButtonType type, Decoration* decoration, QObject* parent)
: DecorationButton(type, decoration, parent)
{
const int height = decoration->captionHeight();
setGeometry(QRect(0, 0, height, height));
connect(decoration, &Decoration::bordersChanged, this, [this, decoration] {
const int height = decoration->captionHeight();
if (height == geometry().height()) {
return;
}
ImageProvider::self()->clearCache(this);
setGeometry(QRectF(geometry().topLeft(), QSizeF(height, height)));
});
}
Button::Button(QObject *parent, const QVariantList &args)
: DecorationButton(args.at(0).value<KDecoration2::DecorationButtonType>(), args.at(1).value<Decoration*>(), parent)
, m_standalone(true)
{
}
Button *Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent)
{
if (Decoration *d = qobject_cast<Decoration*>(decoration)) {
Button *b = new Button(type, d, parent);
if (type == KDecoration2::DecorationButtonType::Menu) {
QObject::connect(d->client().data(), &KDecoration2::DecoratedClient::iconChanged, b, [b]() { b->update(); });
}
return b;
}
return nullptr;
}
Button::~Button() = default;
void Button::paint(QPainter *painter, const QRect &repaintRegion)
{
Q_UNUSED(repaintRegion)
if (!decoration()) {
return;
}
// TODO: optimize based on repaintRegion
if (type() == KDecoration2::DecorationButtonType::Menu) {
const QPixmap pixmap = decoration()->client().data()->icon().pixmap(size().toSize());
painter->drawPixmap(geometry().center() - QPoint(pixmap.width()/2, pixmap.height()/2), pixmap);
} else {
painter->drawImage(geometry().topLeft(), ImageProvider::self()->button(this));
}
}
} // namespace