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 @@
+
+
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
+