diff --git a/windec/CMakeLists.txt b/windec/CMakeLists.txt index 7ac6064c..4ba2ea67 100644 --- a/windec/CMakeLists.txt +++ b/windec/CMakeLists.txt @@ -13,12 +13,11 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECompilerSettings) include(KDECMakeSettings) -#find_package(KDecorations REQUIRED) #find_package(KF5ConfigWidgets REQUIRED) #find_package(Qt5 CONFIG REQUIRED COMPONENTS Core DBus Quick Widgets Concurrent X11Extras Test UiTools) -#add_subdirectory(code) +add_subdirectory(kdecoration2) install(DIRECTORY package/ DESTINATION ${DATA_INSTALL_DIR}/${KWIN_NAME}/decorations/kwin4_decoration_qml_breeze) diff --git a/windec/kdecoration2/CMakeLists.txt b/windec/kdecoration2/CMakeLists.txt new file mode 100644 index 00000000..bf5d921b --- /dev/null +++ b/windec/kdecoration2/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(KDecoration2 REQUIRED) +find_package(KF5 REQUIRED COMPONENTS CoreAddons Config) + + +set(breezedecoration_SRCS + breezedeco.cpp + breezebuttons.cpp +) + +add_library(breezedecoration MODULE ${breezedecoration_SRCS}) + +target_link_libraries(breezedecoration + PUBLIC + Qt5::Core + Qt5::Gui + PRIVATE + KDecoration2::KDecoration + KF5::ConfigCore + KF5::CoreAddons +) + +install(TARGETS breezedecoration DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) diff --git a/windec/kdecoration2/breeze.json b/windec/kdecoration2/breeze.json new file mode 100644 index 00000000..1abfb27f --- /dev/null +++ b/windec/kdecoration2/breeze.json @@ -0,0 +1,12 @@ +{ + "Type": "Service", + "X-KDE-Library": "breezedecoration", + "X-KDE-PluginInfo-EnabledByDefault": true, + "X-KDE-PluginInfo-Name": "org.kde.breeze", + "X-KDE-ServiceTypes": [ + "org.kde.kdecoration2" + ], + "org.kde.kdecoration2": { + "blur": false + } +} diff --git a/windec/kdecoration2/breezebuttons.cpp b/windec/kdecoration2/breezebuttons.cpp new file mode 100644 index 00000000..3944f482 --- /dev/null +++ b/windec/kdecoration2/breezebuttons.cpp @@ -0,0 +1,434 @@ +/* + * Copyright 2014 Martin Gräßlin + * + * 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 . + */ +#include "breezebuttons.h" + +#include + +#include + +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(), args.at(1).value(), parent) + , m_standalone(true) +{ +} + +Button *Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent) +{ + if (Decoration *d = qobject_cast(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 diff --git a/windec/kdecoration2/breezebuttons.h b/windec/kdecoration2/breezebuttons.h new file mode 100644 index 00000000..11ccd490 --- /dev/null +++ b/windec/kdecoration2/breezebuttons.h @@ -0,0 +1,102 @@ +/* + * Copyright 2014 Martin Gräßlin + * + * 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 . + */ +#ifndef BREEZE_BUTTONS_H +#define BREEZE_BUTTONS_H +#include +#include "breezedeco.h" + +#include +#include +uint qHash(const QPalette &pal); + +namespace Breeze +{ + +class Button; + +enum class ButtonState : uint { + Normal, + Disabled, + Hovered, + Pressed, + Checked, + CheckedHovered, + CheckedPressed, + Preview +}; + +inline uint qHash(const ButtonState &state) { + return static_cast(state); +} + +class ImageProvider final +{ +public: + ~ImageProvider(); + static ImageProvider *self(); + QImage button(Button *decorationButton); + void clearCache(Button *decorationButton); + + void invalidate(); + +private: + ImageProvider(); + QImage renderButton(Button *decorationButton) const; + void renderCloseButton(QPainter *p, Button *decorationButton) const; + void renderShadeButton(QPainter *p, Button *decorationButton) const; + void renderMaximizeButton(QPainter *p, Button *decorationButton) const; + void renderOnAllDesktopsButton(QPainter *p, Button *decorationButton) const; + void drawGenericButtonBackground(QPainter *painter, Button *decorationButton) const; + void drawBackground(QPainter *painter, Button *decorationButton, const QColor &color) const; + void drawDownArrow(QPainter *painter, Button *decorationButton, const QPointF &offset = QPointF(0.0, 0.0)) const; + void drawUpArrow(QPainter *painter, Button *decorationButton, const QPointF &offset = QPointF(0.0, 0.0)) const; + QColor foregroundColor(Button *decorationButton) const; + ColorSettings colorSettings(Button *decorationButton) const; + ColorSettings colorSettings(const QPalette &pal) const; + static ImageProvider *s_self; + typedef QHash ImagesForButtonState; + typedef QHash ImagesForDecoState; + typedef QHash ImagesForButton; + QHash m_images; + QList m_colorSettings; +}; + +class Button : public KDecoration2::DecorationButton +{ + Q_OBJECT +public: + explicit Button(QObject *parent, const QVariantList &args); + + virtual ~Button(); + void paint(QPainter *painter, const QRect &repaintRegion) override; + static Button *create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent); + + bool isStandAlone() const { + return m_standalone; + } + +private: + explicit Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent = nullptr); + bool m_standalone = false; +}; + +} // namespace + +#endif diff --git a/windec/kdecoration2/breezedeco.cpp b/windec/kdecoration2/breezedeco.cpp new file mode 100644 index 00000000..7605c05f --- /dev/null +++ b/windec/kdecoration2/breezedeco.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2014 Martin Gräßlin + * + * 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 . + */ +#include "breezedeco.h" +#include "breezebuttons.h" +#include +#include +#include +#include + +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON(BreezeDecoFactory, + "breeze.json", + registerPlugin(); + registerPlugin(QStringLiteral("button")); + ) + +namespace Breeze +{ + +ColorSettings::ColorSettings(const QPalette &pal) +{ + init(pal); +} + +void ColorSettings::update(const QPalette &pal) +{ + init(pal); +} + +void ColorSettings::init(const QPalette &pal) +{ + m_palette = pal; + KConfigGroup wmConfig(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("WM")); + m_activeFrameColor = wmConfig.readEntry("frame", pal.color(QPalette::Active, QPalette::Background)); + m_inactiveFrameColor = wmConfig.readEntry("inactiveFrame", m_activeFrameColor); + m_activeTitleBarColor = wmConfig.readEntry("activeBackground", pal.color(QPalette::Active, QPalette::Highlight)); + m_inactiveTitleBarColor = wmConfig.readEntry("inactiveBackground", m_inactiveFrameColor); + m_activeFontColor = wmConfig.readEntry("activeForeground", pal.color(QPalette::Active, QPalette::HighlightedText)); + m_inactiveFontColor = wmConfig.readEntry("inactiveForeground", m_activeFontColor.dark()); +} + +Decoration::Decoration(QObject *parent, const QVariantList &args) + : KDecoration2::Decoration(parent, args) + , m_colorSettings(client().data()->palette()) + , m_leftButtons(nullptr) + , m_rightButtons(nullptr) +{ +} + +Decoration::~Decoration() = default; + +void Decoration::init() +{ + recalculateBorders(); + updateTitleBar(); + auto s = settings(); + connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, this, &Decoration::recalculateBorders); + // a change in font might cause the borders to change + connect(s.data(), &KDecoration2::DecorationSettings::fontChanged, this, &Decoration::recalculateBorders); + connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::recalculateBorders); + connect(client().data(), &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::recalculateBorders); + connect(client().data(), &KDecoration2::DecoratedClient::maximizedHorizontallyChanged, this, &Decoration::recalculateBorders); + connect(client().data(), &KDecoration2::DecoratedClient::maximizedVerticallyChanged, this, &Decoration::recalculateBorders); + connect(client().data(), &KDecoration2::DecoratedClient::captionChanged, this, + [this]() { + // update the caption area + update(captionRect()); + } + ); + connect(client().data(), &KDecoration2::DecoratedClient::activeChanged, this, [this]() { update(); }); + connect(client().data(), &KDecoration2::DecoratedClient::paletteChanged, this, + [this]() { + m_colorSettings.update(client().data()->palette()); + update(); + } + ); + connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateTitleBar); + connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateTitleBar); + connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::setOpaque); + + connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateButtonPositions); + connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateButtonPositions); + connect(client().data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateButtonPositions); + + createButtons(); + createShadow(); +} + +void Decoration::updateTitleBar() +{ + auto s = settings(); + const bool maximized = client().data()->isMaximized(); + const int width = client().data()->width(); + const int height = maximized ? borderTop() : borderTop() - s->smallSpacing(); + const int x = maximized ? 0 : s->largeSpacing() / 2; + const int y = maximized ? 0 : s->smallSpacing(); + setTitleBar(QRect(x, y, width, height)); +} + +static int borderSize(const QSharedPointer &settings, bool bottom) { + const int baseSize = settings->largeSpacing() / 2; + switch (settings->borderSize()) { + case KDecoration2::BorderSize::None: + return 0; + case KDecoration2::BorderSize::NoSides: + return bottom ? baseSize : 0; + case KDecoration2::BorderSize::Tiny: + return baseSize / 2; + case KDecoration2::BorderSize::Normal: + return baseSize; + case KDecoration2::BorderSize::Large: + return baseSize * 1.5; + case KDecoration2::BorderSize::VeryLarge: + return baseSize * 2; + case KDecoration2::BorderSize::Huge: + return baseSize * 2.5; + case KDecoration2::BorderSize::VeryHuge: + return baseSize * 3; + case KDecoration2::BorderSize::Oversized: + return baseSize * 5; + default: + return baseSize; + } +} + +static int borderSize(const QSharedPointer &settings) { + return borderSize(settings, false); +} + +void Decoration::recalculateBorders() +{ + auto s = settings(); + const auto c = client().data(); + const Qt::Edges edges = c->adjacentScreenEdges(); + int left = c->isMaximizedHorizontally() || edges.testFlag(Qt::LeftEdge) ? 0 : borderSize(s); + int right = c->isMaximizedHorizontally() || edges.testFlag(Qt::RightEdge) ? 0 : borderSize(s); + + QFontMetrics fm(s->font()); + int top = qMax(fm.boundingRect(c->caption()).height(), s->gridUnit() * 2); + // padding below + top += s->smallSpacing() * 2 + 1; + if (!c->isMaximized()) { + // padding above only on maximized + top += s->smallSpacing(); + } + + int bottom = c->isMaximizedVertically() || edges.testFlag(Qt::BottomEdge) ? 0 : borderSize(s, true); + setBorders(QMargins(left, top, right, bottom)); + + const int extSize = s->largeSpacing() / 2; + int extSides = 0; + int extBottom = 0; + if (s->borderSize() == KDecoration2::BorderSize::None) { + extSides = extSize; + extBottom = extSize; + } else if (s->borderSize() == KDecoration2::BorderSize::NoSides) { + extSides = extSize; + } + setResizeOnlyBorders(QMargins(extSides, 0, extSides, extBottom)); +} + +void Decoration::createButtons() +{ + m_leftButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Left, this, &Button::create); + m_rightButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Right, this, &Button::create); + updateButtonPositions(); +} + +void Decoration::updateButtonPositions() +{ + auto s = settings(); + const int padding = client().data()->isMaximized() ? 0 : s->smallSpacing(); + m_rightButtons->setSpacing(s->smallSpacing()); + m_leftButtons->setSpacing(s->smallSpacing()); + m_leftButtons->setPos(QPointF(padding, padding)); + m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - padding, padding)); +} + +void Decoration::paint(QPainter *painter, const QRect &repaintRegion) +{ + // TODO: optimize based on repaintRegion + // paint background + painter->fillRect(rect(), Qt::transparent); + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(m_colorSettings.frame(client().data()->isActive())); + // clip away the top part + painter->save(); + painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip); + painter->drawRoundedRect(rect(), 5.0, 5.0); + painter->restore(); + + paintTitleBar(painter, repaintRegion); + + painter->restore(); +} + +void Decoration::paintTitleBar(QPainter *painter, const QRect &repaintRegion) +{ + const auto c = client().data(); + const bool active = c->isActive(); + const QRect titleRect(QPoint(0, 0), QSize(size().width(), borderTop())); + const QColor titleBarColor(m_colorSettings.titleBarColor(c->isActive())); + // render a linear gradient on title area + QLinearGradient gradient; + gradient.setStart(0.0, 0.0); + gradient.setFinalStop(0.0, titleRect.height()); + gradient.setColorAt(0.0, titleBarColor.lighter(c->isActive() ? 120.0 : 100.0)); + gradient.setColorAt(0.8, titleBarColor); + gradient.setColorAt(1.0, titleBarColor); + gradient.setFinalStop(0.0, titleRect.height()); + painter->save(); + painter->setBrush(gradient); + painter->setPen(Qt::NoPen); + if (c->isMaximized()) { + painter->drawRect(titleRect); + } else { + painter->setClipRect(titleRect, Qt::IntersectClip); + // we make the rect a little bit larger to be able to clip away the rounded corners on bottom + painter->drawRoundedRect(titleRect.adjusted(0, 0, 0, 5), 5.0, 5.0); + } + painter->restore(); + auto s = settings(); + const int titleBarSpacer = s->smallSpacing(); + if (true) { + // TODO: should be config option + painter->fillRect(0, borderTop() - titleBarSpacer - 1, + size().width(), 1, + c->palette().color(active ? QPalette::Highlight: QPalette::Background)); + } + // draw title bar spacer + painter->fillRect(0, borderTop() - titleBarSpacer, size().width(), titleBarSpacer, c->palette().color(QPalette::Background)); + + // draw caption + painter->setFont(s->font()); + const QRect cR = captionRect(); + const QString caption = painter->fontMetrics().elidedText(c->caption(), Qt::ElideMiddle, cR.width()); + painter->setPen(m_colorSettings.font(c->isActive())); + painter->drawText(cR, Qt::AlignCenter | Qt::TextSingleLine, caption); + + // draw all buttons + m_leftButtons->paint(painter, repaintRegion); + m_rightButtons->paint(painter, repaintRegion); +} + +int Decoration::captionHeight() const +{ + return borderTop() - settings()->smallSpacing() * (client().data()->isMaximized() ? 2 : 3) - 1; +} + +QRect Decoration::captionRect() const +{ + const int leftOffset = m_leftButtons->geometry().x() + m_leftButtons->geometry().width(); + const int rightOffset = size().width() - m_rightButtons->geometry().x(); + const int offset = qMax(leftOffset, rightOffset); + const int yOffset = client().data()->isMaximized() ? 0 : settings()->smallSpacing(); + // below is the spacer + return QRect(offset, yOffset, size().width() - offset * 2, captionHeight()); +} + +void Decoration::createShadow() +{ + auto decorationShadow = QSharedPointer::create(); + decorationShadow->setPadding(QMargins(10, 10, 20, 20)); + decorationShadow->setInnerShadowRect(QRect(20, 20, 20, 20)); + + QImage image(60, 60, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + + auto gradientStopColor = [](qreal alpha) { + QColor color(35, 38, 41); + color.setAlphaF(alpha); + return color; + }; + QRadialGradient radialGradient(20, 20, 20); + radialGradient.setColorAt(0.0, gradientStopColor(0.35)); + radialGradient.setColorAt(0.25, gradientStopColor(0.25)); + radialGradient.setColorAt(0.5, gradientStopColor(0.13)); + radialGradient.setColorAt(0.75, gradientStopColor(0.04)); + radialGradient.setColorAt(1.0, gradientStopColor(0.0)); + + QLinearGradient linearGradient; + linearGradient.setColorAt(0.0, gradientStopColor(0.35)); + linearGradient.setColorAt(0.25, gradientStopColor(0.25)); + linearGradient.setColorAt(0.5, gradientStopColor(0.13)); + linearGradient.setColorAt(0.75, gradientStopColor(0.04)); + linearGradient.setColorAt(1.0, gradientStopColor(0.0)); + + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Source); + // topLeft + p.fillRect(QRect(0, 0, 20, 20), radialGradient); + // top + linearGradient.setStart(20, 20); + linearGradient.setFinalStop(20, 0); + p.fillRect(QRect(20, 0, 20, 20), linearGradient); + // topRight + radialGradient.setCenter(40.0, 20.0); + radialGradient.setFocalPoint(40.0, 20.0); + p.fillRect(QRect(40, 0, 20, 20), radialGradient); + // left + linearGradient.setStart(20, 20); + linearGradient.setFinalStop(0, 20); + p.fillRect(QRect(0, 20, 20, 20), linearGradient); + // bottom left + radialGradient.setCenter(20.0, 40.0); + radialGradient.setFocalPoint(20.0, 40.0); + p.fillRect(QRect(0, 40, 20, 20), radialGradient); + // bottom + linearGradient.setStart(20, 40); + linearGradient.setFinalStop(20, 60); + p.fillRect(QRect(20, 40, 20, 20), linearGradient); + // bottom right + radialGradient.setCenter(40.0, 40.0); + radialGradient.setFocalPoint(40.0, 40.0); + p.fillRect(QRect(40, 40, 20, 20), radialGradient); + // right + linearGradient.setStart(40, 20); + linearGradient.setFinalStop(60, 20); + p.fillRect(QRect(40, 20, 20, 20), linearGradient); + + decorationShadow->setShadow(image); + + setShadow(decorationShadow); +} + +} // namespace + +#include "breezedeco.moc" diff --git a/windec/kdecoration2/breezedeco.h b/windec/kdecoration2/breezedeco.h new file mode 100644 index 00000000..052a3984 --- /dev/null +++ b/windec/kdecoration2/breezedeco.h @@ -0,0 +1,123 @@ +/* + * Copyright 2014 Martin Gräßlin + * + * 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 . + */ +#ifndef BREEZE_DECORATION_H +#define BREEZE_DECORATION_H + +#include + +#include +#include +#include + +namespace KDecoration2 +{ + class DecorationButton; + class DecorationButtonGroup; +} + +namespace Breeze +{ + +// TODO: move to deco API +class ColorSettings +{ +public: + ColorSettings(const QPalette &pal); + + void update(const QPalette &pal); + + const QColor &titleBarColor(bool active) const { + return active ? m_activeTitleBarColor : m_inactiveTitleBarColor; + } + const QColor &activeTitleBarColor() const { + return m_activeTitleBarColor; + } + const QColor &inactiveTitleBarColor() const { + return m_inactiveTitleBarColor; + } + const QColor &frame(bool active) const { + return active ? m_activeFrameColor : m_inactiveFrameColor; + } + const QColor &activeFrame() const { + return m_activeFrameColor; + } + const QColor &inactiveFrame() const { + return m_inactiveFrameColor; + } + const QColor &font(bool active) const { + return active ? m_activeFontColor : m_inactiveFontColor; + } + const QColor &activeFont() const { + return m_activeFontColor; + } + const QColor &inactiveFont() const { + return m_inactiveFontColor; + } + const QPalette &palette() const { + return m_palette; + } +private: + void init(const QPalette &pal); + QColor m_activeTitleBarColor; + QColor m_inactiveTitleBarColor; + QColor m_activeFrameColor; + QColor m_inactiveFrameColor; + QColor m_activeFontColor; + QColor m_inactiveFontColor; + QPalette m_palette; +}; + +class Decoration : public KDecoration2::Decoration +{ + Q_OBJECT +public: + explicit Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList()); + virtual ~Decoration(); + + void paint(QPainter *painter, const QRect &repaintRegion) override; + + const ColorSettings &colorSettings() { + return m_colorSettings; + } + + int captionHeight() const; + +public Q_SLOTS: + void init() override; + +private Q_SLOTS: + void recalculateBorders(); + void updateButtonPositions(); + void updateTitleBar(); + +private: + QRect captionRect() const; + void createButtons(); + void paintTitleBar(QPainter *painter, const QRect &repaintRegion); + void createShadow(); + ColorSettings m_colorSettings; + QList m_buttons; + KDecoration2::DecorationButtonGroup *m_leftButtons; + KDecoration2::DecorationButtonGroup *m_rightButtons; +}; + +} // namespace + +#endif