diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index e4717dc45720b0f8f98829c23a1a32200a5c0ce0..3b7edf48687377277278b3ffb960c1a3e3cbb554 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 0000000000000000000000000000000000000000..fa4f5a63310890bdb8fbb1c4162a284ab8b0699e --- /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 0000000000000000000000000000000000000000..51b3b2864cd13f0cb3d1195731269700f1b1e104 --- /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 0000000000000000000000000000000000000000..4b49f9fe1e4d47aaaee0f3ecd9db7a2b7b61b832 --- /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 0000000000000000000000000000000000000000..27b9f154c8696f000395e91c164b22f5b1dfc108 --- /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 0000000000000000000000000000000000000000..442017dec85b37d9ec2eb06ae34a03e46f6a5bfb --- /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 869825165278daad108f9084026cbb9a027ef4cf..8fa4098154d12a818a89c36498319f3370a2f153 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 1fb120fdfb55bbf9df9457d99591202c1e2b4c8e..0000000000000000000000000000000000000000 --- 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 d5c11dfef718e6b2c0b1647c58ba0bde78e7bf2a..178b47eea79e2ba109ba5cbaf7d3ff484137f31c 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 fd499a8ecdedecdbedbfcee2404bf1c8f053e36b..0601a256b95ade25ef60fa3afe3950185369bfcd 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 0b7cfaf0d2eea47f158146e94de14a96f13a4883..f9e1288d0a294ee215431c8220fe2dd18ea6961a 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 e6268e418169dbcf9eb721d77365178bc49a9207..3dae6e654d9ec9a563b301f1ae4d095e01c25d77 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 0000000000000000000000000000000000000000..2a20977387ce293ee0aef7ae497a0ee4a796b0df --- /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 0000000000000000000000000000000000000000..5bfcd105c6fd26f3c360d16b6869e6c0dd7db44e --- /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 69279685f56a86f2d6b7e8aea0ad61a4e640283f..a17c4173e202bffd8d6be8040f41562873d86bff 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 4e0e9aac9b55c8d8cc15cb3e6bc4e08f5a08e6e4..ab423a56f1b0de2e792799d7517fbe059cde23e2 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 8dca1b71ceffa8bc7ff326d334f43e5e425d5aa5..3578fcf351049c2244d0eaca123aac183abb9074 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 146f4f7db41764380e14e4c0562adf4602fc9979..8b8941a91866677ac79796fc209e5fff9f541f7d 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 +