提交 024d5ecc 编写于 作者: 之一Yo's avatar 之一Yo

完成圆角菜单添加子菜单和插入动作的功能

上级 f8669fd7
...@@ -16,7 +16,7 @@ class Demo(QWidget): ...@@ -16,7 +16,7 @@ class Demo(QWidget):
self.layout().addWidget(self.label) self.layout().addWidget(self.label)
self.resize(400, 400) self.resize(400, 400)
self.setStyleSheet('Demo{background: white} QLabel{font: 20px "Segoe UI"}') self.setStyleSheet('Demo{background: white} QLabel{font-size: 20px}')
def contextMenuEvent(self, e): def contextMenuEvent(self, e):
menu = RoundMenu(parent=self) menu = RoundMenu(parent=self)
...@@ -25,17 +25,34 @@ class Demo(QWidget): ...@@ -25,17 +25,34 @@ class Demo(QWidget):
menu.addAction(QAction(FIF.icon(FIF.COPY), 'Copy')) menu.addAction(QAction(FIF.icon(FIF.COPY), 'Copy'))
menu.addAction(QAction(FIF.icon(FIF.CUT), 'Cut')) menu.addAction(QAction(FIF.icon(FIF.CUT), 'Cut'))
submenu = RoundMenu("Add To", self) # add sub menu
submenu = RoundMenu(self, "Add to")
submenu.setIcon(FIF.icon(FIF.ADD)) submenu.setIcon(FIF.icon(FIF.ADD))
submenu.addActions([
QAction(FIF.icon(FIF.FOLDER), 'Folder'),
QAction(FIF.icon(FIF.MUSIC_FOLDER), 'Music folder'),
])
menu.addMenu(submenu) menu.addMenu(submenu)
# add actions # add actions
menu.addAction(QAction(FIF.icon(FIF.PASTE), 'Paste')) menu.addActions([
menu.addAction(QAction(FIF.icon(FIF.CANCEL), 'Undo')) QAction(FIF.icon(FIF.PASTE), 'Paste'),
QAction(FIF.icon(FIF.CANCEL), 'Undo')
])
# add separator # add separator
menu.addSeparator() menu.addSeparator()
menu.addAction(QAction(f'Select all')) menu.addAction(QAction(f'Select all'))
# insert actions
menu.insertAction(
menu.menuActions()[-1], QAction(FIF.icon(FIF.SETTING), 'Settings'))
menu.insertActions(
menu.menuActions()[-1],
[QAction(FIF.icon(FIF.HELP), 'Help'), QAction(FIF.icon(FIF.FEEDBACK), 'Feedback')]
)
# show menu
menu.exec(e.globalPos()) menu.exec(e.globalPos())
......
...@@ -70,6 +70,14 @@ MenuActionListWidget::item { ...@@ -70,6 +70,14 @@ MenuActionListWidget::item {
color: white; color: white;
} }
MenuActionListWidget::disbled {
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
border: none;
color: white;
}
MenuActionListWidget::item:hover { MenuActionListWidget::item:hover {
background-color: rgba(255, 255, 255, 0.08); background-color: rgba(255, 255, 255, 0.08);
} }
......
...@@ -68,6 +68,14 @@ MenuActionListWidget::item { ...@@ -68,6 +68,14 @@ MenuActionListWidget::item {
border: none; border: none;
} }
MenuActionListWidget::item:disbaled {
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
border: none;
color: black;
}
MenuActionListWidget::item:hover { MenuActionListWidget::item:hover {
background-color: rgba(0, 0, 0, 9); background-color: rgba(0, 0, 0, 9);
} }
......
此差异已折叠。
...@@ -6,8 +6,8 @@ from PyQt5.QtSvg import QSvgRenderer ...@@ -6,8 +6,8 @@ from PyQt5.QtSvg import QSvgRenderer
from .config import qconfig from .config import qconfig
class PixmapIconEngine(QIconEngine): class IconEngine(QIconEngine):
""" Pixmap icon engine """ """ Icon engine """
def __init__(self, iconPath): def __init__(self, iconPath):
self.iconPath = iconPath self.iconPath = iconPath
...@@ -32,7 +32,17 @@ class Icon(QIcon): ...@@ -32,7 +32,17 @@ class Icon(QIcon):
def __init__(self, iconPath): def __init__(self, iconPath):
self.iconPath = iconPath self.iconPath = iconPath
super().__init__(PixmapIconEngine(iconPath)) super().__init__(IconEngine(iconPath))
class MenuIconEngine(QIconEngine):
def __init__(self, icon):
super().__init__()
self.icon = icon
def paint(self, painter, rect, mode, state):
self.icon.paint(painter, rect, Qt.AlignHCenter, QIcon.Normal, state)
def getIconColor(): def getIconColor():
......
# coding:utf-8 # coding:utf-8
from qframelesswindow import WindowEffect from qframelesswindow import WindowEffect
from PyQt5.QtCore import QEasingCurve, QEvent, QPropertyAnimation, QRect, Qt, QSize, QRectF from PyQt5.QtCore import (QEasingCurve, QEvent, QPropertyAnimation, QRect,
Qt, QSize, QRectF, pyqtSignal, QPoint)
from PyQt5.QtGui import QIcon, QColor, QPainter, QPen, QPixmap from PyQt5.QtGui import QIcon, QColor, QPainter, QPen, QPixmap
from PyQt5.QtWidgets import (QAction, QApplication, QMenu, QProxyStyle, QStyle, from PyQt5.QtWidgets import (QAction, QApplication, QMenu, QProxyStyle, QStyle,
QGraphicsDropShadowEffect, QListWidget, QWidget, QHBoxLayout, QGraphicsDropShadowEffect, QListWidget, QWidget, QHBoxLayout,
...@@ -8,6 +9,7 @@ from PyQt5.QtWidgets import (QAction, QApplication, QMenu, QProxyStyle, QStyle, ...@@ -8,6 +9,7 @@ from PyQt5.QtWidgets import (QAction, QApplication, QMenu, QProxyStyle, QStyle,
from ...common.smooth_scroll import SmoothScroll from ...common.smooth_scroll import SmoothScroll
from ...common.icon import FluentIconFactory as FIF from ...common.icon import FluentIconFactory as FIF
from ...common.icon import MenuIconEngine
from ...common.style_sheet import setStyleSheet from ...common.style_sheet import setStyleSheet
from ...common.config import qconfig from ...common.config import qconfig
...@@ -151,18 +153,29 @@ class MenuSeparator(QWidget): ...@@ -151,18 +153,29 @@ class MenuSeparator(QWidget):
class SubMenuItemWidget(QWidget): class SubMenuItemWidget(QWidget):
""" Sub menu item """ """ Sub menu item """
def __init__(self, menu: QMenu, parent=None): showMenuSig = pyqtSignal()
hideMenuSig = pyqtSignal()
def __init__(self, menu, item, parent=None):
""" """
Parameters Parameters
---------- ----------
menu: QMenu menu: QMenu | RoundMenu
sub menu sub menu
item: QListWidgetItem
menu item
parent: QWidget parent: QWidget
parent widget parent widget
""" """
super().__init__(parent) super().__init__(parent)
self.menu = menu self.menu = menu
self.item = item
def enterEvent(self, e):
super().enterEvent(e)
self.showMenuSig.emit()
def paintEvent(self, e): def paintEvent(self, e):
painter = QPainter(self) painter = QPainter(self)
...@@ -190,10 +203,28 @@ class MenuActionListWidget(QListWidget): ...@@ -190,10 +203,28 @@ class MenuActionListWidget(QListWidget):
self.setTextElideMode(Qt.ElideNone) self.setTextElideMode(Qt.ElideNone)
self.setIconSize(QSize(14, 14)) self.setIconSize(QSize(14, 14))
self.smoothScroll = SmoothScroll(self) self.smoothScroll = SmoothScroll(self)
self.setStyleSheet(
'MenuActionListWidget{font: 14px "Segoe UI", "Microsoft YaHei"}')
def wheelEvent(self, e): def wheelEvent(self, e):
self.smoothScroll.wheelEvent(e) self.smoothScroll.wheelEvent(e)
def insertItem(self, row, item):
""" inserts menu item at the position in the list given by row """
super().insertItem(row, item)
self.adjustSize()
def addItem(self, item):
""" add menu item at the end """
super().addItem(item)
self.adjustSize()
def takeItem(self, row):
""" delete item from list """
item = super().takeItem(row)
self.adjustSize()
return item
def adjustSize(self): def adjustSize(self):
size = QSize() size = QSize()
for i in range(self.count()): for i in range(self.count()):
...@@ -216,11 +247,14 @@ class MenuActionListWidget(QListWidget): ...@@ -216,11 +247,14 @@ class MenuActionListWidget(QListWidget):
class RoundMenu(QWidget): class RoundMenu(QWidget):
""" Round corner menu """ """ Round corner menu """
def __init__(self, title="", parent=None): def __init__(self, parent, title=""):
super().__init__(parent=parent) super().__init__(parent=parent)
self._title = title self._title = title
self._icon = QIcon() self._icon = QIcon()
self._actions = [] self._actions = []
self.isSubMenu = False
self.parentMenu = None
self.menuItem = None
self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout = QHBoxLayout(self)
self.view = MenuActionListWidget(self) self.view = MenuActionListWidget(self)
self.__initWidgets() self.__initWidgets()
...@@ -229,6 +263,7 @@ class RoundMenu(QWidget): ...@@ -229,6 +263,7 @@ class RoundMenu(QWidget):
self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint | self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint |
Qt.NoDropShadowWindowHint) Qt.NoDropShadowWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground) self.setAttribute(Qt.WA_TranslucentBackground)
self.setMouseTracking(True)
self.setShadowEffect() self.setShadowEffect()
self.hBoxLayout.addWidget(self.view) self.hBoxLayout.addWidget(self.view)
...@@ -242,9 +277,13 @@ class RoundMenu(QWidget): ...@@ -242,9 +277,13 @@ class RoundMenu(QWidget):
self.shadowEffect.setBlurRadius(blurRadius) self.shadowEffect.setBlurRadius(blurRadius)
self.shadowEffect.setOffset(*offset) self.shadowEffect.setOffset(*offset)
self.shadowEffect.setColor(color) self.shadowEffect.setColor(color)
self.view.setGraphicsEffect(None)
self.view.setGraphicsEffect(self.shadowEffect) self.view.setGraphicsEffect(self.shadowEffect)
def _setParentMenu(self, parent, item):
self.parentMenu = parent
self.menuItem = item
self.isSubMenu = True if parent else False
def icon(self): def icon(self):
return self._icon return self._icon
...@@ -268,28 +307,53 @@ class RoundMenu(QWidget): ...@@ -268,28 +307,53 @@ class RoundMenu(QWidget):
action: QAction action: QAction
menu action menu action
""" """
self._actions.append(action) item = self._createMenuActionItem(action)
self.view.addItem(item)
def _createMenuActionItem(self, action, before=None):
""" create menu action item """
if not before:
self._actions.append(action)
elif before in self._actions:
index = self._actions.index(before)
self._actions.insert(index, action)
else:
raise ValueError('`before` is not in the action list')
hasIcon = any(not i.icon().isNull() for i in self._actions) hasIcon = any(not i.icon().isNull() for i in self._actions)
# icon empty icon # icon empty icon
icon = action.icon() icon = QIcon(MenuIconEngine(action.icon()))
if hasIcon and icon.isNull(): if hasIcon and icon.isNull():
pixmap = QPixmap(self.view.iconSize()) pixmap = QPixmap(self.view.iconSize())
pixmap.fill(Qt.transparent) pixmap.fill(Qt.transparent)
icon = QIcon(pixmap) icon = QIcon(pixmap)
item = QListWidgetItem(icon, action.text(), self.view) item = QListWidgetItem(icon, action.text())
if not hasIcon: if not hasIcon:
w = 28 + self.view.fontMetrics().width(action.text()) w = 28 + self.view.fontMetrics().width(action.text())
else: else:
# add a blank character to increase space between icon and text # add a blank character to increase space between icon and text
item.setText(" " + item.text()) item.setText(" " + item.text())
w = 60 + self.view.fontMetrics().width(action.text()) w = 60 + self.view.fontMetrics().width(item.text())
item.setSizeHint(QSize(w, 33)) item.setSizeHint(QSize(w, 33))
self.view.addItem(item) action.setProperty('item', item)
self.view.adjustSize() item.setData(Qt.UserRole, action)
self.adjustSize() return item
def insertAction(self, before, action):
""" inserts action to menu, before the action before """
if before not in self._actions:
return
beforeItem = before.property('item')
if not beforeItem:
return
index = self.view.row(beforeItem)
item = self._createMenuActionItem(action, before)
self.view.insertItem(index, item)
def addActions(self, actions): def addActions(self, actions):
""" add actions to menu """ add actions to menu
...@@ -302,6 +366,11 @@ class RoundMenu(QWidget): ...@@ -302,6 +366,11 @@ class RoundMenu(QWidget):
for action in actions: for action in actions:
self.addAction(action) self.addAction(action)
def insertActions(self, before, actions):
""" inserts the actions actions to menu, before the action before """
for action in actions:
self.insertAction(before, action)
def removeAction(self, action): def removeAction(self, action):
""" remove action from menu """ """ remove action from menu """
if action not in self._actions: if action not in self._actions:
...@@ -309,8 +378,9 @@ class RoundMenu(QWidget): ...@@ -309,8 +378,9 @@ class RoundMenu(QWidget):
index = self._actions.index(action) index = self._actions.index(action)
self._actions.remove(action) self._actions.remove(action)
action.setProperty('item', None)
item = self.view.takeItem(index) item = self.view.takeItem(index)
self.view.adjustSize() item.setData(Qt.UserRole, None)
# delete widget # delete widget
widget = self.view.itemWidget(item) widget = self.view.itemWidget(item)
...@@ -325,9 +395,19 @@ class RoundMenu(QWidget): ...@@ -325,9 +395,19 @@ class RoundMenu(QWidget):
index = self._actions.index(action) index = self._actions.index(action)
self.view.setCurrentRow(index) self.view.setCurrentRow(index)
def addMenu(self, menu: QMenu): def addMenu(self, menu):
""" add sub menu """ """ add sub menu
hasIcon = any(not self.view.item(i).icon().isNull() for i in range(self.view.count()))
Parameters
----------
menu: RoundMenu
sub round menu
"""
if not isinstance(menu, RoundMenu):
raise ValueError('`menu` should be an instance of `RoundMenu`.')
hasIcon = any(not self.view.item(i).icon().isNull()
for i in range(self.view.count()))
# icon empty icon # icon empty icon
icon = menu.icon() icon = menu.icon()
...@@ -338,16 +418,41 @@ class RoundMenu(QWidget): ...@@ -338,16 +418,41 @@ class RoundMenu(QWidget):
item = QListWidgetItem(icon, menu.title(), self.view) item = QListWidgetItem(icon, menu.title(), self.view)
if not hasIcon: if not hasIcon:
w = 28 + self.view.fontMetrics().width(menu.title()) w = 48 + self.view.fontMetrics().width(menu.title())
else: else:
# add a blank character to increase space between icon and text # add a blank character to increase space between icon and text
item.setText(" " + item.text()) item.setText(" " + item.text())
w = 60 + self.view.fontMetrics().width(menu.title()) w = 60 + self.view.fontMetrics().width(item.text())
# add submenu item
menu._setParentMenu(self, item)
item.setSizeHint(QSize(w, 33)) item.setSizeHint(QSize(w, 33))
item.setData(Qt.UserRole, menu)
w = SubMenuItemWidget(menu, item, self)
w.showMenuSig.connect(self._showSubMenu)
w.hideMenuSig.connect(self._hideSubMenu)
self.view.addItem(item) self.view.addItem(item)
self.view.setItemWidget(item, SubMenuItemWidget(menu, self)) self.view.setItemWidget(item, w)
self.view.adjustSize()
def _showSubMenu(self):
""" show sub menu """
w = self.sender()
# update selected row
self.view.clearSelection()
QApplication.processEvents()
self.view.setCurrentItem(w.item)
# show sub menu
pos = w.mapToGlobal(QPoint())
pos += QPoint(w.width()-6, 0)
w.menu.exec(pos)
def _hideSubMenu(self):
""" hide sub menu """
w = self.sender()
w.item.setSelected(False)
w.menu.hide()
def addSeparator(self): def addSeparator(self):
""" add seperator to menu """ """ add seperator to menu """
...@@ -366,23 +471,76 @@ class RoundMenu(QWidget): ...@@ -366,23 +471,76 @@ class RoundMenu(QWidget):
self.view.setItemWidget(item, separator) self.view.setItemWidget(item, separator)
def __onItemClicked(self, item): def __onItemClicked(self, item):
index = self.view.row(item) action = item.data(Qt.UserRole)
if not 0 <= index < len(self._actions): if action not in self._actions:
return
action.trigger()
self._hideMenu()
if not self.isSubMenu:
return return
self._actions[index].trigger() # close parent menu
self.deleteLater() menu = self
while menu.parentMenu:
menu = menu.parentMenu
menu.deleteLater()
def _hideMenu(self):
self.view.clearSelection()
if self.isSubMenu:
self.hide()
else:
self.deleteLater()
def menuActions(self): def menuActions(self):
return self._actions return self._actions
def mousePressEvent(self, e): def mousePressEvent(self, e):
if self.childAt(e.pos()) is not self.view: if self.childAt(e.pos()) is not self.view:
self.deleteLater() self._hideMenu()
def mouseMoveEvent(self, e):
if not self.isSubMenu:
return
# hide submenu when mouse moves out of submenu item
pos = e.globalPos()
view = self.parentMenu.view
w = view.itemWidget(self.menuItem)
rect = w.geometry().translated(w.mapToGlobal(QPoint())-w.pos())
if self.parentMenu.geometry().contains(pos) and not rect.contains(pos):
view.clearSelection()
self._hideMenu()
# update style
index = view.row(self.menuItem)
if index > 0:
view.item(index-1).setFlags(Qt.ItemIsEnabled)
if index < view.count()-1:
view.item(index+1).setFlags(Qt.ItemIsEnabled)
def exec(self, pos): def exec(self, pos):
desktop = QApplication.desktop().availableGeometry() desktop = QApplication.desktop().availableGeometry()
pos.setX(min(pos.x() - 40, desktop.width() - self.width())) m = self.layout().contentsMargins()
pos.setY(min(pos.y() - 12, desktop.height() - self.height())) w = self.view.width() + m.left() + m.right() + 20
h = self.view.height() + m.top() + m.bottom() + 20
pos.setX(min(pos.x() - 30, desktop.width() - w))
pos.setY(min(pos.y() - 12, desktop.height() - h))
self.move(pos) self.move(pos)
self.show() self.show()
if not self.isSubMenu:
return
self.menuItem.setSelected(True)
# temporarily disable item to change style
view = self.parentMenu.view
index = view.row(self.menuItem)
if index > 0:
view.item(index-1).setFlags(Qt.NoItemFlags)
if index < view.count()-1:
view.item(index+1).setFlags(Qt.NoItemFlags)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册