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

添加 `CommandBarView`

上级 8c7cd1e4
# coding:utf-8
import sys
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import QApplication, QWidget, QToolBar, QHBoxLayout, QAction
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
from qfluentwidgets import FluentIcon, TransparentPushButton
from qfluentwidgets import CommandButton, CommandBar, Action, setTheme, Theme, setFont
from qfluentwidgets import (FluentIcon, TransparentDropDownPushButton, RoundMenu, CommandBar, Action,
setTheme, Theme, setFont, CommandBarView, Flyout, FlyoutAnimationType,
ImageLabel, ToolButton, PushButton)
from qframelesswindow import FramelessWindow, StandardTitleBar
class Demo(QWidget):
class Demo1(QWidget):
def __init__(self):
super().__init__()
# setTheme(Theme.DARK)
# self.setStyleSheet('Demo{background: rgb(32, 32, 32)}')
# self.setStyleSheet('Demo1{background: rgb(32, 32, 32)}')
self.hBoxLayout = QHBoxLayout(self)
self.commandBar = CommandBar(self)
self.dropDownButton = self.createDropDownButton()
self.hBoxLayout.addWidget(self.commandBar, 0)
# change button style
self.commandBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
# self.commandBar.setMenuDropDown(False)
# self.commandBar.setButtonTight(True)
# setFont(self.commandBar, 14)
self.addButton(FluentIcon.ADD, 'Add')
......@@ -30,10 +37,15 @@ class Demo(QWidget):
self.addButton(FluentIcon.COPY, 'Copy')
self.addButton(FluentIcon.SHARE, 'Share')
# add custom widget
self.commandBar.addWidget(self.dropDownButton)
# add hidden actions
self.commandBar.addHiddenAction(Action(FluentIcon.SCROLL, 'Sort', triggered=lambda: print('排序')))
self.commandBar.addHiddenAction(Action(FluentIcon.SETTING, 'Settings'))
self.resize(170, 40)
self.resize(240, 40)
self.setWindowTitle('Drag window')
def addButton(self, icon, text):
action = Action(icon, text, self)
......@@ -43,6 +55,52 @@ class Demo(QWidget):
def onEdit(self, isChecked):
print('Enter edit mode' if isChecked else 'Exit edit mode')
def createDropDownButton(self):
button = TransparentDropDownPushButton('Menu', self, FluentIcon.MENU)
button.setFixedHeight(34)
setFont(button, 12)
menu = RoundMenu(parent=self)
menu.addActions([
Action(FluentIcon.COPY, 'Copy'),
Action(FluentIcon.CUT, 'Cut'),
Action(FluentIcon.PASTE, 'Paste'),
Action(FluentIcon.CANCEL, 'Cancel'),
Action('Select all'),
])
button.setMenu(menu)
return button
class Demo2(FramelessWindow):
def __init__(self):
super().__init__()
self.setTitleBar(StandardTitleBar(self))
self.vBoxLayout = QHBoxLayout(self)
self.imageLabel = ImageLabel('resource/pink_memory.jpg')
self.imageLabel.scaledToWidth(380)
self.imageLabel.clicked.connect(self.showCommandBar)
self.vBoxLayout.addWidget(self.imageLabel)
self.vBoxLayout.setContentsMargins(0, 80, 0, 0)
self.setStyleSheet('Demo2{background: white}')
self.setWindowTitle('Click image 🥵')
self.setWindowIcon(QIcon(":/qfluentwidgets/images/logo.png"))
def showCommandBar(self):
view = CommandBarView(self)
view.addAction(Action(FluentIcon.SHARE, 'Share'))
view.addAction(Action(FluentIcon.SAVE, 'Save'))
view.addAction(Action(FluentIcon.DELETE, 'Delete'))
view.addHiddenAction(Action(FluentIcon.SETTING, 'Settings'))
view.resizeToSuitableWidth()
Flyout.make(view, self.imageLabel, self, FlyoutAnimationType.FADE_IN)
if __name__ == '__main__':
# enable dpi scale
......@@ -52,6 +110,8 @@ if __name__ == '__main__':
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
w = Demo()
w.show()
w1 = Demo1()
w1.show()
w2 = Demo2()
w2.show()
app.exec_()
# coding: utf-8
from PyQt5.QtCore import Qt
from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin
from qfluentwidgets import CommandBar, Action, FluentIcon
from plugin_base import PluginBase
class ToolBarPlugin(PluginBase):
def group(self):
return super().group() + ' (ToolBar)'
class CommandBarPlugin(ToolBarPlugin, QPyDesignerCustomWidgetPlugin):
""" Command bar plugin """
def createWidget(self, parent):
w = CommandBar(parent)
w.addAction(Action(FluentIcon.SHARE, 'Share'))
w.addAction(Action(FluentIcon.SAVE, 'Save'))
w.addAction(Action(FluentIcon.DELETE, 'Delete'))
return w
def icon(self):
return super().icon("CommandBar")
def name(self):
return "CommandBar"
......@@ -76,12 +76,24 @@ MenuActionListWidget::item:selected:active {
color: rgba(255, 255, 255, 0.7);
}
#completerListWidget[dropDown=true] {
#completerListWidget[dropDown=true],
#commandListWidget[dropDown=true][long=false] {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
#completerListWidget[dropDown=false] {
#commandListWidget[dropDown=true][long=true] {
border-top-left-radius: 9px;
border-top-right-radius: 0px;
}
#commandListWidget[dropDown=false][long=true] {
border-bottom-left-radius: 9px;
border-bottom-right-radius: 0px;
}
#completerListWidget[dropDown=false],
#commandListWidget[dropDown=false][long=false] {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
......
......@@ -73,12 +73,24 @@ MenuActionListWidget::item:selected:active {
color: rgba(0, 0, 0, 0.7);
}
#completerListWidget[dropDown=true] {
#completerListWidget[dropDown=true],
#commandListWidget[dropDown=true][long=false] {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
#completerListWidget[dropDown=false] {
#commandListWidget[dropDown=true][long=true] {
border-top-left-radius: 9px;
border-top-right-radius: 0px;
}
#commandListWidget[dropDown=false][long=true] {
border-bottom-left-radius: 9px;
border-bottom-right-radius: 0px;
}
#completerListWidget[dropDown=false],
#commandListWidget[dropDown=false][long=false] {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
......@@ -7,7 +7,7 @@ from .button import (DropDownPushButton, DropDownToolButton, PrimaryPushButton,
from .card_widget import CardWidget
from .check_box import CheckBox
from .combo_box import ComboBox, EditableComboBox
from .command_bar import CommandBar, CommandButton
from .command_bar import CommandBar, CommandButton, CommandBarView
from .line_edit import LineEdit, TextEdit, PlainTextEdit, LineEditButton, SearchLineEdit
from .icon_widget import IconWidget
from .label import (PixmapLabel, CaptionLabel, StrongBodyLabel, BodyLabel, SubtitleLabel, TitleLabel,
......
# coding:utf-8
from typing import Iterable, List, Tuple, Union
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QRectF, QRect, QPoint
from PyQt5.QtGui import QPixmap, QPainter, QColor, QFont
from PyQt5.QtWidgets import QAction, QLayoutItem, QWidget, QFrame
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QRectF, QRect, QPoint, QEvent
from PyQt5.QtGui import QPixmap, QPainter, QColor, QFont, QHoverEvent, QPainterPath
from PyQt5.QtWidgets import QAction, QLayoutItem, QWidget, QFrame, QHBoxLayout, QApplication
from ...common.font import setFont
from ...common.icon import FluentIcon, Icon, Action
from ...common.style_sheet import isDarkTheme
from .menu import RoundMenu, MenuAnimationType
from .button import TransparentToggleToolButton
from .tool_tip import ToolTipFilter
from .flyout import FlyoutViewBase, Flyout
class CommandButton(TransparentToggleToolButton):
......@@ -33,14 +35,13 @@ class CommandButton(TransparentToggleToolButton):
return self._isTight
def sizeHint(self) -> QSize:
style = self.toolButtonStyle()
if style in [Qt.ToolButtonIconOnly, Qt.ToolButtonFollowStyle] or not self.text():
return QSize(40, 34) if self.isTight() else QSize(48, 34)
if self.isIconOnly():
return QSize(36, 34) if self.isTight() else QSize(48, 34)
# get the width of text
tw = self.fontMetrics().width(self.text())
style = self.toolButtonStyle()
if style == Qt.ToolButtonTextBesideIcon:
return QSize(tw + 47, 34)
if style == Qt.ToolButtonTextOnly:
......@@ -48,6 +49,12 @@ class CommandButton(TransparentToggleToolButton):
return QSize(tw + 32, 50)
def isIconOnly(self):
if not self.text():
return True
return self.toolButtonStyle() in [Qt.ToolButtonIconOnly, Qt.ToolButtonFollowStyle]
def _drawIcon(self, icon, painter, rect):
pass
......@@ -62,6 +69,17 @@ class CommandButton(TransparentToggleToolButton):
self._action = action
self.clicked.connect(action.trigger)
action.toggled.connect(self._onActionToggled)
action.changed.connect(self._onActionChanged)
# set tool tip
self.setToolTip(action.toolTip())
self.installEventFilter(CommandToolTipFilter(self, 700))
def _onActionChanged(self):
action = self.action()
self.setIcon(action.icon())
self.setText(action.text())
self.setToolTip(action.toolTip())
def _onActionToggled(self, isChecked: bool):
self.setCheckable(True)
......@@ -86,7 +104,7 @@ class CommandButton(TransparentToggleToolButton):
style = self.toolButtonStyle()
iw, ih = self.iconSize().width(), self.iconSize().height()
if style in [Qt.ToolButtonIconOnly, Qt.ToolButtonFollowStyle] or not self.text():
if self.isIconOnly():
y = (self.height() - ih) / 2
x = (self.width() - iw) / 2
super()._drawIcon(self._icon, painter, QRectF(x, y, iw, ih))
......@@ -106,6 +124,13 @@ class CommandButton(TransparentToggleToolButton):
painter.drawText(rect, Qt.AlignHCenter | Qt.AlignTop, self.text())
class CommandToolTipFilter(ToolTipFilter):
""" Command tool tip filter """
def _canShowToolTip(self) -> bool:
return super()._canShowToolTip() and self.parent().isIconOnly()
class MoreActionsButton(CommandButton):
""" More action button """
......@@ -116,6 +141,11 @@ class MoreActionsButton(CommandButton):
def sizeHint(self):
return QSize(40, 34)
def clearState(self):
self.setAttribute(Qt.WA_UnderMouse, False)
e = QHoverEvent(QEvent.HoverLeave, QPoint(-1, -1), QPoint())
QApplication.sendEvent(self, e)
class CommandSeparator(QWidget):
""" Command separator """
......@@ -132,6 +162,7 @@ class CommandSeparator(QWidget):
class CommandMenu(RoundMenu):
""" Command menu """
def __init__(self, parent=None):
super().__init__("", parent)
......@@ -140,6 +171,7 @@ class CommandMenu(RoundMenu):
class CommandBar(QFrame):
""" Command bar """
def __init__(self, parent=None):
super().__init__(parent=parent)
......@@ -149,6 +181,7 @@ class CommandBar(QFrame):
self._menuAnimation = MenuAnimationType.DROP_DOWN
self._toolButtonStyle = Qt.ToolButtonIconOnly
self._iconSize = QSize(16, 16)
self._isButtonTight = False
self._spacing = 4
......@@ -157,6 +190,7 @@ class CommandBar(QFrame):
self.moreButton.hide()
setFont(self, 12)
self.setAttribute(Qt.WA_TranslucentBackground)
def setSpaing(self, spacing: int):
if spacing == self._spacing:
......@@ -202,12 +236,12 @@ class CommandBar(QFrame):
for action in actions:
self.addHiddenAction(action)
def insertAction(self, before: Union[Action, QAction], action: Union[Action, QAction], checkable=False):
def insertAction(self, before: QAction, action: QAction):
if before not in self.actions():
return
index = self.actions().index(before)
button = self._createButton(action, checkable)
button = self._createButton(action)
self._insertWidgetToLayout(index, button)
super().insertAction(before, action)
return button
......@@ -222,6 +256,30 @@ class CommandBar(QFrame):
""" add widget to command bar """
self._insertWidgetToLayout(-1, widget)
def removeAction(self, action: QAction):
if action not in self.actions():
return
for w in self.commandButtons:
if w.action() is action:
self._widgets.remove(w)
w.hide()
w.deleteLater()
break
self.updateGeometry()
def removeWidget(self, widget: QWidget):
if widget not in self._widgets:
return
self._widgets.remove(widget)
self.updateGeometry()
def removeHiddenAction(self, action: QAction):
if action in self._hiddenActions:
self._hiddenActions.remove(action)
def setToolButtonStyle(self, style: Qt.ToolButtonStyle):
""" set the style of tool button """
if self.toolButtonStyle() == style:
......@@ -248,16 +306,28 @@ class CommandBar(QFrame):
def isButtonTight(self):
return self._isButtonTight
def setIconSize(self, size: QSize):
if size == self._iconSize:
return
self._iconSize = size
for w in self.commandButtons:
w.setIconSize(size)
def iconSize(self):
return self._iconSize
def resizeEvent(self, e):
self.updateGeometry()
def _createButton(self, action: QAction):
""" create command button """
button = CommandButton(action.icon())
button = CommandButton(action.icon(), self)
button.setText(action.text())
button.setAction(action)
button.setToolButtonStyle(self.toolButtonStyle())
button.setTight(self.isButtonTight())
button.setIconSize(self.iconSize())
button.setFont(self.font())
return button
......@@ -271,6 +341,7 @@ class CommandBar(QFrame):
else:
self._widgets.insert(index, widget)
self.setFixedHeight(max(w.height() for w in self._widgets))
self.updateGeometry()
def minimumSizeHint(self) -> QSize:
......@@ -291,7 +362,7 @@ class CommandBar(QFrame):
x += (w + self.spacing())
# show more actions button
if self._hiddenActions:
if self._hiddenActions or len(visibles) < len(self._widgets):
self.moreButton.show()
self.moreButton.move(x, (h - self.moreButton.height()) // 2)
......@@ -301,28 +372,31 @@ class CommandBar(QFrame):
def _visibleWidgets(self) -> List[QLayoutItem]:
""" return the visible widgets in layout """
w = 0
# show more actions if there are hidden actions
widgets = self._widgets.copy()
if self._hiddenActions:
widgets.append(self.moreButton)
widths = [i.width() for i in widgets]
total = sum(widths) + w + self.spacing() * max(len(widgets) - 1, 0)
# have enough spacing to show all widgets
if total <= self.width():
if self.suitableWidth() <= self.width():
return self._widgets
w += self.moreButton.width()
w = self.moreButton.width()
for index, widget in enumerate(self._widgets):
w += widget.width()
if index > 0:
w += self.spacing()
if w > self.width():
break
return self._widgets[:index]
def suitableWidth(self):
widths = [w.width() for w in self._widgets]
if self._hiddenActions:
widths.append(self.moreButton.width())
return sum(widths) + self.spacing() * max(len(widths) - 1, 0)
def resizeToSuitableWidth(self):
self.setFixedWidth(self.suitableWidth())
def setFont(self, font: QFont):
super().setFont(font)
for button in self.commandButtons:
......@@ -339,8 +413,13 @@ class CommandBar(QFrame):
else:
self._menuAnimation = MenuAnimationType.PULL_UP
def isMenuDropDown(self):
return self._menuAnimation == MenuAnimationType.DROP_DOWN
def _showMoreActionsMenu(self):
""" show more actions menu """
self.moreButton.clearState()
actions = self._hiddenActions.copy()
for w in reversed(self._hiddenWidgets):
......@@ -351,11 +430,194 @@ class CommandBar(QFrame):
menu.addActions(actions)
x = -menu.width() + menu.layout().contentsMargins().right() + \
self.moreButton.width() + 5
self.moreButton.width() + 18
if self._menuAnimation == MenuAnimationType.DROP_DOWN:
y = self.moreButton.height()
else:
y = -menu.height() + menu.layout().contentsMargins().bottom()
y = -5
pos = self.moreButton.mapToGlobal(QPoint(x, y))
menu.exec(pos, aniType=self._menuAnimation)
class CommandViewMenu(CommandMenu):
""" Command view menu """
def __init__(self, parent=None):
super().__init__(parent)
self.view.setObjectName('commandListWidget')
def setDropDown(self, down: bool, long=False):
self.view.setProperty('dropDown', down)
self.view.setProperty('long', long)
self.view.setStyle(QApplication.style())
self.view.update()
class CommandViewBar(CommandBar):
""" Command view bar """
def __init__(self, parent=None):
super().__init__(parent)
self.setMenuDropDown(True)
def setMenuDropDown(self, down: bool):
""" set the animation direction of more actions menu """
if down:
self._menuAnimation = MenuAnimationType.FADE_IN_DROP_DOWN
else:
self._menuAnimation = MenuAnimationType.FADE_IN_PULL_UP
def isMenuDropDown(self):
return self._menuAnimation == MenuAnimationType.FADE_IN_DROP_DOWN
def _showMoreActionsMenu(self):
self.moreButton.clearState()
actions = self._hiddenActions.copy()
for w in reversed(self._hiddenWidgets):
if isinstance(w, CommandButton):
actions.insert(0, w.action())
menu = CommandViewMenu(self)
menu.addActions(actions)
# adjust the shape of view
view = self.parent() # type: CommandBarView
view.setMenuVisible(True)
# adjust the shape of menu
menu.closedSignal.connect(lambda: view.setMenuVisible(False))
menu.setDropDown(self.isMenuDropDown(), menu.view.width() > view.width())
# adjust menu size
if menu.view.width() < view.width():
menu.view.setFixedWidth(view.width())
menu.adjustSize()
x = -menu.width() + menu.layout().contentsMargins().right() + \
self.moreButton.width() + 18
if self.isMenuDropDown():
y = self.moreButton.height()
else:
y = -13
menu.setShadowEffect(0, (0, 0), QColor(0, 0, 0, 0))
menu.layout().setContentsMargins(12, 20, 12, 8)
pos = self.moreButton.mapToGlobal(QPoint(x, y))
menu.exec_(pos, aniType=self._menuAnimation)
menu.exec(pos, aniType=self._menuAnimation)
class CommandBarView(FlyoutViewBase):
""" Command bar view """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.bar = CommandViewBar(self)
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.setContentsMargins(6, 6, 6, 6)
self.hBoxLayout.addWidget(self.bar)
self.hBoxLayout.setSizeConstraint(QHBoxLayout.SetMinAndMaxSize)
self.setButtonTight(True)
self.setIconSize(QSize(14, 14))
self._isMenuVisible = False
def setMenuVisible(self, isVisible):
self._isMenuVisible = isVisible
self.update()
def addWidget(self, widget: QWidget):
self.bar.addWidget(widget)
def setSpaing(self, spacing: int):
self.bar.setSpaing(spacing)
def spacing(self):
return self.bar.spacing()
def addAction(self, action: QAction):
return self.bar.addAction(action)
def addActions(self, actions: Iterable[QAction]):
self.bar.addActions(actions)
def addHiddenAction(self, action: QAction):
self.bar.addHiddenAction(action)
def addHiddenActions(self, actions: List[QAction]):
self.bar.addHiddenActions(actions)
def insertAction(self, before: QAction, action: QAction):
return self.bar.insertAction(before, action)
def addSeparator(self):
self.bar.addSeparator()
def insertSeparator(self, index: int):
self.bar.insertSeparator(index)
def removeAction(self, action: QAction):
self.bar.removeAction(action)
def removeWidget(self, widget: QWidget):
self.bar.removeWidget(widget)
def removeHiddenAction(self, action: QAction):
self.bar.removeAction(action)
def setToolButtonStyle(self, style: Qt.ToolButtonStyle):
self.bar.setToolButtonStyle(style)
def toolButtonStyle(self):
return self.bar.toolButtonStyle()
def setButtonTight(self, isTight: bool):
self.bar.setButtonTight(isTight)
def isButtonTight(self):
return self.bar.isButtonTight()
def setIconSize(self, size: QSize):
self.bar.setIconSize(size)
def iconSize(self):
return self.bar.iconSize()
def setFont(self, font: QFont):
self.bar.setFont(font)
def setMenuDropDown(self, down: bool):
self.bar.setMenuDropDown(down)
def suitableWidth(self):
m = self.contentsMargins()
return m.left() + m.right() + self.bar.suitableWidth()
def resizeToSuitableWidth(self):
self.bar.resizeToSuitableWidth()
self.setFixedWidth(self.suitableWidth())
def actions(self):
return self.bar.actions()
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
path = QPainterPath()
path.setFillRule(Qt.WindingFill)
path.addRoundedRect(QRectF(self.rect().adjusted(1, 1, -1, -1)), 8, 8)
if self._isMenuVisible:
y = self.height() - 10 if self.bar.isMenuDropDown() else 1
path.addRect(1, y, self.width() - 2, 9)
painter.setBrush(
QColor(40, 40, 40) if isDarkTheme() else QColor(248, 248, 248))
painter.setPen(
QColor(23, 23, 23) if isDarkTheme() else QColor(233, 233, 233))
painter.drawPath(path.simplified())
......@@ -20,7 +20,8 @@ class FlyoutAnimationType(Enum):
DROP_DOWN = 1
SLIDE_LEFT = 2
SLIDE_RIGHT = 3
NONE = 4
FADE_IN = 4
NONE = 5
class IconWidget(QWidget):
......@@ -436,6 +437,24 @@ class SlideRightFlyoutAnimationManager(FlyoutAnimationManager):
self.aniGroup.start()
@FlyoutAnimationManager.register(FlyoutAnimationType.FADE_IN)
class DropDownFlyoutAnimationManager(FlyoutAnimationManager):
""" Drop down flyout animation manager """
def position(self, target: QWidget):
w = self.flyout
pos = target.mapToGlobal(QPoint())
x = pos.x() + target.width()//2 - w.sizeHint().width()//2
y = pos.y() - w.sizeHint().height() + w.layout().contentsMargins().bottom()
return QPoint(x, y)
def exec(self, pos: QPoint):
self.flyout.move(self._adjustPosition(pos))
self.aniGroup.removeAnimation(self.slideAni)
self.aniGroup.start()
@FlyoutAnimationManager.register(FlyoutAnimationType.NONE)
class DummyFlyoutAnimationManager(FlyoutAnimationManager):
""" Dummy flyout animation manager """
......
# coding:utf-8
from typing import List, Union
from PyQt5.QtCore import Qt, QRectF, QPoint
from PyQt5.QtCore import Qt, QRectF, QPoint, pyqtSignal
from PyQt5.QtGui import (QPixmap, QPainter, QPalette, QColor, QFont, QImage, QPainterPath,
QImageReader, QBrush, QMovie)
from PyQt5.QtWidgets import QLabel, QWidget
......@@ -129,6 +129,8 @@ class DisplayLabel(FluentLabelBase):
class ImageLabel(QLabel):
""" Image label """
clicked = pyqtSignal()
def __init__(self, image: Union[str, QPixmap, QImage] = None, parent=None):
super().__init__(parent=parent)
self.setImage(image)
......@@ -185,6 +187,10 @@ class ImageLabel(QLabel):
def isNull(self):
return self.image.isNull()
def mouseReleaseEvent(self, e):
super().mouseReleaseEvent(e)
self.clicked.emit()
def paintEvent(self, e):
if self.isNull():
return
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册