2025-10-29 22:20:21 +08:00
|
|
|
|
# coding:utf-8
|
|
|
|
|
|
import sys
|
|
|
|
|
|
from typing import Union
|
|
|
|
|
|
|
|
|
|
|
|
from PyQt6.QtCore import QRect, QSize, Qt
|
|
|
|
|
|
from PyQt6.QtGui import QColor, QIcon, QPainter, QPixmap
|
|
|
|
|
|
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QVBoxLayout, QWidget
|
2025-11-01 20:39:53 +08:00
|
|
|
|
from qfluentwidgets import MSFluentWindow, NavigationItemPosition, setTheme, Theme
|
2025-10-29 22:20:21 +08:00
|
|
|
|
from qfluentwidgets.common.config import qconfig
|
|
|
|
|
|
from qfluentwidgets.common.icon import FluentIconBase
|
|
|
|
|
|
from qfluentwidgets.common.router import qrouter
|
|
|
|
|
|
from qfluentwidgets.common.style_sheet import (
|
|
|
|
|
|
FluentStyleSheet,
|
|
|
|
|
|
isDarkTheme,
|
|
|
|
|
|
)
|
|
|
|
|
|
from qfluentwidgets.components.navigation import (
|
2025-11-01 20:39:53 +08:00
|
|
|
|
NavigationTreeWidget
|
2025-10-29 22:20:21 +08:00
|
|
|
|
)
|
|
|
|
|
|
from qfluentwidgets.components.widgets.frameless_window import FramelessWindow
|
|
|
|
|
|
|
|
|
|
|
|
from app.view.widgets.stacked_widget import StackedWidget
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
class CustomFluentWindow(MSFluentWindow):
|
|
|
|
|
|
"""自定义的Fluent窗口,基于MSFluentWindow实现"""
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
|
|
|
|
|
|
# 背景相关设置
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self._isMicaEnabled = False
|
|
|
|
|
|
self._lightBackgroundColor = QColor(240, 244, 249)
|
|
|
|
|
|
self._darkBackgroundColor = QColor(32, 32, 32)
|
|
|
|
|
|
self._backgroundPixmap = None # 存储背景图片
|
|
|
|
|
|
self._backgroundOpacity = 1.0 # 背景图片不透明度
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
|
|
|
|
|
# 启用mica效果
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self.setMicaEffectEnabled(True)
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
|
|
|
|
|
# 连接主题变化信号
|
2025-10-29 22:20:21 +08:00
|
|
|
|
qconfig.themeChangedFinished.connect(self._onThemeChangedFinished)
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def addSubInterface(
|
|
|
|
|
|
self,
|
|
|
|
|
|
interface: QWidget,
|
|
|
|
|
|
icon: Union[FluentIconBase, QIcon, str],
|
|
|
|
|
|
text: str,
|
2025-11-01 20:39:53 +08:00
|
|
|
|
selectedIcon: Union[FluentIconBase, QIcon, str] = None,
|
2025-10-29 22:20:21 +08:00
|
|
|
|
position=NavigationItemPosition.TOP,
|
2025-11-01 20:39:53 +08:00
|
|
|
|
parent=None,
|
|
|
|
|
|
isTransparent=False,
|
|
|
|
|
|
) -> NavigationTreeWidget:
|
|
|
|
|
|
"""添加子界面
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
|
----------
|
|
|
|
|
|
interface: QWidget
|
2025-11-01 20:39:53 +08:00
|
|
|
|
要添加的子界面
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
icon: FluentIconBase | QIcon | str
|
|
|
|
|
|
导航项的图标
|
|
|
|
|
|
|
|
|
|
|
|
selectedIcon: FluentIconBase | QIcon | str
|
|
|
|
|
|
选中状态下的图标
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
text: str
|
|
|
|
|
|
导航项的文本
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
position: NavigationItemPosition
|
|
|
|
|
|
导航项的位置
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
parent: QWidget
|
|
|
|
|
|
导航项的父项
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
isTransparent: bool
|
|
|
|
|
|
是否使用透明背景
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not interface.objectName():
|
|
|
|
|
|
raise ValueError("The object name of `interface` can't be empty string.")
|
|
|
|
|
|
|
|
|
|
|
|
interface.setProperty("isStackedTransparent", isTransparent)
|
|
|
|
|
|
|
|
|
|
|
|
# 使用MSFluentWindow的addSubInterface方法
|
|
|
|
|
|
if selectedIcon:
|
|
|
|
|
|
item = super().addSubInterface(interface, icon, text, selectedIcon, position)
|
|
|
|
|
|
else:
|
|
|
|
|
|
item = super().addSubInterface(interface, icon, text, position=position)
|
|
|
|
|
|
|
|
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
|
|
def removeInterface(self, interface, isDelete=False):
|
|
|
|
|
|
"""移除子界面
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
Parameters
|
|
|
|
|
|
----------
|
|
|
|
|
|
interface: QWidget
|
|
|
|
|
|
要移除的子界面
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
2025-11-01 20:39:53 +08:00
|
|
|
|
isDelete: bool
|
|
|
|
|
|
是否删除子界面
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 从stackedWidget中移除
|
|
|
|
|
|
self.stackedWidget.removeWidget(interface)
|
|
|
|
|
|
interface.hide()
|
|
|
|
|
|
|
|
|
|
|
|
# 从导航中移除
|
|
|
|
|
|
self.navigationInterface.removeWidget(interface.objectName())
|
|
|
|
|
|
|
|
|
|
|
|
if isDelete:
|
|
|
|
|
|
interface.deleteLater()
|
|
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def setCustomBackgroundColor(self, light, dark):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
"""设置自定义背景颜色
|
2025-10-29 22:20:21 +08:00
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
|
----------
|
|
|
|
|
|
light, dark: QColor | Qt.GlobalColor | str
|
2025-11-01 20:39:53 +08:00
|
|
|
|
亮/暗主题模式下的背景颜色
|
2025-10-29 22:20:21 +08:00
|
|
|
|
"""
|
|
|
|
|
|
self._lightBackgroundColor = QColor(light)
|
|
|
|
|
|
self._darkBackgroundColor = QColor(dark)
|
|
|
|
|
|
self._updateBackgroundColor()
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def setBackgroundImage(self, imagePath: str, opacity: float = 1.0):
|
|
|
|
|
|
"""设置背景图片
|
|
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
|
----------
|
|
|
|
|
|
imagePath: str
|
|
|
|
|
|
背景图片路径
|
|
|
|
|
|
opacity: float
|
|
|
|
|
|
背景图片不透明度,范围0.0-1.0
|
|
|
|
|
|
"""
|
2025-11-01 20:39:53 +08:00
|
|
|
|
# 如果启用了Mica效果,先禁用
|
|
|
|
|
|
if self._isMicaEnabled:
|
|
|
|
|
|
self.setMicaEffectEnabled(False)
|
|
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self._backgroundPixmap = QPixmap(imagePath)
|
|
|
|
|
|
if self._backgroundPixmap.isNull():
|
|
|
|
|
|
print(f"无法加载背景图片: {imagePath}")
|
|
|
|
|
|
return
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self._backgroundOpacity = max(0.0, min(1.0, opacity)) # 确保在0-1范围内
|
|
|
|
|
|
self.update() # 触发重绘
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def removeBackgroundImage(self):
|
|
|
|
|
|
"""移除背景图片"""
|
|
|
|
|
|
self._backgroundPixmap = None
|
|
|
|
|
|
self.update()
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def _normalBackgroundColor(self):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
if not self._isMicaEnabled:
|
2025-10-29 22:20:21 +08:00
|
|
|
|
return (
|
|
|
|
|
|
self._darkBackgroundColor
|
|
|
|
|
|
if isDarkTheme()
|
|
|
|
|
|
else self._lightBackgroundColor
|
|
|
|
|
|
)
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
return QColor(0, 0, 0, 0)
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def _onThemeChangedFinished(self):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
if self._isMicaEnabled:
|
|
|
|
|
|
# MSFluentWindow已经处理了Mica效果,这里只需要确保背景颜色正确
|
|
|
|
|
|
self.setBackgroundColor(self._normalBackgroundColor())
|
|
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def paintEvent(self, e):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
# 先调用父类的绘制方法
|
|
|
|
|
|
super().paintEvent(e)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果有背景图片,绘制背景图片
|
2025-10-29 22:20:21 +08:00
|
|
|
|
if self._backgroundPixmap and not self._backgroundPixmap.isNull():
|
2025-11-01 20:39:53 +08:00
|
|
|
|
painter = QPainter(self)
|
|
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
# 设置不透明度
|
|
|
|
|
|
painter.setOpacity(self._backgroundOpacity)
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
# 缩放图片以适应窗口大小
|
|
|
|
|
|
scaled_pixmap = self._backgroundPixmap.scaled(
|
|
|
|
|
|
self.size(), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation
|
|
|
|
|
|
)
|
|
|
|
|
|
painter.drawPixmap(0, 0, scaled_pixmap)
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def setMicaEffectEnabled(self, isEnabled: bool):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
"""设置是否启用mica效果,仅在Win11上可用"""
|
2025-10-29 22:20:21 +08:00
|
|
|
|
if sys.platform != "win32" or sys.getwindowsversion().build < 22000:
|
2025-11-01 20:39:53 +08:00
|
|
|
|
self._isMicaEnabled = False
|
2025-10-29 22:20:21 +08:00
|
|
|
|
return
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self._isMicaEnabled = isEnabled
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
if isEnabled:
|
|
|
|
|
|
# 启用Mica效果时移除背景图片
|
|
|
|
|
|
self.removeBackgroundImage()
|
2025-11-01 20:39:53 +08:00
|
|
|
|
# MSFluentWindow自带Mica效果支持
|
|
|
|
|
|
self.windowEffect.setMicaEffect(self.winId(), isDarkTheme())
|
2025-10-29 22:20:21 +08:00
|
|
|
|
else:
|
|
|
|
|
|
self.windowEffect.removeBackgroundEffect(self.winId())
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
self.setBackgroundColor(self._normalBackgroundColor())
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def isMicaEffectEnabled(self):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
"""获取是否启用了mica效果"""
|
2025-10-29 22:20:21 +08:00
|
|
|
|
return self._isMicaEnabled
|
2025-11-01 20:39:53 +08:00
|
|
|
|
|
2025-10-29 22:20:21 +08:00
|
|
|
|
def resizeEvent(self, e):
|
2025-11-01 20:39:53 +08:00
|
|
|
|
super().resizeEvent(e)
|
|
|
|
|
|
# 确保标题栏正确显示
|
|
|
|
|
|
if hasattr(self, 'titleBar'):
|
|
|
|
|
|
self.titleBar.raise_()
|