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.
Files changed (107) hide show
  1. fastuiwidgets/__init__.py +12 -0
  2. fastuiwidgets/_rc/__init__.py +0 -0
  3. fastuiwidgets/_rc/resource.py +98835 -0
  4. fastuiwidgets/common/__init__.py +12 -0
  5. fastuiwidgets/common/animation.py +530 -0
  6. fastuiwidgets/common/auto_wrap.py +164 -0
  7. fastuiwidgets/common/color.py +95 -0
  8. fastuiwidgets/common/config.py +423 -0
  9. fastuiwidgets/common/exception_handler.py +31 -0
  10. fastuiwidgets/common/font.py +38 -0
  11. fastuiwidgets/common/icon.py +703 -0
  12. fastuiwidgets/common/image_utils.py +198 -0
  13. fastuiwidgets/common/overload.py +47 -0
  14. fastuiwidgets/common/router.py +133 -0
  15. fastuiwidgets/common/screen.py +25 -0
  16. fastuiwidgets/common/smooth_scroll.py +141 -0
  17. fastuiwidgets/common/style_sheet.py +512 -0
  18. fastuiwidgets/common/theme_listener.py +27 -0
  19. fastuiwidgets/common/translator.py +14 -0
  20. fastuiwidgets/components/__init__.py +6 -0
  21. fastuiwidgets/components/date_time/__init__.py +4 -0
  22. fastuiwidgets/components/date_time/calendar_picker.py +121 -0
  23. fastuiwidgets/components/date_time/calendar_view.py +671 -0
  24. fastuiwidgets/components/date_time/date_picker.py +245 -0
  25. fastuiwidgets/components/date_time/fast_calendar_view.py +487 -0
  26. fastuiwidgets/components/date_time/picker_base.py +632 -0
  27. fastuiwidgets/components/date_time/time_picker.py +223 -0
  28. fastuiwidgets/components/dialog_box/__init__.py +6 -0
  29. fastuiwidgets/components/dialog_box/color_dialog.py +414 -0
  30. fastuiwidgets/components/dialog_box/dialog.py +167 -0
  31. fastuiwidgets/components/dialog_box/folder_list_dialog.py +307 -0
  32. fastuiwidgets/components/dialog_box/mask_dialog_base.py +120 -0
  33. fastuiwidgets/components/dialog_box/message_box_base.py +92 -0
  34. fastuiwidgets/components/dialog_box/message_dialog.py +65 -0
  35. fastuiwidgets/components/layout/__init__.py +3 -0
  36. fastuiwidgets/components/layout/expand_layout.py +96 -0
  37. fastuiwidgets/components/layout/flow_layout.py +236 -0
  38. fastuiwidgets/components/layout/v_box_layout.py +41 -0
  39. fastuiwidgets/components/material/__init__.py +6 -0
  40. fastuiwidgets/components/material/acrylic_combo_box.py +96 -0
  41. fastuiwidgets/components/material/acrylic_flyout.py +105 -0
  42. fastuiwidgets/components/material/acrylic_line_edit.py +27 -0
  43. fastuiwidgets/components/material/acrylic_menu.py +204 -0
  44. fastuiwidgets/components/material/acrylic_tool_tip.py +39 -0
  45. fastuiwidgets/components/material/acrylic_widget.py +42 -0
  46. fastuiwidgets/components/navigation/__init__.py +9 -0
  47. fastuiwidgets/components/navigation/breadcrumb.py +350 -0
  48. fastuiwidgets/components/navigation/navigation_bar.py +416 -0
  49. fastuiwidgets/components/navigation/navigation_interface.py +268 -0
  50. fastuiwidgets/components/navigation/navigation_panel.py +657 -0
  51. fastuiwidgets/components/navigation/navigation_widget.py +686 -0
  52. fastuiwidgets/components/navigation/pivot.py +272 -0
  53. fastuiwidgets/components/navigation/segmented_widget.py +174 -0
  54. fastuiwidgets/components/settings/__init__.py +8 -0
  55. fastuiwidgets/components/settings/custom_color_setting_card.py +139 -0
  56. fastuiwidgets/components/settings/expand_setting_card.py +390 -0
  57. fastuiwidgets/components/settings/folder_list_setting_card.py +134 -0
  58. fastuiwidgets/components/settings/options_setting_card.py +86 -0
  59. fastuiwidgets/components/settings/setting_card.py +449 -0
  60. fastuiwidgets/components/settings/setting_card_group.py +48 -0
  61. fastuiwidgets/components/widgets/__init__.py +41 -0
  62. fastuiwidgets/components/widgets/acrylic_label.py +261 -0
  63. fastuiwidgets/components/widgets/button.py +1059 -0
  64. fastuiwidgets/components/widgets/card_widget.py +369 -0
  65. fastuiwidgets/components/widgets/check_box.py +203 -0
  66. fastuiwidgets/components/widgets/combo_box.py +556 -0
  67. fastuiwidgets/components/widgets/command_bar.py +636 -0
  68. fastuiwidgets/components/widgets/cycle_list_widget.py +251 -0
  69. fastuiwidgets/components/widgets/flip_view.py +430 -0
  70. fastuiwidgets/components/widgets/flyout.py +521 -0
  71. fastuiwidgets/components/widgets/frameless_window.py +49 -0
  72. fastuiwidgets/components/widgets/icon_widget.py +53 -0
  73. fastuiwidgets/components/widgets/info_badge.py +483 -0
  74. fastuiwidgets/components/widgets/info_bar.py +596 -0
  75. fastuiwidgets/components/widgets/label.py +553 -0
  76. fastuiwidgets/components/widgets/line_edit.py +551 -0
  77. fastuiwidgets/components/widgets/list_view.py +158 -0
  78. fastuiwidgets/components/widgets/menu.py +1318 -0
  79. fastuiwidgets/components/widgets/pips_pager.py +331 -0
  80. fastuiwidgets/components/widgets/progress_bar.py +311 -0
  81. fastuiwidgets/components/widgets/progress_ring.py +212 -0
  82. fastuiwidgets/components/widgets/scroll_area.py +125 -0
  83. fastuiwidgets/components/widgets/scroll_bar.py +673 -0
  84. fastuiwidgets/components/widgets/separator.py +43 -0
  85. fastuiwidgets/components/widgets/slider.py +307 -0
  86. fastuiwidgets/components/widgets/spin_box.py +306 -0
  87. fastuiwidgets/components/widgets/stacked_widget.py +211 -0
  88. fastuiwidgets/components/widgets/state_tool_tip.py +188 -0
  89. fastuiwidgets/components/widgets/switch_button.py +312 -0
  90. fastuiwidgets/components/widgets/tab_view.py +804 -0
  91. fastuiwidgets/components/widgets/table_view.py +360 -0
  92. fastuiwidgets/components/widgets/teaching_tip.py +657 -0
  93. fastuiwidgets/components/widgets/tool_tip.py +460 -0
  94. fastuiwidgets/components/widgets/tree_view.py +216 -0
  95. fastuiwidgets/multimedia/__init__.py +3 -0
  96. fastuiwidgets/multimedia/media_play_bar.py +319 -0
  97. fastuiwidgets/multimedia/media_player.py +124 -0
  98. fastuiwidgets/multimedia/video_widget.py +93 -0
  99. fastuiwidgets/window/__init__.py +2 -0
  100. fastuiwidgets/window/fluent_window.py +413 -0
  101. fastuiwidgets/window/splash_screen.py +92 -0
  102. fastuiwidgets/window/stacked_widget.py +66 -0
  103. python_fastui_widgets-1.0.0.dist-info/METADATA +30 -0
  104. python_fastui_widgets-1.0.0.dist-info/RECORD +107 -0
  105. python_fastui_widgets-1.0.0.dist-info/WHEEL +5 -0
  106. python_fastui_widgets-1.0.0.dist-info/licenses/LICENSE +674 -0
  107. 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)