Python-FastUI-Widgets 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastuiwidgets/__init__.py +12 -0
- fastuiwidgets/_rc/__init__.py +0 -0
- fastuiwidgets/_rc/resource.py +98835 -0
- fastuiwidgets/common/__init__.py +12 -0
- fastuiwidgets/common/animation.py +530 -0
- fastuiwidgets/common/auto_wrap.py +164 -0
- fastuiwidgets/common/color.py +95 -0
- fastuiwidgets/common/config.py +423 -0
- fastuiwidgets/common/exception_handler.py +31 -0
- fastuiwidgets/common/font.py +38 -0
- fastuiwidgets/common/icon.py +703 -0
- fastuiwidgets/common/image_utils.py +198 -0
- fastuiwidgets/common/overload.py +47 -0
- fastuiwidgets/common/router.py +133 -0
- fastuiwidgets/common/screen.py +25 -0
- fastuiwidgets/common/smooth_scroll.py +141 -0
- fastuiwidgets/common/style_sheet.py +512 -0
- fastuiwidgets/common/theme_listener.py +27 -0
- fastuiwidgets/common/translator.py +14 -0
- fastuiwidgets/components/__init__.py +6 -0
- fastuiwidgets/components/date_time/__init__.py +4 -0
- fastuiwidgets/components/date_time/calendar_picker.py +121 -0
- fastuiwidgets/components/date_time/calendar_view.py +671 -0
- fastuiwidgets/components/date_time/date_picker.py +245 -0
- fastuiwidgets/components/date_time/fast_calendar_view.py +487 -0
- fastuiwidgets/components/date_time/picker_base.py +632 -0
- fastuiwidgets/components/date_time/time_picker.py +223 -0
- fastuiwidgets/components/dialog_box/__init__.py +6 -0
- fastuiwidgets/components/dialog_box/color_dialog.py +414 -0
- fastuiwidgets/components/dialog_box/dialog.py +167 -0
- fastuiwidgets/components/dialog_box/folder_list_dialog.py +307 -0
- fastuiwidgets/components/dialog_box/mask_dialog_base.py +120 -0
- fastuiwidgets/components/dialog_box/message_box_base.py +92 -0
- fastuiwidgets/components/dialog_box/message_dialog.py +65 -0
- fastuiwidgets/components/layout/__init__.py +3 -0
- fastuiwidgets/components/layout/expand_layout.py +96 -0
- fastuiwidgets/components/layout/flow_layout.py +236 -0
- fastuiwidgets/components/layout/v_box_layout.py +41 -0
- fastuiwidgets/components/material/__init__.py +6 -0
- fastuiwidgets/components/material/acrylic_combo_box.py +96 -0
- fastuiwidgets/components/material/acrylic_flyout.py +105 -0
- fastuiwidgets/components/material/acrylic_line_edit.py +27 -0
- fastuiwidgets/components/material/acrylic_menu.py +204 -0
- fastuiwidgets/components/material/acrylic_tool_tip.py +39 -0
- fastuiwidgets/components/material/acrylic_widget.py +42 -0
- fastuiwidgets/components/navigation/__init__.py +9 -0
- fastuiwidgets/components/navigation/breadcrumb.py +350 -0
- fastuiwidgets/components/navigation/navigation_bar.py +416 -0
- fastuiwidgets/components/navigation/navigation_interface.py +268 -0
- fastuiwidgets/components/navigation/navigation_panel.py +657 -0
- fastuiwidgets/components/navigation/navigation_widget.py +686 -0
- fastuiwidgets/components/navigation/pivot.py +272 -0
- fastuiwidgets/components/navigation/segmented_widget.py +174 -0
- fastuiwidgets/components/settings/__init__.py +8 -0
- fastuiwidgets/components/settings/custom_color_setting_card.py +139 -0
- fastuiwidgets/components/settings/expand_setting_card.py +390 -0
- fastuiwidgets/components/settings/folder_list_setting_card.py +134 -0
- fastuiwidgets/components/settings/options_setting_card.py +86 -0
- fastuiwidgets/components/settings/setting_card.py +449 -0
- fastuiwidgets/components/settings/setting_card_group.py +48 -0
- fastuiwidgets/components/widgets/__init__.py +41 -0
- fastuiwidgets/components/widgets/acrylic_label.py +261 -0
- fastuiwidgets/components/widgets/button.py +1059 -0
- fastuiwidgets/components/widgets/card_widget.py +369 -0
- fastuiwidgets/components/widgets/check_box.py +203 -0
- fastuiwidgets/components/widgets/combo_box.py +556 -0
- fastuiwidgets/components/widgets/command_bar.py +636 -0
- fastuiwidgets/components/widgets/cycle_list_widget.py +251 -0
- fastuiwidgets/components/widgets/flip_view.py +430 -0
- fastuiwidgets/components/widgets/flyout.py +521 -0
- fastuiwidgets/components/widgets/frameless_window.py +49 -0
- fastuiwidgets/components/widgets/icon_widget.py +53 -0
- fastuiwidgets/components/widgets/info_badge.py +483 -0
- fastuiwidgets/components/widgets/info_bar.py +596 -0
- fastuiwidgets/components/widgets/label.py +553 -0
- fastuiwidgets/components/widgets/line_edit.py +551 -0
- fastuiwidgets/components/widgets/list_view.py +158 -0
- fastuiwidgets/components/widgets/menu.py +1318 -0
- fastuiwidgets/components/widgets/pips_pager.py +331 -0
- fastuiwidgets/components/widgets/progress_bar.py +311 -0
- fastuiwidgets/components/widgets/progress_ring.py +212 -0
- fastuiwidgets/components/widgets/scroll_area.py +125 -0
- fastuiwidgets/components/widgets/scroll_bar.py +673 -0
- fastuiwidgets/components/widgets/separator.py +43 -0
- fastuiwidgets/components/widgets/slider.py +307 -0
- fastuiwidgets/components/widgets/spin_box.py +306 -0
- fastuiwidgets/components/widgets/stacked_widget.py +211 -0
- fastuiwidgets/components/widgets/state_tool_tip.py +188 -0
- fastuiwidgets/components/widgets/switch_button.py +312 -0
- fastuiwidgets/components/widgets/tab_view.py +804 -0
- fastuiwidgets/components/widgets/table_view.py +360 -0
- fastuiwidgets/components/widgets/teaching_tip.py +657 -0
- fastuiwidgets/components/widgets/tool_tip.py +460 -0
- fastuiwidgets/components/widgets/tree_view.py +216 -0
- fastuiwidgets/multimedia/__init__.py +3 -0
- fastuiwidgets/multimedia/media_play_bar.py +319 -0
- fastuiwidgets/multimedia/media_player.py +124 -0
- fastuiwidgets/multimedia/video_widget.py +93 -0
- fastuiwidgets/window/__init__.py +2 -0
- fastuiwidgets/window/fluent_window.py +413 -0
- fastuiwidgets/window/splash_screen.py +92 -0
- fastuiwidgets/window/stacked_widget.py +66 -0
- python_fastui_widgets-1.0.0.dist-info/METADATA +30 -0
- python_fastui_widgets-1.0.0.dist-info/RECORD +107 -0
- python_fastui_widgets-1.0.0.dist-info/WHEEL +5 -0
- python_fastui_widgets-1.0.0.dist-info/licenses/LICENSE +674 -0
- python_fastui_widgets-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from typing import List
|
3
|
+
from PySide6.QtCore import Qt, QRect, QRectF, QSize
|
4
|
+
from PySide6.QtGui import QPainter, QColor, QPainterPath, QAction
|
5
|
+
from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QListWidget
|
6
|
+
|
7
|
+
from ..widgets.menu import (RoundMenu, MenuAnimationType, MenuAnimationManager, MenuActionListWidget,
|
8
|
+
IndicatorMenuItemDelegate, LineEditMenu, MenuIndicatorType, CheckableMenu)
|
9
|
+
from ..widgets.line_edit import CompleterMenu, LineEdit
|
10
|
+
from ..widgets.acrylic_label import AcrylicBrush
|
11
|
+
from ...common.style_sheet import isDarkTheme
|
12
|
+
|
13
|
+
|
14
|
+
class AcrylicMenuActionListWidget(MenuActionListWidget):
|
15
|
+
|
16
|
+
def __init__(self, parent=None):
|
17
|
+
super().__init__(parent)
|
18
|
+
self.acrylicBrush = AcrylicBrush(self.viewport(), 35)
|
19
|
+
self.setViewportMargins(0, 0, 0, 0)
|
20
|
+
self.setProperty("transparent", True)
|
21
|
+
|
22
|
+
super().addItem(self.createPlaceholderItem(self._topMargin()))
|
23
|
+
super().addItem(self.createPlaceholderItem(self._bottomMargin()))
|
24
|
+
|
25
|
+
def _updateAcrylicColor(self):
|
26
|
+
if isDarkTheme():
|
27
|
+
tintColor = QColor(32, 32, 32, 200)
|
28
|
+
luminosityColor = QColor(0, 0, 0, 0)
|
29
|
+
else:
|
30
|
+
tintColor = QColor(255, 255, 255, 160)
|
31
|
+
luminosityColor = QColor(255, 255, 255, 50)
|
32
|
+
|
33
|
+
self.acrylicBrush.tintColor = tintColor
|
34
|
+
self.acrylicBrush.luminosityColor = luminosityColor
|
35
|
+
|
36
|
+
def _topMargin(self):
|
37
|
+
return 6
|
38
|
+
|
39
|
+
def _bottomMargin(self):
|
40
|
+
return 6
|
41
|
+
|
42
|
+
def setItemHeight(self, height: int):
|
43
|
+
""" set the height of item """
|
44
|
+
if height == self._itemHeight:
|
45
|
+
return
|
46
|
+
|
47
|
+
for i in range(1, self.count() - 1):
|
48
|
+
item = self.item(i)
|
49
|
+
if not self.itemWidget(item):
|
50
|
+
item.setSizeHint(QSize(item.sizeHint().width(), height))
|
51
|
+
|
52
|
+
self._itemHeight = height
|
53
|
+
self.adjustSize()
|
54
|
+
|
55
|
+
def addItem(self, item):
|
56
|
+
return super().insertItem(self.count() - 1, item)
|
57
|
+
|
58
|
+
def createPlaceholderItem(self, height=2):
|
59
|
+
item = QListWidgetItem()
|
60
|
+
item.setSizeHint(QSize(1, height))
|
61
|
+
item.setFlags(Qt.ItemFlag.NoItemFlags)
|
62
|
+
return item
|
63
|
+
|
64
|
+
def clipPath(self):
|
65
|
+
path = QPainterPath()
|
66
|
+
path.addRoundedRect(QRectF(self.rect()).adjusted(0, 0, -2.5, -2.5), 8, 8)
|
67
|
+
return path
|
68
|
+
|
69
|
+
def paintEvent(self, e) -> None:
|
70
|
+
painter = QPainter(self.viewport())
|
71
|
+
painter.setRenderHints(QPainter.Antialiasing |
|
72
|
+
QPainter.SmoothPixmapTransform)
|
73
|
+
|
74
|
+
self.acrylicBrush.clipPath = self.clipPath()
|
75
|
+
self._updateAcrylicColor()
|
76
|
+
self.acrylicBrush.paint()
|
77
|
+
|
78
|
+
super().paintEvent(e)
|
79
|
+
|
80
|
+
|
81
|
+
class AcrylicMenuBase:
|
82
|
+
|
83
|
+
def __init__(self, *args, **kwargs):
|
84
|
+
super().__init__(*args, **kwargs)
|
85
|
+
|
86
|
+
def setUpMenu(self, view):
|
87
|
+
self.hBoxLayout.removeWidget(self.view)
|
88
|
+
self.view.deleteLater()
|
89
|
+
|
90
|
+
self.view = view
|
91
|
+
self.hBoxLayout.addWidget(self.view)
|
92
|
+
|
93
|
+
self.setShadowEffect()
|
94
|
+
|
95
|
+
self.view.itemClicked.connect(self._onItemClicked)
|
96
|
+
self.view.itemEntered.connect(self._onItemEntered)
|
97
|
+
|
98
|
+
def exec(self, pos, ani=True, aniType=MenuAnimationType.DROP_DOWN):
|
99
|
+
p = MenuAnimationManager.make(self, aniType)._endPosition(pos)
|
100
|
+
self.view.acrylicBrush.grabImage(QRect(p, self.layout().sizeHint()))
|
101
|
+
super().exec(pos, ani, aniType)
|
102
|
+
|
103
|
+
|
104
|
+
class AcrylicMenu(AcrylicMenuBase, RoundMenu):
|
105
|
+
""" Acrylic menu """
|
106
|
+
|
107
|
+
def __init__(self, title="", parent=None):
|
108
|
+
super().__init__(title, parent)
|
109
|
+
self.setUpMenu(AcrylicMenuActionListWidget(self))
|
110
|
+
|
111
|
+
def exec(self, pos, ani=True, aniType=MenuAnimationType.DROP_DOWN):
|
112
|
+
return super().exec(pos, ani, aniType)
|
113
|
+
|
114
|
+
|
115
|
+
class AcrylicCompleterMenuActionListWidget(AcrylicMenuActionListWidget):
|
116
|
+
|
117
|
+
def clipPath(self):
|
118
|
+
path = QPainterPath()
|
119
|
+
path.setFillRule(Qt.FillRule.WindingFill)
|
120
|
+
path.addRoundedRect(QRectF(self.rect()).adjusted(1, 1, -2.5, -2.5), 8, 8)
|
121
|
+
|
122
|
+
if self.property("dropDown"):
|
123
|
+
path.addRect(1, 1, 11, 11)
|
124
|
+
path.addRect(self.width() - 12, 1, 11, 11)
|
125
|
+
else:
|
126
|
+
path.addRect(1, self.height() - 11, 11, 11)
|
127
|
+
path.addRect(self.width() - 12, self.height() - 11, 11, 11)
|
128
|
+
|
129
|
+
return path
|
130
|
+
|
131
|
+
|
132
|
+
class AcrylicCompleterMenu(AcrylicMenuBase, CompleterMenu):
|
133
|
+
""" Acrylic completer menu """
|
134
|
+
|
135
|
+
def __init__(self, lineEdit: LineEdit):
|
136
|
+
super().__init__(lineEdit)
|
137
|
+
self.setUpMenu(AcrylicCompleterMenuActionListWidget(self))
|
138
|
+
|
139
|
+
self.view.setObjectName('completerListWidget')
|
140
|
+
self.view.setItemDelegate(IndicatorMenuItemDelegate())
|
141
|
+
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
142
|
+
self.setItemHeight(33)
|
143
|
+
|
144
|
+
def _onItemClicked(self, item):
|
145
|
+
self._hideMenu(False)
|
146
|
+
self._onCompletionItemSelected(item.text(), self.view.row(item)-1)
|
147
|
+
|
148
|
+
def exec(self, pos, ani=True, aniType=MenuAnimationType.DROP_DOWN):
|
149
|
+
return super().exec(pos, ani, aniType)
|
150
|
+
|
151
|
+
def setItems(self, items):
|
152
|
+
""" set completion items """
|
153
|
+
self.view.clear()
|
154
|
+
|
155
|
+
self.items = items
|
156
|
+
|
157
|
+
QListWidget.addItem(self.view, self.view.createPlaceholderItem(self.view._topMargin()))
|
158
|
+
self.view.addItems(items)
|
159
|
+
|
160
|
+
for i in range(1, self.view.count()):
|
161
|
+
item = self.view.item(i)
|
162
|
+
item.setSizeHint(QSize(1, self.itemHeight))
|
163
|
+
|
164
|
+
QListWidget.addItem(self.view, self.view.createPlaceholderItem(self.view._bottomMargin()))
|
165
|
+
|
166
|
+
|
167
|
+
class AcrylicLineEditMenu(AcrylicMenuBase, LineEditMenu):
|
168
|
+
""" Acrylic line edit menu """
|
169
|
+
|
170
|
+
def __init__(self, parent: QLineEdit):
|
171
|
+
super().__init__(parent)
|
172
|
+
self.setUpMenu(AcrylicMenuActionListWidget(self))
|
173
|
+
|
174
|
+
def exec(self, pos, ani=True, aniType=MenuAnimationType.DROP_DOWN):
|
175
|
+
return super().exec(pos, ani, aniType)
|
176
|
+
|
177
|
+
|
178
|
+
class AcrylicCheckableMenu(AcrylicMenuBase, CheckableMenu):
|
179
|
+
""" Checkable menu """
|
180
|
+
|
181
|
+
def __init__(self, title="", parent=None, indicatorType=MenuIndicatorType.CHECK):
|
182
|
+
super().__init__(title, parent, indicatorType)
|
183
|
+
self.setUpMenu(AcrylicMenuActionListWidget(self))
|
184
|
+
self.view.setObjectName('checkableListWidget')
|
185
|
+
|
186
|
+
def exec(self, pos, ani=True, aniType=MenuAnimationType.DROP_DOWN):
|
187
|
+
return super().exec(pos, ani, aniType)
|
188
|
+
|
189
|
+
|
190
|
+
class AcrylicSystemTrayMenu(AcrylicMenu):
|
191
|
+
""" System tray menu """
|
192
|
+
|
193
|
+
def showEvent(self, e):
|
194
|
+
super().showEvent(e)
|
195
|
+
self.adjustPosition()
|
196
|
+
self.view.acrylicBrush.grabImage(QRect(self.pos(), self.layout().sizeHint()))
|
197
|
+
|
198
|
+
|
199
|
+
class AcrylicCheckableSystemTrayMenu(AcrylicCheckableMenu):
|
200
|
+
""" Checkable system tray menu """
|
201
|
+
|
202
|
+
def showEvent(self, e):
|
203
|
+
super().showEvent(e)
|
204
|
+
self.adjustPosition()
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
from PySide6.QtCore import QRect, QRectF
|
3
|
+
from PySide6.QtGui import QPainterPath
|
4
|
+
from PySide6.QtWidgets import QApplication, QFrame
|
5
|
+
|
6
|
+
from .acrylic_widget import AcrylicWidget
|
7
|
+
from ..widgets.tool_tip import ToolTip, ToolTipFilter
|
8
|
+
|
9
|
+
|
10
|
+
class AcrylicToolTipContainer(AcrylicWidget, QFrame):
|
11
|
+
""" Acrylic tool tip container """
|
12
|
+
|
13
|
+
def __init__(self, parent=None):
|
14
|
+
super().__init__(parent=parent)
|
15
|
+
self.setProperty("transparent", True)
|
16
|
+
|
17
|
+
def acrylicClipPath(self):
|
18
|
+
path = QPainterPath()
|
19
|
+
path.addRoundedRect(QRectF(self.rect().adjusted(1, 1, -1, -1)), 3, 3)
|
20
|
+
return path
|
21
|
+
|
22
|
+
|
23
|
+
class AcrylicToolTip(ToolTip):
|
24
|
+
""" Acrylic tool tip """
|
25
|
+
|
26
|
+
def _createContainer(self):
|
27
|
+
return AcrylicToolTipContainer(self)
|
28
|
+
|
29
|
+
def showEvent(self, e):
|
30
|
+
pos = self.pos() + self.container.pos()
|
31
|
+
self.container.acrylicBrush.grabImage(QRect(pos, self.container.size()))
|
32
|
+
return super().showEvent(e)
|
33
|
+
|
34
|
+
|
35
|
+
class AcrylicToolTipFilter(ToolTipFilter):
|
36
|
+
""" Acrylic tool tip filter """
|
37
|
+
|
38
|
+
def _createToolTip(self):
|
39
|
+
return AcrylicToolTip(self.parent().toolTip(), self.parent().window())
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from PySide6.QtGui import QPainterPath, QPainter, QColor
|
3
|
+
|
4
|
+
from ..widgets.acrylic_label import AcrylicBrush
|
5
|
+
from ...common.style_sheet import isDarkTheme
|
6
|
+
|
7
|
+
|
8
|
+
class AcrylicWidget:
|
9
|
+
""" Acrylic widget """
|
10
|
+
|
11
|
+
def __init__(self, *args, **kwargs):
|
12
|
+
super().__init__(*args, **kwargs)
|
13
|
+
self.acrylicBrush = AcrylicBrush(self, 30)
|
14
|
+
|
15
|
+
def _updateAcrylicColor(self):
|
16
|
+
if isDarkTheme():
|
17
|
+
tintColor = QColor(32, 32, 32, 200)
|
18
|
+
luminosityColor = QColor(0, 0, 0, 0)
|
19
|
+
else:
|
20
|
+
tintColor = QColor(255, 255, 255, 180)
|
21
|
+
luminosityColor = QColor(255, 255, 255, 0)
|
22
|
+
|
23
|
+
self.acrylicBrush.tintColor = tintColor
|
24
|
+
self.acrylicBrush.luminosityColor = luminosityColor
|
25
|
+
|
26
|
+
def acrylicClipPath(self):
|
27
|
+
return QPainterPath()
|
28
|
+
|
29
|
+
def _drawAcrylic(self, painter: QPainter):
|
30
|
+
path = self.acrylicClipPath()
|
31
|
+
if not path.isEmpty():
|
32
|
+
self.acrylicBrush.clipPath = self.acrylicClipPath()
|
33
|
+
|
34
|
+
self._updateAcrylicColor()
|
35
|
+
self.acrylicBrush.paint()
|
36
|
+
|
37
|
+
def paintEvent(self, e):
|
38
|
+
painter = QPainter(self)
|
39
|
+
painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
|
40
|
+
|
41
|
+
self._drawAcrylic(painter)
|
42
|
+
super().paintEvent(e)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
from .navigation_widget import (NavigationWidget, NavigationPushButton, NavigationSeparator, NavigationToolButton,
|
2
|
+
NavigationTreeWidget, NavigationTreeWidgetBase, NavigationAvatarWidget)
|
3
|
+
from .navigation_panel import NavigationPanel, NavigationItemPosition, NavigationDisplayMode
|
4
|
+
from .navigation_interface import NavigationInterface
|
5
|
+
from .navigation_bar import NavigationBarPushButton, NavigationBar
|
6
|
+
from .pivot import Pivot, PivotItem
|
7
|
+
from .segmented_widget import (SegmentedItem, SegmentedWidget, SegmentedToolItem, SegmentedToolWidget,
|
8
|
+
SegmentedToggleToolItem, SegmentedToggleToolWidget)
|
9
|
+
from .breadcrumb import BreadcrumbBar, BreadcrumbItem
|
@@ -0,0 +1,350 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
import math
|
3
|
+
|
4
|
+
from typing import Dict, List
|
5
|
+
from PySide6.QtCore import Qt, Signal, QRectF, Property, QPoint, QEvent
|
6
|
+
from PySide6.QtGui import QPainter, QFont, QHoverEvent, QAction
|
7
|
+
from PySide6.QtWidgets import QWidget, QApplication
|
8
|
+
|
9
|
+
from ...common.font import setFont
|
10
|
+
from ...common.icon import FluentIcon
|
11
|
+
from ...common.style_sheet import isDarkTheme
|
12
|
+
from ...components.widgets.menu import RoundMenu, MenuAnimationType
|
13
|
+
|
14
|
+
|
15
|
+
class BreadcrumbWidget(QWidget):
|
16
|
+
""" Bread crumb widget """
|
17
|
+
|
18
|
+
clicked = Signal()
|
19
|
+
|
20
|
+
def __init__(self, parent=None):
|
21
|
+
super().__init__(parent=parent)
|
22
|
+
self.isHover = False
|
23
|
+
self.isPressed = False
|
24
|
+
|
25
|
+
def mousePressEvent(self, e):
|
26
|
+
self.isPressed = True
|
27
|
+
self.update()
|
28
|
+
|
29
|
+
def mouseReleaseEvent(self, e):
|
30
|
+
self.isPressed = False
|
31
|
+
self.update()
|
32
|
+
self.clicked.emit()
|
33
|
+
|
34
|
+
def enterEvent(self, e):
|
35
|
+
self.isHover = True
|
36
|
+
self.update()
|
37
|
+
|
38
|
+
def leaveEvent(self, e):
|
39
|
+
self.isHover = False
|
40
|
+
self.update()
|
41
|
+
|
42
|
+
|
43
|
+
class ElideButton(BreadcrumbWidget):
|
44
|
+
""" Elide button """
|
45
|
+
|
46
|
+
def __init__(self, parent=None):
|
47
|
+
super().__init__(parent)
|
48
|
+
self.setFixedSize(16, 16)
|
49
|
+
|
50
|
+
def paintEvent(self, e):
|
51
|
+
painter = QPainter(self)
|
52
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
53
|
+
painter.setPen(Qt.NoPen)
|
54
|
+
|
55
|
+
if self.isPressed:
|
56
|
+
painter.setOpacity(0.5)
|
57
|
+
elif not self.isHover:
|
58
|
+
painter.setOpacity(0.61)
|
59
|
+
|
60
|
+
FluentIcon.MORE.render(painter, self.rect())
|
61
|
+
|
62
|
+
def clearState(self):
|
63
|
+
self.setAttribute(Qt.WA_UnderMouse, False)
|
64
|
+
self.isHover = False
|
65
|
+
e = QHoverEvent(QEvent.HoverLeave, QPoint(-1, -1), QPoint())
|
66
|
+
QApplication.sendEvent(self, e)
|
67
|
+
|
68
|
+
|
69
|
+
class BreadcrumbItem(BreadcrumbWidget):
|
70
|
+
""" Breadcrumb item """
|
71
|
+
|
72
|
+
def __init__(self, routeKey: str, text: str, index: int, parent=None):
|
73
|
+
super().__init__(parent=parent)
|
74
|
+
self.text = text
|
75
|
+
self.routeKey = routeKey
|
76
|
+
self.isHover = False
|
77
|
+
self.isPressed = False
|
78
|
+
self.isSelected = False
|
79
|
+
self.index = index
|
80
|
+
self.spacing = 5
|
81
|
+
|
82
|
+
def setText(self, text: str):
|
83
|
+
self.text = text
|
84
|
+
|
85
|
+
rect = self.fontMetrics().boundingRect(text)
|
86
|
+
w = rect.width() + math.ceil(self.font().pixelSize() / 10)
|
87
|
+
if not self.isRoot():
|
88
|
+
w += self.spacing * 2
|
89
|
+
|
90
|
+
self.setFixedWidth(w)
|
91
|
+
self.setFixedHeight(rect.height())
|
92
|
+
self.update()
|
93
|
+
|
94
|
+
def isRoot(self):
|
95
|
+
return self.index == 0
|
96
|
+
|
97
|
+
def setSelected(self, isSelected: bool):
|
98
|
+
self.isSelected = isSelected
|
99
|
+
self.update()
|
100
|
+
|
101
|
+
def setFont(self, font: QFont):
|
102
|
+
super().setFont(font)
|
103
|
+
self.setText(self.text)
|
104
|
+
|
105
|
+
def setSpacing(self, spacing: int):
|
106
|
+
self.spacing = spacing
|
107
|
+
self.setText(self.text)
|
108
|
+
|
109
|
+
def paintEvent(self, e):
|
110
|
+
painter = QPainter(self)
|
111
|
+
painter.setRenderHints(QPainter.TextAntialiasing | QPainter.Antialiasing)
|
112
|
+
painter.setPen(Qt.NoPen)
|
113
|
+
|
114
|
+
# draw seperator
|
115
|
+
sw = self.spacing * 2
|
116
|
+
if not self.isRoot():
|
117
|
+
iw = self.font().pixelSize() / 14 * 8
|
118
|
+
rect = QRectF((sw - iw) / 2, (self.height() - iw) / 2 + 1, iw, iw)
|
119
|
+
|
120
|
+
painter.setOpacity(0.61)
|
121
|
+
FluentIcon.CHEVRON_RIGHT_MED.render(painter, rect)
|
122
|
+
|
123
|
+
# draw text
|
124
|
+
if self.isPressed:
|
125
|
+
alpha = 0.54 if isDarkTheme() else 0.45
|
126
|
+
painter.setOpacity(1 if self.isSelected else alpha)
|
127
|
+
elif self.isSelected or self.isHover:
|
128
|
+
painter.setOpacity(1)
|
129
|
+
else:
|
130
|
+
painter.setOpacity(0.79 if isDarkTheme() else 0.61)
|
131
|
+
|
132
|
+
painter.setFont(self.font())
|
133
|
+
painter.setPen(Qt.white if isDarkTheme() else Qt.black)
|
134
|
+
|
135
|
+
if self.isRoot():
|
136
|
+
rect = self.rect()
|
137
|
+
else:
|
138
|
+
rect = QRectF(sw, 0, self.width() - sw, self.height())
|
139
|
+
|
140
|
+
painter.drawText(rect, Qt.AlignVCenter | Qt.AlignLeft, self.text)
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
class BreadcrumbBar(QWidget):
|
145
|
+
""" Breadcrumb bar """
|
146
|
+
|
147
|
+
currentItemChanged = Signal(str)
|
148
|
+
currentIndexChanged = Signal(int)
|
149
|
+
|
150
|
+
def __init__(self, parent=None):
|
151
|
+
super().__init__(parent=parent)
|
152
|
+
self.itemMap = {} # type: Dict[BreadcrumbItem]
|
153
|
+
self.items = [] # type: List[BreadcrumbItem]
|
154
|
+
self.hiddenItems = [] # type: List[BreadcrumbItem]
|
155
|
+
|
156
|
+
self._spacing = 10
|
157
|
+
self._currentIndex = -1
|
158
|
+
|
159
|
+
self.elideButton = ElideButton(self)
|
160
|
+
|
161
|
+
setFont(self, 14)
|
162
|
+
self.setAttribute(Qt.WA_TranslucentBackground)
|
163
|
+
|
164
|
+
self.elideButton.hide()
|
165
|
+
self.elideButton.clicked.connect(self._showHiddenItemsMenu)
|
166
|
+
|
167
|
+
def addItem(self, routeKey: str, text: str):
|
168
|
+
""" add item
|
169
|
+
|
170
|
+
Parameters
|
171
|
+
----------
|
172
|
+
routeKey: str
|
173
|
+
unique key of item
|
174
|
+
|
175
|
+
text: str
|
176
|
+
the text of item
|
177
|
+
"""
|
178
|
+
if routeKey in self.itemMap:
|
179
|
+
return
|
180
|
+
|
181
|
+
item = BreadcrumbItem(routeKey, text, len(self.items), self)
|
182
|
+
item.setFont(self.font())
|
183
|
+
item.setSpacing(self.spacing)
|
184
|
+
item.clicked.connect(lambda: self.setCurrentItem(routeKey))
|
185
|
+
|
186
|
+
self.itemMap[routeKey] = item
|
187
|
+
self.items.append(item)
|
188
|
+
self.setFixedHeight(max(i.height() for i in self.items))
|
189
|
+
self.setCurrentItem(routeKey)
|
190
|
+
|
191
|
+
self.updateGeometry()
|
192
|
+
|
193
|
+
def setCurrentIndex(self, index: int):
|
194
|
+
if not 0 <= index < len(self.items) or index == self.currentIndex():
|
195
|
+
return
|
196
|
+
|
197
|
+
if 0<= self.currentIndex() < len(self.items):
|
198
|
+
self.currentItem().setSelected(False)
|
199
|
+
|
200
|
+
self._currentIndex = index
|
201
|
+
self.currentItem().setSelected(True)
|
202
|
+
|
203
|
+
# remove trailing items
|
204
|
+
for item in self.items[-1:index:-1]:
|
205
|
+
item = self.items.pop()
|
206
|
+
self.itemMap.pop(item.routeKey)
|
207
|
+
item.deleteLater()
|
208
|
+
|
209
|
+
self.updateGeometry()
|
210
|
+
|
211
|
+
self.currentIndexChanged.emit(index)
|
212
|
+
self.currentItemChanged.emit(self.currentItem().routeKey)
|
213
|
+
|
214
|
+
def setCurrentItem(self, routeKey: str):
|
215
|
+
if routeKey not in self.itemMap:
|
216
|
+
return
|
217
|
+
|
218
|
+
self.setCurrentIndex(self.items.index(self.itemMap[routeKey]))
|
219
|
+
|
220
|
+
def setItemText(self, routeKey: str, text: str):
|
221
|
+
item = self.item(routeKey)
|
222
|
+
if item:
|
223
|
+
item.setText(text)
|
224
|
+
|
225
|
+
def item(self, routeKey: str) -> BreadcrumbItem:
|
226
|
+
return self.itemMap.get(routeKey, None)
|
227
|
+
|
228
|
+
def itemAt(self, index: int):
|
229
|
+
if 0 <= index < len(self.items):
|
230
|
+
return self.items[index]
|
231
|
+
|
232
|
+
return None
|
233
|
+
|
234
|
+
def currentIndex(self):
|
235
|
+
return self._currentIndex
|
236
|
+
|
237
|
+
def currentItem(self) -> BreadcrumbItem:
|
238
|
+
if self.currentIndex() >= 0:
|
239
|
+
return self.items[self.currentIndex()]
|
240
|
+
|
241
|
+
return None
|
242
|
+
|
243
|
+
def resizeEvent(self, e):
|
244
|
+
self.updateGeometry()
|
245
|
+
|
246
|
+
def clear(self):
|
247
|
+
""" clear all items """
|
248
|
+
while self.items:
|
249
|
+
item = self.items.pop()
|
250
|
+
self.itemMap.pop(item.routeKey)
|
251
|
+
item.deleteLater()
|
252
|
+
|
253
|
+
self.elideButton.hide()
|
254
|
+
self._currentIndex = -1
|
255
|
+
|
256
|
+
def popItem(self):
|
257
|
+
""" pop trailing item """
|
258
|
+
if not self.items:
|
259
|
+
return
|
260
|
+
|
261
|
+
if self.count() >= 2:
|
262
|
+
self.setCurrentIndex(self.currentIndex() - 1)
|
263
|
+
else:
|
264
|
+
self.clear()
|
265
|
+
|
266
|
+
def count(self):
|
267
|
+
""" Returns the number of items """
|
268
|
+
return len(self.items)
|
269
|
+
|
270
|
+
def updateGeometry(self):
|
271
|
+
if not self.items:
|
272
|
+
return
|
273
|
+
|
274
|
+
x = 0
|
275
|
+
self.elideButton.hide()
|
276
|
+
self.hiddenItems = self.items[:-1].copy()
|
277
|
+
|
278
|
+
if not self.isElideVisible():
|
279
|
+
visibleItems = self.items
|
280
|
+
self.hiddenItems.clear()
|
281
|
+
else:
|
282
|
+
visibleItems = [self.elideButton, self.items[-1]]
|
283
|
+
w = sum(i.width() for i in visibleItems)
|
284
|
+
|
285
|
+
for item in self.items[-2::-1]:
|
286
|
+
w += item.width()
|
287
|
+
if w > self.width():
|
288
|
+
break
|
289
|
+
|
290
|
+
visibleItems.insert(1, item)
|
291
|
+
self.hiddenItems.remove(item)
|
292
|
+
|
293
|
+
for item in self.hiddenItems:
|
294
|
+
item.hide()
|
295
|
+
|
296
|
+
for item in visibleItems:
|
297
|
+
item.move(x, (self.height() - item.height()) // 2)
|
298
|
+
item.show()
|
299
|
+
x += item.width()
|
300
|
+
|
301
|
+
def isElideVisible(self):
|
302
|
+
w = sum(i.width() for i in self.items)
|
303
|
+
return w > self.width()
|
304
|
+
|
305
|
+
def setFont(self, font: QFont):
|
306
|
+
super().setFont(font)
|
307
|
+
|
308
|
+
s = int(font.pixelSize() / 14 * 16)
|
309
|
+
self.elideButton.setFixedSize(s, s)
|
310
|
+
|
311
|
+
for item in self.items:
|
312
|
+
item.setFont(font)
|
313
|
+
|
314
|
+
def _showHiddenItemsMenu(self):
|
315
|
+
self.elideButton.clearState()
|
316
|
+
|
317
|
+
menu = RoundMenu(parent=self)
|
318
|
+
menu.setItemHeight(32)
|
319
|
+
|
320
|
+
for item in self.hiddenItems:
|
321
|
+
menu.addAction(
|
322
|
+
QAction(item.text, menu, triggered=lambda i=item: self.setCurrentItem(i.routeKey)))
|
323
|
+
|
324
|
+
# determine the animation type by choosing the maximum height of view
|
325
|
+
x = -menu.layout().contentsMargins().left()
|
326
|
+
pd = self.mapToGlobal(QPoint(x, self.height()))
|
327
|
+
hd = menu.view.heightForAnimation(pd, MenuAnimationType.DROP_DOWN)
|
328
|
+
|
329
|
+
pu = self.mapToGlobal(QPoint(x, 0))
|
330
|
+
hu = menu.view.heightForAnimation(pu, MenuAnimationType.PULL_UP)
|
331
|
+
|
332
|
+
if hd >= hu:
|
333
|
+
menu.view.adjustSize(pd, MenuAnimationType.DROP_DOWN)
|
334
|
+
menu.exec(pd, aniType=MenuAnimationType.DROP_DOWN)
|
335
|
+
else:
|
336
|
+
menu.view.adjustSize(pu, MenuAnimationType.PULL_UP)
|
337
|
+
menu.exec(pu, aniType=MenuAnimationType.PULL_UP)
|
338
|
+
|
339
|
+
def getSpacing(self):
|
340
|
+
return self._spacing
|
341
|
+
|
342
|
+
def setSpacing(self, spacing: int):
|
343
|
+
if spacing == self._spacing:
|
344
|
+
return
|
345
|
+
|
346
|
+
self._spacing = spacing
|
347
|
+
for item in self.items:
|
348
|
+
item.setSpacing(spacing)
|
349
|
+
|
350
|
+
spacing = Property(int, getSpacing, setSpacing)
|