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

圆角菜单添加插入子菜单的方法

上级 de40612a
...@@ -10,7 +10,7 @@ online at https://pyqt-fluent-widgets.readthedocs.io. ...@@ -10,7 +10,7 @@ online at https://pyqt-fluent-widgets.readthedocs.io.
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
__version__ = "0.2.2" __version__ = "0.2.3"
from .components import * from .components import *
from .common import * from .common import *
......
...@@ -35,24 +35,6 @@ QMenu::item:selected { ...@@ -35,24 +35,6 @@ QMenu::item:selected {
} }
QMenu#lineEditMenu {
width: 1px;
height: 1px;
border: 1px solid rgb(200, 200, 200);
}
QMenu#lineEditMenu::icon {
position: absolute;
left: 13px;
}
QMenu#lineEditMenu::item {
padding-left: 21px;
}
QMenu#lineEditMenu[selectAll=true]::item {
width: 98px;
}
MenuActionListWidget { MenuActionListWidget {
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
......
...@@ -33,25 +33,6 @@ QMenu::item:selected { ...@@ -33,25 +33,6 @@ QMenu::item:selected {
} }
QMenu#lineEditMenu {
width: 1px;
height: 1px;
border: 1px solid rgb(200, 200, 200);
}
QMenu#lineEditMenu::icon {
position: absolute;
left: 13px;
}
QMenu#lineEditMenu::item {
padding-left: 21px;
}
QMenu#lineEditMenu[selectAll=true]::item {
width: 98px;
}
MenuActionListWidget { MenuActionListWidget {
border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1);
......
此差异已折叠。
# coding:utf-8 # coding:utf-8
from qframelesswindow import WindowEffect from qframelesswindow import WindowEffect
from PyQt5.QtCore import (QEasingCurve, QEvent, QPropertyAnimation, QRect, from PyQt5.QtCore import (QEasingCurve, QEvent, QPropertyAnimation, QRect,
Qt, QSize, QRectF, pyqtSignal, QPoint) Qt, QSize, QRectF, pyqtSignal, QPoint, QTimer)
from PyQt5.QtGui import QIcon, QColor, QPainter, QPen, QPixmap, QRegion, QCursor from PyQt5.QtGui import QIcon, QColor, QPainter, QPen, QPixmap, QRegion, QCursor
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,
QListWidgetItem, QStyleOptionViewItem) QListWidgetItem)
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
...@@ -52,87 +52,6 @@ class DWMMenu(QMenu): ...@@ -52,87 +52,6 @@ class DWMMenu(QMenu):
return QMenu.event(self, e) return QMenu.event(self, e)
class LineEditMenu(DWMMenu):
""" Line edit menu """
def __init__(self, parent):
super().__init__("", parent)
self.setObjectName("lineEditMenu")
self.animation = QPropertyAnimation(self, b"geometry")
self.animation.setDuration(300)
self.animation.setEasingCurve(QEasingCurve.OutQuad)
self.setProperty("selectAll", bool(self.parent().text()))
def createActions(self):
self.cutAct = QAction(
FIF.icon(FIF.CUT),
self.tr("Cut"),
self,
shortcut="Ctrl+X",
triggered=self.parent().cut,
)
self.copyAct = QAction(
FIF.icon(FIF.COPY),
self.tr("Copy"),
self,
shortcut="Ctrl+C",
triggered=self.parent().copy,
)
self.pasteAct = QAction(
FIF.icon(FIF.PASTE),
self.tr("Paste"),
self,
shortcut="Ctrl+V",
triggered=self.parent().paste,
)
self.cancelAct = QAction(
FIF.icon(FIF.CANCEL),
self.tr("Cancel"),
self,
shortcut="Ctrl+Z",
triggered=self.parent().undo,
)
self.selectAllAct = QAction(
self.tr("Select all"),
self,
shortcut="Ctrl+A",
triggered=self.parent().selectAll
)
self.action_list = [self.cutAct, self.copyAct,
self.pasteAct, self.cancelAct, self.selectAllAct]
def exec_(self, pos):
self.clear()
self.createActions()
if QApplication.clipboard().mimeData().hasText():
if self.parent().text():
if self.parent().selectedText():
self.addActions(self.action_list)
else:
self.addActions(self.action_list[2:])
else:
self.addAction(self.pasteAct)
else:
if self.parent().text():
if self.parent().selectedText():
self.addActions(
self.action_list[:2] + self.action_list[3:])
else:
self.addActions(self.action_list[3:])
else:
return
w = 92+max(self.fontMetrics().width(i.text()) for i in self.actions())
h = len(self.actions()) * 32 + 8
self.animation.setStartValue(QRect(pos.x(), pos.y(), 1, 1))
self.animation.setEndValue(QRect(pos.x(), pos.y(), w, h))
self.setStyle(CustomMenuStyle())
self.animation.start()
super().exec_(pos)
class MenuSeparator(QWidget): class MenuSeparator(QWidget):
""" Menu separator """ """ Menu separator """
...@@ -260,9 +179,13 @@ class RoundMenu(QWidget): ...@@ -260,9 +179,13 @@ class RoundMenu(QWidget):
self._title = title self._title = title
self._icon = QIcon() self._icon = QIcon()
self._actions = [] self._actions = []
self._subMenus = []
self.isSubMenu = False self.isSubMenu = False
self.parentMenu = None self.parentMenu = None
self.menuItem = None self.menuItem = None
self.lastHoverItem = None
self.lastHoverSubMenuItem = None
self.isHideBySystem = True
self.itemHeight = 28 self.itemHeight = 28
self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout = QHBoxLayout(self)
self.view = MenuActionListWidget(self) self.view = MenuActionListWidget(self)
...@@ -336,11 +259,11 @@ class RoundMenu(QWidget): ...@@ -336,11 +259,11 @@ class RoundMenu(QWidget):
action: QAction action: QAction
menu action menu action
""" """
item = self._createMenuActionItem(action) item = self._createActionItem(action)
self.view.addItem(item) self.view.addItem(item)
self.adjustSize() self.adjustSize()
def _createMenuActionItem(self, action, before=None): def _createActionItem(self, action, before=None):
""" create menu action item """ """ create menu action item """
if not before: if not before:
self._actions.append(action) self._actions.append(action)
...@@ -350,19 +273,8 @@ class RoundMenu(QWidget): ...@@ -350,19 +273,8 @@ class RoundMenu(QWidget):
else: else:
raise ValueError('`before` is not in the action list') raise ValueError('`before` is not in the action list')
hasIcon = any(not i.icon().isNull() for i in self._actions) item = QListWidgetItem(self._createItemIcon(action), action.text())
if not self._hasItemIcon():
# icon empty icon
icon = QIcon(MenuIconEngine(action.icon()))
if hasIcon and action.icon().isNull():
pixmap = QPixmap(self.view.iconSize())
pixmap.fill(Qt.transparent)
icon = QIcon(pixmap)
elif not hasIcon:
icon = QIcon()
item = QListWidgetItem(icon, action.text())
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
...@@ -370,10 +282,28 @@ class RoundMenu(QWidget): ...@@ -370,10 +282,28 @@ class RoundMenu(QWidget):
w = 60 + self.view.fontMetrics().width(item.text()) w = 60 + self.view.fontMetrics().width(item.text())
item.setSizeHint(QSize(w, self.itemHeight)) item.setSizeHint(QSize(w, self.itemHeight))
action.setProperty('item', item)
item.setData(Qt.UserRole, action) item.setData(Qt.UserRole, action)
action.setProperty('item', item)
action.changed.connect(self._onActionChanged)
return item return item
def _hasItemIcon(self):
return any(not i.icon().isNull() for i in self._actions+self._subMenus)
def _createItemIcon(self, w):
""" create the icon of menu item """
hasIcon = self._hasItemIcon()
icon = QIcon(MenuIconEngine(w.icon()))
if hasIcon and w.icon().isNull():
pixmap = QPixmap(self.view.iconSize())
pixmap.fill(Qt.transparent)
icon = QIcon(pixmap)
elif not hasIcon:
icon = QIcon()
return icon
def insertAction(self, before, action): def insertAction(self, before, action):
""" inserts action to menu, before the action before """ """ inserts action to menu, before the action before """
if before not in self._actions: if before not in self._actions:
...@@ -384,7 +314,7 @@ class RoundMenu(QWidget): ...@@ -384,7 +314,7 @@ class RoundMenu(QWidget):
return return
index = self.view.row(beforeItem) index = self.view.row(beforeItem)
item = self._createMenuActionItem(action, before) item = self._createActionItem(action, before)
self.view.insertItem(index, item) self.view.insertItem(index, item)
self.adjustSize() self.adjustSize()
...@@ -439,18 +369,29 @@ class RoundMenu(QWidget): ...@@ -439,18 +369,29 @@ class RoundMenu(QWidget):
if not isinstance(menu, RoundMenu): if not isinstance(menu, RoundMenu):
raise ValueError('`menu` should be an instance of `RoundMenu`.') raise ValueError('`menu` should be an instance of `RoundMenu`.')
hasIcon = any(not self.view.item(i).icon().isNull() item, w = self._createSubMenuItem(menu)
for i in range(self.view.count())) self.view.addItem(item)
self.view.setItemWidget(item, w)
self.adjustSize()
def insertMenu(self, before, menu):
""" insert menu before action `before` """
if not isinstance(menu, RoundMenu):
raise ValueError('`menu` should be an instance of `RoundMenu`.')
if before not in self._actions:
raise ValueError('`before` should be in menu action list')
# icon empty icon item, w = self._createSubMenuItem(menu)
icon = menu.icon() self.view.insertItem(self.view.row(before.property('item')), item)
if hasIcon and icon.isNull(): self.view.setItemWidget(item, w)
pixmap = QPixmap(self.view.iconSize()) self.adjustSize()
pixmap.fill(Qt.transparent)
icon = QIcon(pixmap) def _createSubMenuItem(self, menu):
self._subMenus.append(menu)
item = QListWidgetItem(icon, menu.title(), self.view) item = QListWidgetItem(self._createItemIcon(menu), menu.title())
if not hasIcon: if not self._hasItemIcon():
w = 48 + 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
...@@ -464,13 +405,21 @@ class RoundMenu(QWidget): ...@@ -464,13 +405,21 @@ class RoundMenu(QWidget):
w = SubMenuItemWidget(menu, item, self) w = SubMenuItemWidget(menu, item, self)
w.showMenuSig.connect(self._showSubMenu) w.showMenuSig.connect(self._showSubMenu)
w.resize(item.sizeHint()) w.resize(item.sizeHint())
self.view.addItem(item)
self.view.setItemWidget(item, w) return item, w
self.adjustSize()
def _showSubMenu(self, item): def _showSubMenu(self, item):
""" show sub menu """ """ show sub menu """
w = self.view.itemWidget(item) self.lastHoverItem = item
self.lastHoverSubMenuItem = item
# delay 400 ms to anti-shake
QTimer.singleShot(400, self._onShowMenuTimeOut)
def _onShowMenuTimeOut(self):
if self.lastHoverSubMenuItem is None or not self.lastHoverItem is self.lastHoverSubMenuItem:
return
w = self.view.itemWidget(self.lastHoverSubMenuItem)
pos = w.mapToGlobal(QPoint(w.width()+5, -5)) pos = w.mapToGlobal(QPoint(w.width()+5, -5))
w.menu.exec(pos) w.menu.exec(pos)
...@@ -496,39 +445,51 @@ class RoundMenu(QWidget): ...@@ -496,39 +445,51 @@ class RoundMenu(QWidget):
if action not in self._actions: if action not in self._actions:
return return
self._hideMenu() self._hideMenu(False)
if not self.isSubMenu: if not self.isSubMenu:
action.trigger() action.trigger()
return return
# close parent menu # close parent menu
self._closeParentMenu()
action.trigger()
def _closeParentMenu(self):
menu = self menu = self
while menu.parentMenu: while menu.parentMenu:
menu = menu.parentMenu menu = menu.parentMenu
menu.close() menu.close()
action.trigger()
def _onItemEntered(self, item): def _onItemEntered(self, item):
self.lastHoverItem = item
if not isinstance(item.data(Qt.UserRole), RoundMenu): if not isinstance(item.data(Qt.UserRole), RoundMenu):
return return
self._showSubMenu(item) self._showSubMenu(item)
def _hideMenu(self): def _hideMenu(self, isHideBySystem=False):
self.isHideBySystem = isHideBySystem
self.view.clearSelection() self.view.clearSelection()
if self.isSubMenu: if self.isSubMenu:
self.hide() self.hide()
else: else:
self.close() self.close()
def hideEvent(self, e):
if self.isHideBySystem and self.isSubMenu:
self._closeParentMenu()
self.isHideBySystem = True
e.accept()
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._hideMenu() self._hideMenu(True)
def mouseMoveEvent(self, e): def mouseMoveEvent(self, e):
if not self.isSubMenu: if not self.isSubMenu:
...@@ -545,7 +506,7 @@ class RoundMenu(QWidget): ...@@ -545,7 +506,7 @@ class RoundMenu(QWidget):
if self.parentMenu.geometry().contains(pos) and not rect.contains(pos) and \ if self.parentMenu.geometry().contains(pos) and not rect.contains(pos) and \
not self.geometry().contains(pos): not self.geometry().contains(pos):
view.clearSelection() view.clearSelection()
self._hideMenu() self._hideMenu(False)
# update style # update style
index = view.row(self.menuItem) index = view.row(self.menuItem)
...@@ -554,6 +515,24 @@ class RoundMenu(QWidget): ...@@ -554,6 +515,24 @@ class RoundMenu(QWidget):
if index < view.count()-1: if index < view.count()-1:
view.item(index+1).setFlags(Qt.ItemIsEnabled) view.item(index+1).setFlags(Qt.ItemIsEnabled)
def _onActionChanged(self):
""" action changed slot """
action = self.sender()
item = action.property('item')
item.setIcon(self._createItemIcon(action))
if not self._hasItemIcon():
item.setText(action.text())
w = 28 + self.view.fontMetrics().width(action.text())
else:
# add a blank character to increase space between icon and text
item.setText(" " + action.text())
w = 60 + self.view.fontMetrics().width(item.text())
item.setSizeHint(QSize(w, self.itemHeight))
self.view.adjustSize()
self.adjustSize()
def _onSlideValueChanged(self, pos): def _onSlideValueChanged(self, pos):
m = self.layout().contentsMargins() m = self.layout().contentsMargins()
w = self.view.width() + m.left() + m.right() + 120 w = self.view.width() + m.left() + m.right() + 120
...@@ -603,3 +582,89 @@ class RoundMenu(QWidget): ...@@ -603,3 +582,89 @@ class RoundMenu(QWidget):
view.item(index-1).setFlags(Qt.NoItemFlags) view.item(index-1).setFlags(Qt.NoItemFlags)
if index < view.count()-1: if index < view.count()-1:
view.item(index+1).setFlags(Qt.NoItemFlags) view.item(index+1).setFlags(Qt.NoItemFlags)
def exec_(self, pos: QPoint, ani=True):
""" show menu
Parameters
----------
pos: QPoint
pop-up position
ani: bool
Whether to show pop-up animation
"""
self.exec(pos, ani)
class LineEditMenu(RoundMenu):
""" Line edit menu """
def __init__(self, parent):
super().__init__("", parent)
self.animation = QPropertyAnimation(self, b"geometry")
self.animation.setDuration(300)
self.animation.setEasingCurve(QEasingCurve.OutQuad)
self.setProperty("selectAll", bool(self.parent().text()))
def createActions(self):
self.cutAct = QAction(
FIF.icon(FIF.CUT),
self.tr("Cut"),
self,
shortcut="Ctrl+X",
triggered=self.parent().cut,
)
self.copyAct = QAction(
FIF.icon(FIF.COPY),
self.tr("Copy"),
self,
shortcut="Ctrl+C",
triggered=self.parent().copy,
)
self.pasteAct = QAction(
FIF.icon(FIF.PASTE),
self.tr("Paste"),
self,
shortcut="Ctrl+V",
triggered=self.parent().paste,
)
self.cancelAct = QAction(
FIF.icon(FIF.CANCEL),
self.tr("Cancel"),
self,
shortcut="Ctrl+Z",
triggered=self.parent().undo,
)
self.selectAllAct = QAction(
self.tr("Select all"),
self,
shortcut="Ctrl+A",
triggered=self.parent().selectAll
)
self.action_list = [self.cutAct, self.copyAct,
self.pasteAct, self.cancelAct, self.selectAllAct]
def exec(self, pos, ani=True):
self.clear()
self.createActions()
if QApplication.clipboard().mimeData().hasText():
if self.parent().text():
if self.parent().selectedText():
self.addActions(self.action_list)
else:
self.addActions(self.action_list[2:])
else:
self.addAction(self.pasteAct)
else:
if self.parent().text():
if self.parent().selectedText():
self.addActions(
self.action_list[:2] + self.action_list[3:])
else:
self.addActions(self.action_list[3:])
else:
return
super().exec(pos, ani)
...@@ -6,7 +6,7 @@ with open('README.md', encoding='utf-8') as f: ...@@ -6,7 +6,7 @@ with open('README.md', encoding='utf-8') as f:
setuptools.setup( setuptools.setup(
name="PyQt-Fluent-Widgets", name="PyQt-Fluent-Widgets",
version="0.2.2", version="0.2.3",
keywords="pyqt fluent widgets", keywords="pyqt fluent widgets",
author="zhiyiYo", author="zhiyiYo",
author_email="shokokawaii@outlook.com", author_email="shokokawaii@outlook.com",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册