提交 2dc6bb7b 编写于 作者: 之一Yo's avatar 之一Yo

添加导航组件

上级 226e18a1
# coding:utf-8
import sys
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtGui import QIcon, QPainter, QImage, QBrush, QColor, QFont
from PyQt5.QtWidgets import QApplication, QFrame, QStackedWidget, QHBoxLayout, QLabel
from qfluentwidgets import NavigationInterface, NavigationItemPostion, NavigationWidget, MessageBox
from qfluentwidgets import FluentIconFactory as FIF
from qframelesswindow import FramelessWindow, StandardTitleBar
class Widget(QFrame):
def __init__(self, text: str, parent=None):
super().__init__(parent=parent)
self.label = QLabel(text, self)
self.label.setAlignment(Qt.AlignCenter)
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.addWidget(self.label, 1, Qt.AlignCenter)
self.setObjectName(text.replace(' ', '-'))
class AvatarWidget(NavigationWidget):
""" Avatar widget """
def __init__(self, parent=None):
super().__init__(isSelectable=False, parent=parent)
self.avatar = QImage('resource/shoko.png').scaled(
24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(
QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
if self.isPressed:
painter.setOpacity(0.7)
# draw background
if self.isSelected:
painter.setBrush(QColor(0, 0, 0, 6 if self.isEnter else 10))
painter.drawRoundedRect(self.rect(), 5, 5)
# draw indicator
painter.setBrush(QColor(0, 153, 188))
painter.drawRoundedRect(0, 10, 3, 16, 1.5, 1.5)
elif self.isEnter:
painter.setBrush(QColor(0, 0, 0, 10))
painter.drawRoundedRect(self.rect(), 5, 5)
# draw avatar
painter.setBrush(QBrush(self.avatar))
painter.translate(8, 6)
painter.drawEllipse(0, 0, 24, 24)
painter.translate(-8, -6)
if not self.isCompacted:
painter.setPen(Qt.black)
font = QFont('Segoe UI')
font.setPixelSize(14)
painter.setFont(font)
painter.drawText(QRect(44, 0, 255, 36), Qt.AlignVCenter, 'zhiyiYo')
class Window(FramelessWindow):
def __init__(self):
super().__init__()
self.setTitleBar(StandardTitleBar(self))
self.hBoxLayout = QHBoxLayout(self)
self.navigationInterface = NavigationInterface(self, True)
self.stackWidget = QStackedWidget(self)
# create sub interface
self.searchInterface = Widget('Search Interface', self)
self.musicInterface = Widget('Music Interface', self)
self.videoInterface = Widget('Video Interface', self)
self.folderInterface = Widget('Folder Interface', self)
self.settingInterface = Widget('Setting Interface', self)
self.stackWidget.addWidget(self.searchInterface)
self.stackWidget.addWidget(self.musicInterface)
self.stackWidget.addWidget(self.videoInterface)
self.stackWidget.addWidget(self.folderInterface)
self.stackWidget.addWidget(self.settingInterface)
# initialize layout
self.hBoxLayout.setSpacing(0)
self.hBoxLayout.setContentsMargins(0, self.titleBar.height(), 0, 0)
self.hBoxLayout.addWidget(self.navigationInterface)
self.hBoxLayout.addWidget(self.stackWidget)
self.hBoxLayout.setStretchFactor(self.stackWidget, 1)
# add items to navigation interface
self.navigationInterface.addItem(
routeKey=self.searchInterface.objectName(),
iconPath=FIF.path(FIF.SEARCH),
text='Search',
onClick=lambda: self.switchTo(self.searchInterface)
)
self.navigationInterface.addItem(
routeKey=self.musicInterface.objectName(),
iconPath=FIF.path(FIF.MUSIC),
text='Music library',
onClick=lambda: self.switchTo(self.musicInterface)
)
self.navigationInterface.addItem(
routeKey=self.navigationInterface.objectName(),
iconPath=FIF.path(FIF.VIDEO),
text='Video library',
onClick=lambda: self.switchTo(self.videoInterface)
)
self.navigationInterface.addSeparator()
# add navigation items to scroll area
self.navigationInterface.addItem(
routeKey='folder',
iconPath=FIF.path(FIF.FOLDER),
text='Folder library',
onClick=lambda: self.switchTo(self.folderInterface),
position=NavigationItemPostion.SCROLL
)
# for i in range(1, 21):
# self.navigationInterface.addItem(
# f'folder{i}',
# FIF.path(FIF.FOLDER),
# f'Folder {i}',
# lambda: print('Folder clicked'),
# position=NavigationItemPostion.SCROLL
# )
# add custom widget to bottom
self.navigationInterface.addWidget(
routeKey='avatar',
widget=AvatarWidget(),
onClick=self.showMessageBox,
position=NavigationItemPostion.BOTTOM
)
self.navigationInterface.addItem(
routeKey='setting',
iconPath=FIF.path(FIF.SETTING),
text='Settings',
onClick=lambda: self.switchTo(self.settingInterface),
position=NavigationItemPostion.BOTTOM
)
self.stackWidget.currentChanged.connect(self.onCurrentInterfaceChanged)
self.stackWidget.setCurrentIndex(1)
with open('resource/demo.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
self.resize(900, 700)
self.setWindowIcon(QIcon('resource/logo.png'))
self.setWindowTitle('PyQt-Fluent-Widgets')
desktop = QApplication.desktop().availableGeometry()
w, h = desktop.width(), desktop.height()
self.move(w//2 - self.width()//2, h//2 - self.height()//2)
def switchTo(self, widget):
self.stackWidget.setCurrentWidget(widget)
def onCurrentInterfaceChanged(self, index):
widget = self.stackWidget.widget(index)
self.navigationInterface.setCurrentItem(widget.objectName())
def showMessageBox(self):
w = MessageBox(
'This is a help message',
'You clicked a customized navigation widget. You can add more custom widgets by calling `NavigationInterface.addWidget()` 😉',
self
)
w.exec()
if __name__ == '__main__':
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
Widget > QLabel {
font: 24px 'Segoe UI', 'Microsoft YaHei';
}
Widget {
border: 1px solid rgb(229, 229, 229);
border-right: none;
border-bottom: none;
border-top-left-radius: 10px;
background-color: rgb(249, 249, 249);
}
Window {
background-color: rgb(243, 243, 243);
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 16 16">
<g>
<path id="path1" transform="rotate(0,8,8) translate(0,0.5) scale(0.166666666666667,0.166666666666667) " fill="#000000" d="M3,72L93,72 94.13671875,72.22265625 95.109375,72.890625 95.77734375,73.86328125 96,75 95.77734375,76.13671875 95.109375,77.109375 94.13671875,77.77734375 93,78 3,78 1.86328125,77.77734375 0.890625,77.109375 0.22265625,76.13671875 0,75 0.22265625,73.86328125 0.890625,72.890625 1.86328125,72.22265625 3,72z M3,42L93,42 94.13671875,42.2226600646973 95.109375,42.890625 95.77734375,43.8632850646973 96,45 95.77734375,46.1367225646973 95.109375,47.109375 94.13671875,47.77734375 93,48 3,48 1.86328125,47.77734375 0.890625,47.109375 0.22265625,46.1367225646973 0,45 0.22265625,43.8632850646973 0.890625,42.890625 1.86328125,42.2226600646973 3,42z M3,12L93,12 94.13671875,12.22265625 95.109375,12.890625 95.77734375,13.86328125 96,15 95.77734375,16.13671875 95.109375,17.109375 94.13671875,17.77734375 93,18 3,18 1.86328125,17.77734375 0.890625,17.109375 0.22265625,16.13671875 0,15 0.22265625,13.86328125 0.890625,12.890625 1.86328125,12.22265625 3,12z" />
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 16 16">
<g>
<path id="path1" transform="rotate(0,8,8) translate(0,0.5) scale(0.166666666666667,0.166666666666667) " fill="#ffffff" d="M3,72L93,72 94.13671875,72.22265625 95.109375,72.890625 95.77734375,73.86328125 96,75 95.77734375,76.13671875 95.109375,77.109375 94.13671875,77.77734375 93,78 3,78 1.86328125,77.77734375 0.890625,77.109375 0.22265625,76.13671875 0,75 0.22265625,73.86328125 0.890625,72.890625 1.86328125,72.22265625 3,72z M3,42L93,42 94.13671875,42.2226600646973 95.109375,42.890625 95.77734375,43.8632850646973 96,45 95.77734375,46.1367225646973 95.109375,47.109375 94.13671875,47.77734375 93,48 3,48 1.86328125,47.77734375 0.890625,47.109375 0.22265625,46.1367225646973 0,45 0.22265625,43.8632850646973 0.890625,42.890625 1.86328125,42.2226600646973 3,42z M3,12L93,12 94.13671875,12.22265625 95.109375,12.890625 95.77734375,13.86328125 96,15 95.77734375,16.13671875 95.109375,17.109375 94.13671875,17.77734375 93,18 3,18 1.86328125,17.77734375 0.890625,17.109375 0.22265625,16.13671875 0,15 0.22265625,13.86328125 0.890625,12.890625 1.86328125,12.22265625 3,12z" />
</g>
</svg>
NavigationPanel {
background-color: rgba(32, 32, 32, --bgOpacity);
border: 1px solid rgba(57, 57, 57, --bgOpacity);
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
}
QScrollArea,
#scrollWidget {
border: none;
background-color: transparent;
}
NavigationInterface {
background-color: rgb(32, 32, 32);
}
\ No newline at end of file
NavigationPanel {
background-color: rgba(243, 243, 243, --bgOpacity);
border: 1px solid rgba(229, 229, 229, --bgOpacity);
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
}
QScrollArea, #scrollWidget {
border: none;
background-color: transparent;
}
NavigationInterface {
background-color: rgb(243, 243, 243);
}
\ No newline at end of file
此差异已折叠。
......@@ -75,6 +75,8 @@
<file>images/icons/Zoom_white.svg</file>
<file>images/icons/Language_black.svg</file>
<file>images/icons/Language_white.svg</file>
<file>images/icons/Menu_black.svg</file>
<file>images/icons/Menu_white.svg</file>
<file>images/state_tool_tip/close_normal.svg</file>
<file>images/state_tool_tip/close_hover.svg</file>
<file>images/state_tool_tip/close_pressed.svg</file>
......@@ -100,6 +102,7 @@
<file>qss/dark/folder_list_dialog.qss</file>
<file>qss/dark/setting_interface.qss</file>
<file>qss/dark/combo_box.qss</file>
<file>qss/dark/navigation_interface.qss</file>
<file>qss/light/color_dialog.qss</file>
<file>qss/light/dialog.qss</file>
......@@ -114,5 +117,6 @@
<file>qss/light/folder_list_dialog.qss</file>
<file>qss/light/setting_interface.qss</file>
<file>qss/light/combo_box.qss</file>
<file>qss/light/navigation_interface.qss</file>
</qresource>
</RCC>
\ No newline at end of file
from .config import *
from .auto_wrap import TextWrap
from .icon import Icon, getIconColor, drawSvgIcon, FluentIconFactory
from .icon import Icon, getIconColor, drawSvgIcon, FluentIconFactory, drawIcon
from .style_sheet import setStyleSheet, getStyleSheet
from .smooth_scroll import SmoothScroll, SmoothMode
\ No newline at end of file
......@@ -68,6 +68,28 @@ def drawSvgIcon(iconPath, painter, rect):
renderer.render(painter, QRectF(rect))
def drawIcon(iconPath, painter, rect):
""" draw icon
Parameters
----------
iconPath: str
the path of svg icon
painter: QPainter
painter
rect: QRect | QRectF
the rect to render icon
"""
if not iconPath.lower().endswith('svg'):
image = QImage(iconPath).scaled(
rect.width(), rect.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
painter.drawImage(rect, image)
else:
drawSvgIcon(iconPath, painter, rect)
class FluentIconFactory:
""" Fluent icon factory """
......@@ -80,6 +102,7 @@ class FluentIconFactory:
FONT = "Font"
INFO = "Info"
ZOOM = "Zoom"
MENU = "Menu"
CLOSE = "Close"
MOVIE = "Movie"
BRUSH = "Brush"
......
......@@ -2,3 +2,4 @@ from .dialog_box import *
from .layout import *
from .settings import *
from .widgets import *
from .navigation import *
\ No newline at end of file
# coding:utf-8
from PyQt5.QtCore import Qt, pyqtSignal, QObject
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QEvent
from PyQt5.QtGui import QColor, QResizeEvent
from PyQt5.QtWidgets import QLabel, QPushButton, QFrame, QVBoxLayout, QHBoxLayout
from qframelesswindow import FramelessDialog
......@@ -37,11 +37,23 @@ class Ui_MessageBox(QObject):
self.yesButton.setFocus()
self.buttonGroup.setFixedHeight(81)
self.contentLabel.setText(TextWrap.wrap(self.content, 100, False)[0])
self._adjustText()
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.cancelButton.clicked.connect(self.__onCancelButtonClicked)
def _adjustText(self):
if self.isWindow():
if self.parent():
chars = max(min(self.parent().width() / 9, 100), 30)
else:
chars = 100
else:
chars = max(min(self.window().width() / 9, 100), 30)
self.contentLabel.setText(TextWrap.wrap(self.content, chars, False)[0])
def __initLayout(self):
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
......@@ -113,8 +125,17 @@ class MessageBox(MaskDialogBase, Ui_MessageBox):
self.setShadowEffect(60, (0, 10), QColor(0, 0, 0, 50))
self.setMaskColor(QColor(0, 0, 0, 76))
self._hBoxLayout.removeWidget(self.widget)
self._hBoxLayout.addWidget(self.widget, 1, Qt.AlignCenter)
self.widget.setFixedSize(
max(self.contentLabel.width(), self.titleLabel.width())+48,
self.contentLabel.y() + self.contentLabel.height() + 105
)
\ No newline at end of file
)
def eventFilter(self, obj, e: QEvent):
if obj is self.window():
if e.type() == QEvent.Resize:
self._adjustText()
return super().eventFilter(obj, e)
......@@ -12,7 +12,7 @@ class MaskDialogBase(QDialog):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.__hBoxLayout = QHBoxLayout(self)
self._hBoxLayout = QHBoxLayout(self)
self.windowMask = QWidget(self)
# dialog box in the center of mask, all widgets take it as parent
......@@ -24,7 +24,7 @@ class MaskDialogBase(QDialog):
c = 0 if isDarkTheme() else 255
self.windowMask.resize(self.size())
self.windowMask.setStyleSheet(f'background:rgba({c}, {c}, {c}, 0.6)')
self.__hBoxLayout.addWidget(self.widget)
self._hBoxLayout.addWidget(self.widget)
self.setShadowEffect()
self.window().installEventFilter(self)
......
from .navigation_widget import NavigationWidget, NavigationButton, NavigationSeparator
from .navigation_panel import NavigationPanel, NavigationItemPostion
from .navigation_interface import NavigationInterface
\ No newline at end of file
# coding:utf-8
from PyQt5.QtCore import Qt, pyqtSignal, QEvent
from PyQt5.QtGui import QResizeEvent
from PyQt5.QtWidgets import QWidget
from .navigation_panel import NavigationPanel, NavigationItemPostion, NavigationWidget, NavigationDisplayMode
from ...common.style_sheet import setStyleSheet
class NavigationInterface(QWidget):
""" Navigation interface """
def __init__(self, parent=None, showMenuButton=True):
"""
Parameters
----------
showMenuButton: bool
whether to show menu button
parent: widget
parent widget
"""
super().__init__(parent=parent)
self.panel = NavigationPanel(self)
self.panel.setMenuButtonVisible(showMenuButton)
self.panel.installEventFilter(self)
self.resize(48, self.height())
self.setMinimumWidth(48)
self.setAttribute(Qt.WA_StyledBackground)
setStyleSheet(self, 'navigation_interface')
def addItem(self, routeKey: str, iconPath: str, text: str, onClick, selectable=True, position=NavigationItemPostion.TOP):
""" add navigation item
Parameters
----------
routKey: str
the unique name of item
iconPath: str
the svg icon path of navigation item
text: str
the text of navigation item
onClick: callable
the slot connected to item clicked signal
position: NavigationItemPostion
where the button is added
selectable: bool
whether the item is selectable
"""
self.panel.addItem(routeKey, iconPath, text, onClick, selectable, position)
def addWidget(self, routeKey: str, widget: NavigationWidget, onClick, position=NavigationItemPostion.TOP):
""" add custom widget
Parameters
----------
routKey: str
the unique name of item
widget: NavigationWidget
the custom widget to be added
onClick: callable
the slot connected to item clicked signal
position: NavigationItemPostion
where the button is added
"""
self.panel.addWidget(routeKey, widget, onClick, position)
def addSeparator(self, position=NavigationItemPostion.TOP):
""" add separator
Parameters
----------
position: NavigationPostion
where to add the separator
"""
self.panel.addSeparator(position)
def setCurrentItem(self, name: str):
""" set current selected item
Parameters
----------
name: str
the unique name of item
"""
self.panel.setCurrentItem(name)
def eventFilter(self, obj, e: QEvent):
if obj is not self.panel or e.type() != QEvent.Resize:
return super().eventFilter(obj, e)
if self.panel.displayMode != NavigationDisplayMode.MENU:
event = QResizeEvent(e)
if event.oldSize().width() != event.size().width():
self.setFixedWidth(event.size().width())
return super().eventFilter(obj, e)
def resizeEvent(self, e: QResizeEvent):
if e.oldSize().height() != self.height():
self.panel.setFixedHeight(self.height())
# coding:utf-8
from enum import Enum
from typing import Dict
from PyQt5.QtCore import Qt, QPropertyAnimation, QRect, QSize, QEvent, QEasingCurve
from PyQt5.QtGui import QResizeEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QFrame, QApplication
from .navigation_widget import NavigationButton, MenuButton, NavigationWidget, NavigationSeparator
from ..widgets.scroll_area import ScrollArea
from ...common.style_sheet import setStyleSheet, getStyleSheet
class NavigationDisplayMode(Enum):
""" Navigation display mode """
MINIMAL = 0
COMPACT = 1
EXPAND = 2
MENU = 3
class NavigationItemPostion(Enum):
""" Navigation item position """
TOP = 0
SCROLL = 1
BOTTOM = 2
class NavigationPanel(QFrame):
""" Navigation panel """
def __init__(self, parent=None, isMinimalEnabled=False):
super().__init__(parent=parent)
self._parent = parent # type: QWidget
self.scrollArea = ScrollArea(self)
self.scrollWidget = QWidget()
self.menuButton = MenuButton(self)
self.vBoxLayout = NavigationItemLayout(self)
self.topLayout = NavigationItemLayout()
self.bottomLayout = NavigationItemLayout()
self.scrollLayout = NavigationItemLayout(self.scrollWidget)
self.items = {} # type: Dict[str, NavigationWidget]
self._bgOpacity = 0
self.expandAni = QPropertyAnimation(self, b'geometry', self)
self.isMinimalEnabled = isMinimalEnabled
if isMinimalEnabled:
self.displayMode = NavigationDisplayMode.MINIMAL
else:
self.displayMode = NavigationDisplayMode.COMPACT
self.__initWidget()
def __initWidget(self):
self.resize(48, self.height())
self.setAttribute(Qt.WA_StyledBackground)
self.window().installEventFilter(self)
self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scrollArea.setWidget(self.scrollWidget)
self.scrollArea.setWidgetResizable(True)
self.expandAni.setEasingCurve(QEasingCurve.OutQuad)
self.expandAni.setDuration(150)
self.menuButton.clicked.connect(self.toggle)
self.expandAni.finished.connect(self._onExpandAniFinished)
self.scrollWidget.setObjectName('scrollWidget')
setStyleSheet(self, 'navigation_interface')
self.__initLayout()
def __initLayout(self):
self.vBoxLayout.setContentsMargins(0, 5, 0, 5)
self.topLayout.setContentsMargins(4, 0, 4, 0)
self.bottomLayout.setContentsMargins(4, 0, 4, 0)
self.scrollLayout.setContentsMargins(4, 0, 4, 0)
self.vBoxLayout.setSpacing(4)
self.topLayout.setSpacing(4)
self.bottomLayout.setSpacing(4)
self.scrollLayout.setSpacing(4)
self.vBoxLayout.addLayout(self.topLayout, 0)
self.vBoxLayout.addWidget(self.scrollArea, 1, Qt.AlignTop)
self.vBoxLayout.addLayout(self.bottomLayout, 0)
self.vBoxLayout.setAlignment(Qt.AlignTop)
self.topLayout.setAlignment(Qt.AlignTop)
self.scrollLayout.setAlignment(Qt.AlignTop)
self.bottomLayout.setAlignment(Qt.AlignBottom)
self.topLayout.addWidget(self.menuButton, 0, Qt.AlignTop)
def addItem(self, routeKey: str, iconPath: str, text: str, onClick, selectable=True, position=NavigationItemPostion.TOP):
""" add navigation item
Parameters
----------
routeKey: str
the unique name of item
iconPath: str
the svg icon path of navigation item
text: str
the text of navigation item
onClick: callable
the slot connected to item clicked signal
position: NavigationItemPostion
where the button is added
selectable: bool
whether the item is selectable
"""
if text in self.items:
return
button = NavigationButton(iconPath, text, selectable, self)
self.addWidget(routeKey, button, onClick, position)
def addWidget(self, routeKey: str, widget: NavigationWidget, onClick, position=NavigationItemPostion.TOP):
""" add custom widget
Parameters
----------
routeKey: str
the unique name of item
widget: NavigationWidget
the custom widget to be added
onClick: callable
the slot connected to item clicked signal
position: NavigationItemPostion
where the button is added
"""
if routeKey in self.items:
return
widget.clicked.connect(self._onWidgetClicked)
widget.clicked.connect(onClick)
widget.setProperty('routeKey', routeKey)
self.items[routeKey] = widget
self._addWidgetToLayout(widget, position)
def addSeparator(self, position=NavigationItemPostion.TOP):
""" add separator
Parameters
----------
position: NavigationPostion
where to add the separator
"""
separator = NavigationSeparator(self)
self._addWidgetToLayout(separator, position)
def _addWidgetToLayout(self, widget: NavigationWidget, position: NavigationItemPostion):
""" add widget to layout """
if position == NavigationItemPostion.TOP:
widget.setParent(self)
self.topLayout.addWidget(widget, 0, Qt.AlignTop)
elif position == NavigationItemPostion.SCROLL:
widget.setParent(self.scrollWidget)
self.scrollLayout.addWidget(widget, 0, Qt.AlignTop)
else:
widget.setParent(self)
self.bottomLayout.addWidget(widget, 0, Qt.AlignBottom)
widget.show()
def setMenuButtonVisible(self, isVisible: bool):
""" set whether the menu button is visible """
self.menuButton.setVisible(isVisible)
def expand(self):
""" expand navigation panel """
self._setWidgetCompacted(False)
self.expandAni.setProperty('expand', True)
# determine the display mode according to the width of window
# https://learn.microsoft.com/en-us/windows/apps/design/controls/navigationview#default
if self.window().width() > 1007 and not self.isMinimalEnabled:
self.displayMode = NavigationDisplayMode.EXPAND
else:
self.setStyleSheet(getStyleSheet('navigation_interface').replace('--bgOpacity', '1'))
self.displayMode = NavigationDisplayMode.MENU
if not self._parent.isWindow():
pos = self.parent().pos()
self.setParent(self.window())
self.move(pos)
self.show()
self.expandAni.setStartValue(
QRect(self.pos(), QSize(48, self.height())))
self.expandAni.setEndValue(
QRect(self.pos(), QSize(322, self.height())))
self.expandAni.start()
def collapse(self):
""" collapse navigation panel """
if self.expandAni.state() == QPropertyAnimation.Running:
return
self.expandAni.setStartValue(
QRect(self.pos(), QSize(self.width(), self.height())))
self.expandAni.setEndValue(
QRect(self.pos(), QSize(48, self.height())))
self.expandAni.setProperty('expand', False)
self.expandAni.start()
def toggle(self):
""" toggle navigation panel """
if self.displayMode in [NavigationDisplayMode.COMPACT, NavigationDisplayMode.MINIMAL]:
self.expand()
else:
self.collapse()
def setCurrentItem(self, routeKey: str):
""" set current selected item
Parameters
----------
routeKey: str
the unique name of item
"""
if routeKey not in self.items:
return
for k, item in self.items.items():
item.setSelected(k == routeKey)
def _onWidgetClicked(self):
widget = self.sender() # type: NavigationWidget
if not widget.isSelectable:
return
self.setCurrentItem(widget.property('routeKey'))
if widget is not self.menuButton and self.displayMode == NavigationDisplayMode.MENU:
self.collapse()
def resizeEvent(self, e: QResizeEvent):
if e.oldSize().width() == self.width():
return
th = self.topLayout.minimumSize().height()
bh = self.bottomLayout.minimumSize().height()
self.scrollArea.setFixedHeight(self.height()-th-bh-20)
def eventFilter(self, obj, e: QEvent):
if obj is not self.window():
return super().eventFilter(obj, e)
if e.type() == QEvent.MouseButtonRelease:
if not self.geometry().contains(e.pos()) and self.displayMode == NavigationDisplayMode.MENU:
self.collapse()
elif e.type() == QEvent.Resize:
w = QResizeEvent(e).size().width()
if w < 1008 and self.displayMode == NavigationDisplayMode.EXPAND:
self.collapse()
return super().eventFilter(obj, e)
def _onExpandAniFinished(self):
if not self.expandAni.property('expand'):
if self.isMinimalEnabled:
self.displayMode = NavigationDisplayMode.MINIMAL
else:
self.displayMode = NavigationDisplayMode.COMPACT
s = getStyleSheet('navigation_interface')
if self.displayMode == NavigationDisplayMode.MINIMAL:
self.hide()
self.setStyleSheet(s.replace('--bgOpacity', '0'))
elif self.displayMode == NavigationDisplayMode.COMPACT:
self.setStyleSheet(s.replace('--bgOpacity', '0'))
for item in self.items.values():
item.setCompacted(True)
if not self._parent.isWindow():
self.setParent(self._parent)
self.move(0, 0)
self.show()
def _setWidgetCompacted(self, isCompacted: bool):
""" set whether the navigation widget is compacted """
for item in self.findChildren(NavigationWidget):
item.setCompacted(isCompacted)
class NavigationItemLayout(QVBoxLayout):
""" Navigation layout """
def setGeometry(self, rect: QRect):
super().setGeometry(rect)
for i in range(self.count()):
item = self.itemAt(i)
if isinstance(item.widget(), NavigationSeparator):
geo = item.geometry()
item.widget().setGeometry(0, geo.y(), geo.width(), geo.height())
# coding:utf-8
from PyQt5.QtCore import Qt, pyqtSignal, QRect, QRectF
from PyQt5.QtGui import QColor, QPainter, QPen
from PyQt5.QtWidgets import QWidget
from ...common.config import isDarkTheme
from ...common.icon import drawIcon
from ...common.icon import FluentIconFactory as FIF
class NavigationWidget(QWidget):
""" Navigation widget """
clicked = pyqtSignal()
def __init__(self, isSelectable: bool, parent=None):
super().__init__(parent)
self.isCompacted = True
self.isSelected = False
self.isPressed = False
self.isEnter = False
self.isSelectable = isSelectable
self.setFixedSize(40, 36)
def enterEvent(self, e):
self.isEnter = True
self.update()
def leaveEvent(self, e):
self.isEnter = False
self.isPressed = False
self.update()
def mousePressEvent(self, e):
self.isPressed = True
self.update()
def mouseReleaseEvent(self, e):
self.isPressed = False
self.update()
self.clicked.emit()
def setCompacted(self, isCompacted: bool):
""" set whether the widget is compacted """
if isCompacted == self.isCompacted:
return
self.isCompacted = isCompacted
if isCompacted:
self.setFixedSize(40, 36)
else:
self.setFixedSize(312, 36)
self.update()
def setSelected(self, isSelected: bool):
""" set whether the button is selected
Parameters
----------
isSelected: bool
whether the button is selected
"""
if not self.isSelectable or self.isSelected == isSelected:
return
self.isSelected = isSelected
self.update()
class NavigationButton(NavigationWidget):
""" Navigation button """
def __init__(self, iconPath: str, text: str, isSelectable: bool, parent=None):
"""
Parameters
----------
iconPath: str
the path of button icon
text: str
the text of button
"""
super().__init__(isSelectable=isSelectable, parent=parent)
self.iconPath = iconPath
self._text = text
self.setStyleSheet(
"NavigationButton{font: 14px 'Segoe UI', 'Microsoft YaHei'}")
def text(self):
return self._text
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
painter.setPen(Qt.NoPen)
if self.isPressed:
painter.setOpacity(0.7)
# draw background
c = 255 if isDarkTheme() else 0
if self.isSelected:
painter.setBrush(QColor(c, c, c, 6 if self.isEnter else 10))
painter.drawRoundedRect(self.rect(), 5, 5)
# draw indicator
painter.setBrush(QColor(0, 153, 188))
painter.drawRoundedRect(0, 10, 3, 16, 1.5, 1.5)
elif self.isEnter:
painter.setBrush(QColor(c, c, c, 10))
painter.drawRoundedRect(self.rect(), 5, 5)
drawIcon(self.iconPath, painter, QRectF(11.5, 10, 16, 16))
# draw text
if not self.isCompacted:
painter.setFont(self.font())
painter.setPen(QColor(c, c, c))
painter.drawText(QRect(44, 0, self.width()-57,
self.height()), Qt.AlignVCenter, self.text())
class MenuButton(NavigationButton):
""" Menu button """
def __init__(self, parent=None):
super().__init__(FIF.path(FIF.MENU), '', parent)
def setCompacted(self, isCompacted: bool):
self.setFixedSize(40, 36)
class NavigationSeparator(NavigationWidget):
""" Navigation Separator """
def __init__(self, parent=None):
super().__init__(False, parent=parent)
self.setCompacted(True)
def setCompacted(self, isCompacted: bool):
if isCompacted:
self.setFixedSize(48, 3)
else:
self.setFixedSize(322, 3)
self.update()
def paintEvent(self, e):
painter = QPainter(self)
c = 255 if isDarkTheme() else 0
pen = QPen(QColor(c, c, c, 15))
pen.setCosmetic(True)
painter.setPen(pen)
painter.drawLine(0, 1, self.width(), 1)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册