From 6e22fe8afbb00612eaf8f4bac1046607e8b054c0 Mon Sep 17 00:00:00 2001 From: liuxinhao Date: Thu, 20 Nov 2025 14:34:28 +0800 Subject: [PATCH] feature(kdecoration): Add internal theme support and refine drawing details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增两种内部主题,调整绘制细节 --- data/CMakeLists.txt | 1 - .../Kiran-Rounded/window-close-symbolic.svg | 52 ++++ .../window-maximize-symbolic.svg | 13 + .../window-minimize-symbolic.svg | 13 + .../Kiran-Rounded/window-restore-symbolic.svg | 13 + .../images/Kiran}/window-close-symbolic.svg | 0 .../Kiran}/window-maximize-symbolic.svg | 0 .../Kiran}/window-minimize-symbolic.svg | 0 data/kdecoration/kdecoration.qrc | 12 + plugins/kdecoration/CMakeLists.txt | 7 +- plugins/kdecoration/box-shadow-helper.h | 32 --- plugins/kdecoration/button.cpp | 75 ++++-- plugins/kdecoration/button.h | 5 + plugins/kdecoration/decoration.cpp | 255 +++++++++++++----- plugins/kdecoration/decoration.h | 44 ++- plugins/kdecoration/internel-setting.cpp | 82 ++++++ plugins/kdecoration/internel-setting.h | 101 +++++++ plugins/kdecoration/kiran.json | 8 +- plugins/kdecoration/plugin.cpp | 5 +- .../{box-shadow-helper.cpp => utils.cpp} | 145 ++++++++-- .../kdecoration/{shadow-params.h => utils.h} | 84 ++++++ 21 files changed, 801 insertions(+), 146 deletions(-) create mode 100644 data/kdecoration/images/Kiran-Rounded/window-close-symbolic.svg create mode 100644 data/kdecoration/images/Kiran-Rounded/window-maximize-symbolic.svg create mode 100644 data/kdecoration/images/Kiran-Rounded/window-minimize-symbolic.svg create mode 100644 data/kdecoration/images/Kiran-Rounded/window-restore-symbolic.svg rename data/{Kiran/actions/symbolic => kdecoration/images/Kiran}/window-close-symbolic.svg (100%) rename data/{Kiran/actions/symbolic => kdecoration/images/Kiran}/window-maximize-symbolic.svg (100%) rename data/{Kiran/actions/symbolic => kdecoration/images/Kiran}/window-minimize-symbolic.svg (100%) create mode 100644 data/kdecoration/kdecoration.qrc delete mode 100644 plugins/kdecoration/box-shadow-helper.h create mode 100644 plugins/kdecoration/internel-setting.cpp create mode 100644 plugins/kdecoration/internel-setting.h rename plugins/kdecoration/{box-shadow-helper.cpp => utils.cpp} (55%) rename plugins/kdecoration/{shadow-params.h => utils.h} (38%) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index e4717dc..3b7edf4 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -5,4 +5,3 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/kiran-qt5-integration.ini install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kiran-integration-theme.pc DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig/) -install(DIRECTORY Kiran DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons/) diff --git a/data/kdecoration/images/Kiran-Rounded/window-close-symbolic.svg b/data/kdecoration/images/Kiran-Rounded/window-close-symbolic.svg new file mode 100644 index 0000000..fa4f5a6 --- /dev/null +++ b/data/kdecoration/images/Kiran-Rounded/window-close-symbolic.svg @@ -0,0 +1,52 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/data/kdecoration/images/Kiran-Rounded/window-maximize-symbolic.svg b/data/kdecoration/images/Kiran-Rounded/window-maximize-symbolic.svg new file mode 100644 index 0000000..51b3b28 --- /dev/null +++ b/data/kdecoration/images/Kiran-Rounded/window-maximize-symbolic.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/data/kdecoration/images/Kiran-Rounded/window-minimize-symbolic.svg b/data/kdecoration/images/Kiran-Rounded/window-minimize-symbolic.svg new file mode 100644 index 0000000..4b49f9f --- /dev/null +++ b/data/kdecoration/images/Kiran-Rounded/window-minimize-symbolic.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/data/kdecoration/images/Kiran-Rounded/window-restore-symbolic.svg b/data/kdecoration/images/Kiran-Rounded/window-restore-symbolic.svg new file mode 100644 index 0000000..27b9f15 --- /dev/null +++ b/data/kdecoration/images/Kiran-Rounded/window-restore-symbolic.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/data/Kiran/actions/symbolic/window-close-symbolic.svg b/data/kdecoration/images/Kiran/window-close-symbolic.svg similarity index 100% rename from data/Kiran/actions/symbolic/window-close-symbolic.svg rename to data/kdecoration/images/Kiran/window-close-symbolic.svg diff --git a/data/Kiran/actions/symbolic/window-maximize-symbolic.svg b/data/kdecoration/images/Kiran/window-maximize-symbolic.svg similarity index 100% rename from data/Kiran/actions/symbolic/window-maximize-symbolic.svg rename to data/kdecoration/images/Kiran/window-maximize-symbolic.svg diff --git a/data/Kiran/actions/symbolic/window-minimize-symbolic.svg b/data/kdecoration/images/Kiran/window-minimize-symbolic.svg similarity index 100% rename from data/Kiran/actions/symbolic/window-minimize-symbolic.svg rename to data/kdecoration/images/Kiran/window-minimize-symbolic.svg diff --git a/data/kdecoration/kdecoration.qrc b/data/kdecoration/kdecoration.qrc new file mode 100644 index 0000000..442017d --- /dev/null +++ b/data/kdecoration/kdecoration.qrc @@ -0,0 +1,12 @@ + + + images/Kiran/window-close-symbolic.svg + images/Kiran/window-maximize-symbolic.svg + images/Kiran/window-minimize-symbolic.svg + + + images/Kiran-Rounded/window-close-symbolic.svg + images/Kiran-Rounded/window-maximize-symbolic.svg + images/Kiran-Rounded/window-minimize-symbolic.svg + + \ No newline at end of file diff --git a/plugins/kdecoration/CMakeLists.txt b/plugins/kdecoration/CMakeLists.txt index 8698251..8fa4098 100644 --- a/plugins/kdecoration/CMakeLists.txt +++ b/plugins/kdecoration/CMakeLists.txt @@ -10,9 +10,12 @@ find_package(KF5CoreAddons) find_package(KF5GuiAddons) find_package(KF5WindowSystem) -file(GLOB_RECURSE decoration_SRCS "*.cpp" "*.h" "*.ui") +file(GLOB_RECURSE DECORATION_SRCS "*.cpp" "*.h" "*.ui") +set(DECORATION_QRC ${PROJECT_SOURCE_DIR}/data/kdecoration/kdecoration.qrc) + add_library (${TARGET_NAME} MODULE - ${decoration_SRCS} + ${DECORATION_SRCS} + ${DECORATION_QRC} ) target_link_libraries (${TARGET_NAME} diff --git a/plugins/kdecoration/box-shadow-helper.h b/plugins/kdecoration/box-shadow-helper.h deleted file mode 100644 index 1fb120f..0000000 --- a/plugins/kdecoration/box-shadow-helper.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2020 ~ 2025 KylinSec Co., Ltd. - * kiran-qt5-integration is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * - * Author: liuxinhao - */ - -#pragma once -#include -#include -#include -#include - -namespace Kiran -{ -namespace KDecoration -{ -namespace BoxShadowHelper -{ -void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, - int radius, const QColor &color); - -} // namespace BoxShadowHelper -} // namespace KDecoration -} // namespace Kiran \ No newline at end of file diff --git a/plugins/kdecoration/button.cpp b/plugins/kdecoration/button.cpp index d5c11df..178b47e 100644 --- a/plugins/kdecoration/button.cpp +++ b/plugins/kdecoration/button.cpp @@ -19,6 +19,7 @@ #include #include #include +#include "internel-setting.h" namespace Kiran { @@ -33,10 +34,33 @@ struct ButtonRenderRule : icon(icon), hoverBackground(hoverBackground) {} }; +// clang-format off static QMap buttonRenderRuleMap = { - {KDecoration2::DecorationButtonType::Close, {QStringLiteral("window-close-symbolic"), QColor(255, 0, 0, 200)}}, - {KDecoration2::DecorationButtonType::Maximize, {QStringLiteral("window-maximize-symbolic"), QColor(65, 65, 65, 50)}}, - {KDecoration2::DecorationButtonType::Minimize, {QStringLiteral("window-minimize-symbolic"), QColor(65, 65, 65, 50)}}}; + // {type, {icon, hoverBackground}} + { + KDecoration2::DecorationButtonType::Close, + { + QStringLiteral("window-close-symbolic"), + QColor(255, 0, 0, 200) + } + }, + { + KDecoration2::DecorationButtonType::Maximize, + { + QStringLiteral("window-maximize-symbolic"), + QColor(65, 65, 65, 50) + } + }, + { + KDecoration2::DecorationButtonType::Minimize, + { + QStringLiteral("window-minimize-symbolic"), + QColor(65, 65, 65, 50) + } + } +}; +// clang-format on + Button::Button(Type type, Decoration *decoration, QObject *parent) : KDecoration2::DecorationButton(type, decoration, parent) { @@ -47,7 +71,11 @@ Button::Button(Type type, Decoration *decoration, QObject *parent) this, &Button::updateVisible); connect(decoratedClient.data(), &KDecoration2::DecoratedClient::minimizeableChanged, this, &Button::updateVisible); - setGeometry(QRect(QPoint(0, 0), QSize(decoration->titleBarHeight(), decoration->titleBarHeight()))); + + m_buttonSize = decoration->getInternelSetting()->buttonSize(); + m_decorationTheme = decoration->getInternelSetting()->decorationTheme(); + m_buttonRadius = decoration->getInternelSetting()->buttonRadius(); + setGeometry(QRect(QPoint(0, 0), QSize(m_buttonSize, m_buttonSize))); } Button::~Button() @@ -66,34 +94,39 @@ bool Button::isSupported(Type type) void Button::paint(QPainter *painter, const QRect &repaintRegion) { + Q_UNUSED(repaintRegion) + auto renderRule = buttonRenderRuleMap.value(type(), ButtonRenderRule(QString(), QColor())); - auto icon = QIcon::fromTheme(renderRule.icon); - + QIcon icon(QString(":/kdecoration/%1/%2").arg(m_decorationTheme).arg(renderRule.icon)); if (!icon.isNull()) { - auto pixmap = icon.pixmap(QSize(16, 16)); - auto pixmapRect = pixmap.rect(); - auto rect = geometry(); - pixmapRect.moveCenter(rect.center().toPoint()); - + + // 计算图标大小(可以比按钮稍小一些) + int iconSize = qRound(m_buttonSize * 0.75); + auto pixmap = icon.pixmap(QSize(iconSize, iconSize)); + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + + QPainterPath path; + path.addRoundedRect(rect.adjusted(0.5, 0.5, -0.5, -0.5), m_buttonRadius, m_buttonRadius); - // Background + // 悬浮、点击状态下填充背景色 bool hoverd = isHovered() || isPressed(); if (hoverd) { - // Draw hover background - auto background = renderRule.hoverBackground; painter->setPen(Qt::NoPen); - painter->setBrush(background); - painter->drawRect(rect); + painter->setBrush(renderRule.hoverBackground); + painter->drawPath(path); } - // Foreground - painter->setRenderHints(QPainter::Antialiasing, false); - painter->drawPixmap(pixmapRect, pixmap, pixmap.rect()); - + // Foreground - 居中绘制图标 + QRectF iconRect(QPoint(0, 0), pixmap.size()); + iconRect.moveCenter(rect.center()); + + painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter->drawPixmap(iconRect.topLeft(), pixmap); painter->restore(); } } @@ -122,7 +155,7 @@ void Button::updateVisible() visible = false; break; } - + if (isVisible() != visible) { setVisible(visible); diff --git a/plugins/kdecoration/button.h b/plugins/kdecoration/button.h index fd499a8..0601a25 100644 --- a/plugins/kdecoration/button.h +++ b/plugins/kdecoration/button.h @@ -34,6 +34,11 @@ public: private slots: void updateVisible(); + +private: + QString m_decorationTheme; + int m_buttonRadius = 0; + int m_buttonSize = 35; }; } // namespace KDecoration } // namespace Kiran \ No newline at end of file diff --git a/plugins/kdecoration/decoration.cpp b/plugins/kdecoration/decoration.cpp index 0b7cfaf..f9e1288 100644 --- a/plugins/kdecoration/decoration.cpp +++ b/plugins/kdecoration/decoration.cpp @@ -21,13 +21,14 @@ #include #include #include +#include #include #include #include -#include "box-shadow-helper.h" #include "button.h" +#include "internel-setting.h" #include "palette.h" -#include "shadow-params.h" +#include "utils.h" namespace Kiran { @@ -53,6 +54,12 @@ static QSharedPointer s_cachedShadow; Decoration::Decoration(QObject *parent, const QVariantList &args) : KDecoration2::Decoration(parent, args) { + QVariantMap map; + if (args.size() >= 1 && args.at(0).canConvert()) + { + map = args.at(0).toMap(); + } + m_internelSetting = new InternelSetting(map, this); ++s_decoCount; } @@ -69,40 +76,26 @@ void Decoration::paint(QPainter *painter, const QRect &repaintRegion) auto decoratedClient = client().toStrongRef(); if (!decoratedClient->isShaded()) { + // 绘制边框背景 paintFrameBackground(painter, repaintRegion); } - paintTitleBarBackground(painter, repaintRegion); paintButtons(painter, repaintRegion); paintCaption(painter, repaintRegion); + paintBorder(painter, repaintRegion); } void Decoration::init() { auto c = client().toStrongRef(); auto s = settings(); + auto themePalette = Theme::Palette::getDefault(); - connect(c.data(), &KDecoration2::DecoratedClient::widthChanged, - this, &Decoration::updateTitleBar); - connect(c.data(), &KDecoration2::DecoratedClient::widthChanged, - this, &Decoration::updateButtonsGeometry); - connect(c.data(), &KDecoration2::DecoratedClient::maximizedChanged, - this, &Decoration::updateButtonsGeometry); - - auto repaintTitleBar = [this] - { - update(titleBar()); - }; - connect(c.data(), &KDecoration2::DecoratedClient::captionChanged, - this, repaintTitleBar); - connect(c.data(), &KDecoration2::DecoratedClient::activeChanged, - this, repaintTitleBar); - connect(Theme::Palette::getDefault(), &Theme::Palette::baseColorsChanged, - this, repaintTitleBar); + Q_UNUSED(s) updateBorders(); - updateResizeBorders(); updateTitleBar(); + updateResizeBorders(); auto buttonCreator = [this](KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent) -> KDecoration2::DecorationButton * @@ -114,7 +107,6 @@ void Decoration::init() } return new Button(type, this, parent); }; - m_leftButtons = new KDecoration2::DecorationButtonGroup( KDecoration2::DecorationButtonGroup::Position::Left, this, @@ -124,52 +116,90 @@ void Decoration::init() KDecoration2::DecorationButtonGroup::Position::Right, this, buttonCreator); - updateButtonsGeometry(); // For some reason, the shadow should be installed the last. Otherwise, // the Window Decorations KCM crashes. updateShadow(); + + connect(themePalette, &Theme::Palette::baseColorsChanged, + this, [this] + { update(); }); + connect(c.data(), &KDecoration2::DecoratedClient::widthChanged, this, [this] + { + updateTitleBar(); + updateButtonsGeometry(); + update(); + }); + connect(c.data(), &KDecoration2::DecoratedClient::maximizedChanged, + this, [this] + { + updateBorders(); + updateTitleBar(); + updateButtonsGeometry(); + update(); + }); + auto repaintTitleBar = [this] + { + update(titleBar()); + }; + connect(c.data(), &KDecoration2::DecoratedClient::captionChanged, + this, repaintTitleBar); + connect(c.data(), &KDecoration2::DecoratedClient::activeChanged, + this, repaintTitleBar); } void Decoration::updateBorders() { - QMargins borders; - borders.setTop(titleBarHeight()); - setBorders(borders); + QMargins margins; + const int border = borderWidth(); + const int titleBarHeight = m_internelSetting->titleBarHeight(); + margins.setTop(titleBarHeight + border); + margins.setBottom(border); + margins.setLeft(border); + margins.setRight(border); + setBorders(margins); } void Decoration::updateResizeBorders() { QMargins borders; - const int extender = settings()->largeSpacing(); borders.setLeft(extender); borders.setTop(extender); borders.setRight(extender); borders.setBottom(0); - setResizeOnlyBorders(borders); } void Decoration::updateTitleBar() { + // 窗口标题栏填充边框区域,边框绘制在标题栏上方 auto decoratedClient = client().toStrongRef(); - setTitleBar(QRect(0, 0, decoratedClient->width(), titleBarHeight())); + const int border = borderWidth(); + QRect titleBarRect(0, 0, + decoratedClient->width() + 2 * border, + m_internelSetting->titleBarHeight() + border); + setTitleBar(titleBarRect); } void Decoration::updateButtonsGeometry() { + // 根据按钮大小以及标题栏高度,保持按钮居中 + auto buttonSize = m_internelSetting->buttonSize(); + auto titleBarHeightValue = m_internelSetting->titleBarHeight(); + qreal yOffset = (titleBarHeightValue - buttonSize) / 2.0; + if (!m_leftButtons->buttons().isEmpty()) { - m_leftButtons->setPos(QPointF(0, 0)); - m_leftButtons->setSpacing(0); + m_leftButtons->setPos(QPointF(m_internelSetting->buttonSpacing(), yOffset)); + m_leftButtons->setSpacing(m_internelSetting->buttonSpacing()); } if (!m_rightButtons->buttons().isEmpty()) { - m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width(), 0)); - m_rightButtons->setSpacing(0); + m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - m_internelSetting->buttonSpacing(), yOffset)); + m_rightButtons->setSpacing(m_internelSetting->buttonSpacing()); } update(); @@ -190,6 +220,8 @@ void Decoration::updateShadow() return c; }; + // 形成9Patch机制的阴影图像数据,窗口调整大小时无需重新计算阴影图像数据 + // // 为了保证阴影完整显示,需根据最大阴影半径计算绘制区域: // shadowSize:取两层阴影中较大的半径,确保所有阴影都能被容纳。 // box:实际用于绘制阴影的矩形区域,中心为(shadowSize, shadowSize),宽高为2*shadowSize+1。 @@ -198,40 +230,45 @@ void Decoration::updateShadow() const QRect box(shadowSize, shadowSize, 2 * shadowSize + 1, 2 * shadowSize + 1); const QRect rect = box.adjusted(-shadowSize, -shadowSize, shadowSize, shadowSize); + const int shadowRadius = m_internelSetting->borderRadius(); + CornerRadii radii(shadowRadius); + QPainterPath roundedBoxPath = Utils::createRoundedRectPath(box, radii, TopLeftCorner | TopRightCorner); + QImage shadow(rect.size(), QImage::Format_ARGB32_Premultiplied); shadow.fill(Qt::transparent); QPainter painter(&shadow); painter.setRenderHint(QPainter::Antialiasing); - // 绘制主体阴影 - BoxShadowHelper::boxShadow( + // 绘制主体阴影(使用圆角路径) + Utils::boxShadow( &painter, - box, + roundedBoxPath, s_shadowParams.shape.offset, s_shadowParams.shape.radius, withOpacity(s_shadowColor, s_shadowParams.shape.opacity)); - // 绘制对比阴影 - BoxShadowHelper::boxShadow( + // 绘制对比阴影(使用圆角路径) + Utils::boxShadow( &painter, - box, + roundedBoxPath, s_shadowParams.contrast.offset, s_shadowParams.contrast.radius, withOpacity(s_shadowColor, s_shadowParams.contrast.opacity)); - // 去除内矩形,只保留外环阴影 + // 去除内圆角矩形,只保留外环阴影 const QMargins padding = QMargins( shadowSize - s_shadowParams.offset.x(), shadowSize - s_shadowParams.offset.y(), shadowSize + s_shadowParams.offset.x(), shadowSize + s_shadowParams.offset.y()); const QRect innerRect = rect - padding; + QPainterPath innerRoundedPath = Utils::createRoundedRectPath(innerRect, radii, AllCorners); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); - painter.drawRect(innerRect); + painter.drawPath(innerRoundedPath); painter.end(); // 生成阴影对象并缓存 @@ -244,38 +281,75 @@ void Decoration::updateShadow() setShadow(s_cachedShadow); } -int Decoration::titleBarHeight() const -{ - return 35; -} - void Decoration::paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const { Q_UNUSED(repaintRegion) - const auto decoratedClient = client().toStrongRef(); - const auto group = decoratedClient->isActive() - ? KDecoration2::ColorGroup::Active - : KDecoration2::ColorGroup::Inactive; - const auto frameColor = decoratedClient->color(group, KDecoration2::ColorRole::Frame); - + const auto frameColor = frameBackgroundColor(); painter->save(); - painter->fillRect(rect(), Qt::transparent); painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); + painter->setPen(QPen(frameColor, 1)); painter->setBrush(frameColor); painter->setClipRect(0, borderTop(), size().width(), size().height() - borderTop(), Qt::IntersectClip); - painter->drawRect(rect()); + + const bool isMaximized = decoratedClient->isMaximized(); + if (isMaximized) + { + painter->drawRect(rect()); + } + else + { + CornerRadii radii(m_internelSetting->borderRadius()); + CornerPositions corners = BottomLeftCorner | BottomRightCorner; + QPainterPath path = Utils::createRoundedRectPath(rect(), radii, corners, 0); + painter->drawPath(path); + } painter->restore(); } +QColor Decoration::frameBackgroundColor() const +{ + const auto palette = Theme::Palette::getDefault(); + const auto decoratedClient = client().toStrongRef(); + const auto active = decoratedClient->isActive(); + auto bg = palette->getColor(active ? Theme::Palette::ACTIVE : Theme::Palette::INACTIVE, Theme::Palette::WINDOW); + return bg; +} + +int Decoration::borderWidth() const +{ + const auto decoratedClient = client().toStrongRef(); + const bool isMaximized = decoratedClient->isMaximized(); + return isMaximized ? 0 : m_internelSetting->borderWidth(); +} + +QColor Decoration::borderColor() const +{ + const auto palette = Theme::Palette::getDefault(); + const auto decoratedClient = client().toStrongRef(); + const auto active = decoratedClient->isActive(); + auto bg = palette->getColor(active ? Theme::Palette::ACTIVE : Theme::Palette::INACTIVE, Theme::Palette::BORDER); + return bg; +} + QColor Decoration::titleBarBackgroundColor() const { + const auto palette = Theme::Palette::getDefault(); const auto decoratedClient = client().toStrongRef(); - Q_UNUSED(decoratedClient); + const auto active = decoratedClient->isActive(); - auto palette = Theme::Palette::getDefault(); +#if 0 + auto bg = palette->getColor(active ? Theme::Palette::ACTIVE : Theme::Palette::INACTIVE, Theme::Palette::WINDOW); +#else + // 由于目前Theme::Palette INACTIVE状态与ACTIVE状态颜色相同 + // 暂时在外部对Active状态颜色调暗变成INACTIVE状态颜色(10%) auto bg = palette->getColor(Theme::Palette::ACTIVE, Theme::Palette::WINDOW); + if (!active) + { + bg = bg.darker(110); + } +#endif + return bg; } @@ -294,12 +368,24 @@ void Decoration::paintTitleBarBackground(QPainter *painter, const QRect &repaint Q_UNUSED(repaintRegion) const auto decoratedClient = client().toStrongRef(); + const bool isMaximized = decoratedClient->isMaximized(); auto rect = titleBar(); painter->save(); - painter->setPen(Qt::NoPen); + painter->setRenderHint(QPainter::Antialiasing); painter->setBrush(titleBarBackgroundColor()); - painter->drawRect(rect); + + if (isMaximized) + { + painter->drawRect(rect); + } + else + { + CornerRadii radii(m_internelSetting->borderRadius()); + CornerPositions corners = TopLeftCorner | TopRightCorner; + QPainterPath path = Utils::createRoundedRectPath(rect, radii, corners, 0.5); + painter->fillPath(path, titleBarBackgroundColor()); + } painter->restore(); } @@ -324,13 +410,15 @@ void Decoration::paintCaption(QPainter *painter, const QRect &repaintRegion) con iconSize.width(), iconSize.height()); - icon.paint(painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter, QIcon::Normal, decoratedClient->isActive() ? QIcon::On : QIcon::Off); + icon.paint(painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter, + QIcon::Normal, + decoratedClient->isActive() ? QIcon::On : QIcon::Off); availableRect.adjust(iconRect.height() + settings()->largeSpacing(), 0, 0, 0); } // 定位文本 const int textWidth = settings()->fontMetrics().boundingRect(decoratedClient->caption()).width(); - const QRect textRect(availableRect.topLeft(), QSize(textWidth, titleBarHeight())); + const QRect textRect(availableRect.topLeft(), QSize(textWidth, m_internelSetting->titleBarHeight())); Q_UNUSED(textRect); QRect captionRect = availableRect; @@ -354,5 +442,50 @@ void Decoration::paintButtons(QPainter *painter, const QRect &repaintRegion) con m_rightButtons->paint(painter, repaintRegion); } +void Decoration::paintBorder(QPainter *painter, const QRect &repaintRegion) const +{ + Q_UNUSED(repaintRegion) + const int width = this->borderWidth(); + if (width <= 0) + { + return; + } + + QPen pen(borderColor(), width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + + QPainterPath path = Utils::createRoundedRectPath(rect(), + CornerRadii(m_internelSetting->borderRadius()), + TopLeftCorner | TopRightCorner, + 0); + painter->strokePath(path, pen); + painter->restore(); +} + +ThemeProvider::ThemeProvider(QObject *parent, + const KPluginMetaData &data, + const QVariantList &args) + : KDecoration2::DecorationThemeProvider(parent, data, args), + m_data(data) +{ + init(); +} + +void ThemeProvider::init() +{ + auto themes = InternelSetting::supportedThemes(); + for (const auto &theme : themes) + { + KDecoration2::DecorationThemeMetaData data; + data.setPluginId(m_data.pluginId()); + data.setThemeName(theme); + data.setVisibleName(theme); + data.setHasConfiguration(true); + m_themes.append(data); + } +} } // namespace KDecoration } // namespace Kiran \ No newline at end of file diff --git a/plugins/kdecoration/decoration.h b/plugins/kdecoration/decoration.h index e6268e4..3dae6e6 100644 --- a/plugins/kdecoration/decoration.h +++ b/plugins/kdecoration/decoration.h @@ -15,38 +15,42 @@ #pragma once #include #include +#include +#include #include +#include "utils.h" namespace Kiran { namespace KDecoration { -class CloseButton; -class MaximizeButton; -class MinimizeButton; +class InternelSetting; class Decoration : public KDecoration2::Decoration { - Q_OBJECT friend class Button; - + Q_OBJECT public: Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList()); ~Decoration() override; - void paint(QPainter *painter, const QRect &repaintRegion) override; public slots: void init() override; private: + InternelSetting *getInternelSetting() const + { + return m_internelSetting; + } void updateBorders(); void updateResizeBorders(); void updateTitleBar(); void updateButtonsGeometry(); void updateShadow(); - int titleBarHeight() const; - + QColor frameBackgroundColor() const; + int borderWidth() const; + QColor borderColor() const; QColor titleBarBackgroundColor() const; QColor titleBarForegroundColor() const; @@ -54,10 +58,34 @@ private: void paintTitleBarBackground(QPainter *painter, const QRect &repaintRegion) const; void paintCaption(QPainter *painter, const QRect &repaintRegion) const; void paintButtons(QPainter *painter, const QRect &repaintRegion) const; + void paintBorder(QPainter *painter, const QRect &repaintRegion) const; private: KDecoration2::DecorationButtonGroup *m_leftButtons; KDecoration2::DecorationButtonGroup *m_rightButtons; + InternelSetting *m_internelSetting; +}; + +// 外部提取插件主题的接口 +class ThemeProvider : public KDecoration2::DecorationThemeProvider +{ + Q_OBJECT +public: + explicit ThemeProvider(QObject *parent, + const KPluginMetaData &data, + const QVariantList &args); + + QList themes() const override + { + return m_themes; + } + +private: + void init(); + +private: + const KPluginMetaData m_data; + QList m_themes; }; } // namespace KDecoration diff --git a/plugins/kdecoration/internel-setting.cpp b/plugins/kdecoration/internel-setting.cpp new file mode 100644 index 0000000..2a20977 --- /dev/null +++ b/plugins/kdecoration/internel-setting.cpp @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2020 ~ 2025 KylinSec Co., Ltd. + * kiran-qt5-integration is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * + * Author: liuxinhao + */ +#include "internel-setting.h" +#include +#include + +static QMap> themeConfig = { + {"Kiran", + { + {"titleBarHeight", 35}, + {"borderRadius", 0}, + {"borderWidth", 1}, + {"buttonRadius", 0}, + {"buttonSize", 35}, + {"buttonSpacing", 0} + } + }, + {"Kiran-Rounded", + { + {"titleBarHeight", 40}, + {"borderRadius", 8}, + {"borderWidth", 1}, + {"buttonRadius", 24}, + {"buttonSize", 24}, + {"buttonSpacing", 20} + } + }, +}; + +namespace Kiran +{ +namespace KDecoration +{ +InternelSetting::InternelSetting(QVariantMap settings, QObject *parent) + : QObject(parent) +{ + loadSettings(settings); +} + +InternelSetting::~InternelSetting() +{ +} + +QStringList InternelSetting::supportedThemes() +{ + return themeConfig.keys(); +} + +void InternelSetting::loadSettings(QVariantMap settings) +{ + if (settings.contains("theme") && settings.value("theme").canConvert()) + { + m_decorationTheme = settings.value("theme").toString(); + } + + if (!themeConfig.contains(m_decorationTheme)) + { + qWarning() << "InternelSetting loadSettings" << m_decorationTheme + << "not found, use default theme"; + m_decorationTheme = themeConfig.firstKey(); + } + + QMap config = themeConfig.value(m_decorationTheme); + for (auto it = config.constBegin(); it != config.constEnd(); ++it) + { + const QByteArray propName = it.key().toLocal8Bit(); + setProperty(propName.constData(), it.value()); + } +} +} // namespace KDecoration +} // namespace Kiran \ No newline at end of file diff --git a/plugins/kdecoration/internel-setting.h b/plugins/kdecoration/internel-setting.h new file mode 100644 index 0000000..5bfcd10 --- /dev/null +++ b/plugins/kdecoration/internel-setting.h @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2020 ~ 2025 KylinSec Co., Ltd. + * kiran-qt5-integration is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * + * Author: liuxinhao + */ +#pragma once +#include +#include + +namespace Kiran +{ +namespace KDecoration +{ +class InternelSetting : public QObject +{ + Q_OBJECT + Q_PROPERTY(int titleBarHeight READ titleBarHeight WRITE setTitleBarHeight) + Q_PROPERTY(int borderRadius READ borderRadius WRITE setBorderRadius) + Q_PROPERTY(int borderWidth READ borderWidth WRITE setBorderWidth) + Q_PROPERTY(int buttonRadius READ buttonRadius WRITE setButtonRadius) + Q_PROPERTY(int buttonSize READ buttonSize WRITE setButtonSize) + Q_PROPERTY(int buttonSpacing READ buttonSpacing WRITE setButtonSpacing) +public: + explicit InternelSetting(QVariantMap settings = QVariantMap(), QObject *parent = nullptr); + ~InternelSetting() override; + static QStringList supportedThemes(); + QString decorationTheme() const + { + return m_decorationTheme; + }; + int titleBarHeight() const + { + return m_titleBarHeight; + }; + int borderRadius() const + { + return m_borderRadius; + }; + int borderWidth() const + { + return m_borderWidth; + }; + int buttonRadius() const + { + return m_buttonRadius; + }; + int buttonSize() const + { + return m_buttonSize; + }; + int buttonSpacing() const + { + return m_buttonSpacing; + }; + +private: + void loadSettings(QVariantMap settings); + void setTitleBarHeight(int height) + { + m_titleBarHeight = height; + } + void setBorderRadius(int radius) + { + m_borderRadius = radius; + } + void setBorderWidth(int width) + { + m_borderWidth = width; + } + void setButtonRadius(int radius) + { + m_buttonRadius = radius; + } + void setButtonSize(int size) + { + m_buttonSize = size; + } + void setButtonSpacing(int spacing) + { + m_buttonSpacing = spacing; + } + +private: + QString m_decorationTheme; + int m_titleBarHeight = 35; + int m_borderRadius = 0; + int m_borderWidth = 1; + int m_buttonRadius = 0; + int m_buttonSize = 35; + int m_buttonSpacing = 0; +}; +} // namespace KDecoration +} // namespace Kiran \ No newline at end of file diff --git a/plugins/kdecoration/kiran.json b/plugins/kdecoration/kiran.json index 6927968..a17c417 100644 --- a/plugins/kdecoration/kiran.json +++ b/plugins/kdecoration/kiran.json @@ -9,6 +9,10 @@ ] }, "org.kde.kdecoration2": { - "blur": false + "blur": false, + "borderRadius": 0, + "themes": true, + "defaultTheme": "Kiran", + "themeListKeyword": "themes" } -} +} \ No newline at end of file diff --git a/plugins/kdecoration/plugin.cpp b/plugins/kdecoration/plugin.cpp index 4e0e9aa..ab423a5 100644 --- a/plugins/kdecoration/plugin.cpp +++ b/plugins/kdecoration/plugin.cpp @@ -18,6 +18,9 @@ K_PLUGIN_FACTORY_WITH_JSON( KiranDecorationFactory, "kiran.json", - registerPlugin();); + registerPlugin(); + registerPlugin(); +); + #include "plugin.moc" diff --git a/plugins/kdecoration/box-shadow-helper.cpp b/plugins/kdecoration/utils.cpp similarity index 55% rename from plugins/kdecoration/box-shadow-helper.cpp rename to plugins/kdecoration/utils.cpp index 8dca1b7..3578fcf 100644 --- a/plugins/kdecoration/box-shadow-helper.cpp +++ b/plugins/kdecoration/utils.cpp @@ -1,17 +1,18 @@ /** - * Copyright (c) 2020 ~ 2025 KylinSec Co., Ltd. - * kiran-qt5-integration is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - * - * Author: liuxinhao - */ -#include "box-shadow-helper.h" + * Copyright (c) 2020 ~ 2025 KylinSec Co., Ltd. + * kiran-qt5-integration is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * + * Author: liuxinhao + */ + +#include "utils.h" #include #include @@ -19,8 +20,73 @@ namespace Kiran { namespace KDecoration { -namespace BoxShadowHelper +namespace Utils { +QPainterPath createRoundedRectPath(const QRect &rect, + const CornerRadii &cornerRadii, + CornerPositions corners, + qreal extension) +{ + QPainterPath path; + const qreal pixelOffset = 0.5; // 像素对齐偏移 + + // 计算实际边界(考虑扩展) + const qreal left = rect.left() + pixelOffset - extension; + const qreal right = rect.right() + pixelOffset + extension; + const qreal top = rect.top() + pixelOffset - extension; + const qreal bottom = rect.bottom() + pixelOffset + extension; + + // 左上角 + if (corners & TopLeftCorner && cornerRadii.topLeft > 0) + { + const int radius = cornerRadii.topLeft; + path.moveTo(left + radius, top); + path.arcTo(left, top, 2 * radius, 2 * radius, 90, 90); + } + else + { + path.moveTo(left, top); + } + + // 左下角 + if (corners & BottomLeftCorner && cornerRadii.bottomLeft > 0) + { + const int radius = cornerRadii.bottomLeft; + path.lineTo(left, bottom - radius); + path.arcTo(left, bottom - 2 * radius, 2 * radius, 2 * radius, 180, 90); + } + else + { + path.lineTo(left, bottom); + } + + // 右下角 + if (corners & BottomRightCorner && cornerRadii.bottomRight > 0) + { + const int radius = cornerRadii.bottomRight; + path.lineTo(right - radius, bottom); + path.arcTo(right - 2 * radius, bottom - 2 * radius, 2 * radius, 2 * radius, 270, 90); + } + else + { + path.lineTo(right, bottom); + } + + // 右上角 + if (corners & TopRightCorner && cornerRadii.topRight > 0) + { + const int radius = cornerRadii.topRight; + path.lineTo(right, top + radius); + path.arcTo(right - 2 * radius, top, 2 * radius, 2 * radius, 0, 90); + } + else + { + path.lineTo(right, top); + } + + path.closeSubpath(); + return path; +} // According to the CSS Level 3 spec, standard deviation must be equal to // half of the blur radius. https://www.w3.org/TR/css-backgrounds-3/#shadow-blur // Current window size is too small for sigma equal to half of the blur radius. @@ -29,12 +95,12 @@ namespace BoxShadowHelper // Maybe, it should be changed in the future. const qreal SIGMA_BLUR_SCALE = 0.4375; -inline qreal radiusToSigma(qreal radius) +qreal radiusToSigma(qreal radius) { return radius * SIGMA_BLUR_SCALE; } -inline int boxSizeToRadius(int boxSize) +int boxSizeToRadius(int boxSize) { return (boxSize - 1) / 2; } @@ -180,6 +246,49 @@ void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, int radius, p->drawImage(shadowRect, shadow); } -} // namespace BoxShadowHelper +/** + * @brief 在指定路径绘制带有模糊效果的阴影(支持圆角) + */ +void boxShadow(QPainter *p, const QPainterPath &path, const QPoint &offset, int radius, const QColor &color) +{ + // 1. 获取路径的边界矩形,计算阴影图像尺寸 + const QRect pathBounds = path.boundingRect().toAlignedRect(); + const QSize size = pathBounds.size() + 2 * QSize(radius, radius); + const qreal dpr = p->device()->devicePixelRatioF(); + + QPainter painter; + + // 2. 创建一个透明的 QImage 作为阴影缓冲区 + QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied); + shadow.setDevicePixelRatio(dpr); + shadow.fill(Qt::transparent); + + // 3. 在缓冲区中,使用路径填充黑色区域 + painter.begin(&shadow); + painter.setRenderHint(QPainter::Antialiasing); + // 将路径平移到阴影图像中的正确位置(考虑半径偏移) + QPainterPath translatedPath = path; + translatedPath.translate(radius - pathBounds.left(), radius - pathBounds.top()); + painter.fillPath(translatedPath, Qt::black); + painter.end(); + + // 4. 对缓冲区的 alpha 通道进行多次 box blur,实现高斯模糊近似 + const int numIterations = 3; + boxBlurAlpha(shadow, radius, numIterations); + + // 5. 用目标颜色覆盖整个阴影区域,保留原本的Alpha + painter.begin(&shadow); + painter.setCompositionMode(QPainter::CompositionMode_SourceIn); + painter.fillRect(shadow.rect(), color); + painter.end(); + + // 6. 计算阴影最终显示的位置(中心点偏移),并将阴影绘制到目标 QPainter 上 + QRect shadowRect = shadow.rect(); + shadowRect.setSize(shadowRect.size() / dpr); + shadowRect.moveCenter(pathBounds.center() + offset); + p->drawImage(shadowRect, shadow); +} + +} // namespace Utils } // namespace KDecoration -} // namespace Kiran \ No newline at end of file +} // namespace Kiran diff --git a/plugins/kdecoration/shadow-params.h b/plugins/kdecoration/utils.h similarity index 38% rename from plugins/kdecoration/shadow-params.h rename to plugins/kdecoration/utils.h index 146f4f7..8b8941a 100644 --- a/plugins/kdecoration/shadow-params.h +++ b/plugins/kdecoration/utils.h @@ -11,13 +11,21 @@ * * Author: liuxinhao */ + #pragma once +#include +#include +#include +#include +#include +#include #include namespace Kiran { namespace KDecoration { + // 阴影参数结构体,描述单层阴影的偏移、半径和透明度 struct ShadowParams { @@ -45,5 +53,81 @@ struct CompositeShadowParams ShadowParams contrast; }; +// 圆角位置标志 +enum CornerPosition +{ + NoCorner = 0x00, + TopLeftCorner = 0x01, // 左上角 + TopRightCorner = 0x02, // 右上角 + BottomLeftCorner = 0x04, // 左下角 + BottomRightCorner = 0x08, // 右下角 + AllCorners = TopLeftCorner | TopRightCorner | BottomLeftCorner | BottomRightCorner +}; +Q_DECLARE_FLAGS(CornerPositions, CornerPosition) +Q_DECLARE_OPERATORS_FOR_FLAGS(CornerPositions) + +// 圆角大小定义 +struct CornerRadii +{ + int topLeft; // 左上角圆角半径 + int topRight; // 右上角圆角半径 + int bottomLeft; // 左下角圆角半径 + int bottomRight; // 右下角圆角半径 + + CornerRadii() + : topLeft(0), topRight(0), bottomLeft(0), bottomRight(0) + { + } + + CornerRadii(int radius) + : topLeft(radius), topRight(radius), bottomLeft(radius), bottomRight(radius) + { + } + + CornerRadii(int topLeft, int topRight, int bottomLeft, int bottomRight) + : topLeft(topLeft), topRight(topRight), bottomLeft(bottomLeft), bottomRight(bottomRight) + { + } +}; + + +namespace Utils +{ +/** + * @brief 创建指定圆角的矩形路径 + * @param rect 矩形区域 + * @param cornerRadii 各角的圆角半径 + * @param corners 需要应用圆角的位置(使用 CornerPositions 枚举组合) + * @param extension 扩展像素数(用于与相邻区域重叠) + * @return 圆角矩形路径 + */ +QPainterPath createRoundedRectPath(const QRect &rect, + const CornerRadii &cornerRadii, + CornerPositions corners = AllCorners, + qreal extension = 0.0); +/** + * @brief 在指定区域绘制带有模糊效果的矩形阴影 + * @param p 绘图设备 + * @param box 矩形区域 + * @param offset 阴影偏移 + * @param radius 阴影半径 + * @param color 阴影颜色 + */ +void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, + int radius, const QColor &color); + +/** + * @brief 在指定路径绘制带有模糊效果的阴影(支持圆角) + * @param p 绘图设备 + * @param path 路径 + * @param offset 阴影偏移 + * @param radius 阴影半径 + * @param color 阴影颜色 + */ +void boxShadow(QPainter *p, const QPainterPath &path, const QPoint &offset, + int radius, const QColor &color); + +} // namespace Utils } // namespace KDecoration } // namespace Kiran + -- Gitee