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,686 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from typing import Union, List
|
3
|
+
|
4
|
+
from PySide6.QtCore import (Qt, Signal, QRect, QRectF, QPropertyAnimation, Property, QMargins,
|
5
|
+
QEasingCurve, QPoint, QEvent)
|
6
|
+
from PySide6.QtGui import QColor, QPainter, QPen, QIcon, QCursor, QFont, QBrush, QPixmap, QImage
|
7
|
+
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
8
|
+
from collections import deque
|
9
|
+
|
10
|
+
from ...common.config import isDarkTheme
|
11
|
+
from ...common.style_sheet import themeColor
|
12
|
+
from ...common.icon import drawIcon, toQIcon
|
13
|
+
from ...common.icon import FluentIcon as FIF
|
14
|
+
from ...common.color import autoFallbackThemeColor
|
15
|
+
from ...common.font import setFont
|
16
|
+
from ..widgets.scroll_area import ScrollArea
|
17
|
+
from ..widgets.label import AvatarWidget
|
18
|
+
from ..widgets.info_badge import InfoBadgeManager, InfoBadgePosition
|
19
|
+
|
20
|
+
|
21
|
+
class NavigationWidget(QWidget):
|
22
|
+
""" Navigation widget """
|
23
|
+
|
24
|
+
clicked = Signal(bool) # whether triggered by the user
|
25
|
+
selectedChanged = Signal(bool)
|
26
|
+
EXPAND_WIDTH = 312
|
27
|
+
|
28
|
+
def __init__(self, isSelectable: bool, parent=None):
|
29
|
+
super().__init__(parent)
|
30
|
+
self.isCompacted = True
|
31
|
+
self.isSelected = False
|
32
|
+
self.isPressed = False
|
33
|
+
self.isEnter = False
|
34
|
+
self.isSelectable = isSelectable
|
35
|
+
self.treeParent = None
|
36
|
+
self.nodeDepth = 0
|
37
|
+
|
38
|
+
# text color
|
39
|
+
self.lightTextColor = QColor(0, 0, 0)
|
40
|
+
self.darkTextColor = QColor(255, 255, 255)
|
41
|
+
|
42
|
+
self.setFixedSize(40, 36)
|
43
|
+
|
44
|
+
def enterEvent(self, e):
|
45
|
+
self.isEnter = True
|
46
|
+
self.update()
|
47
|
+
|
48
|
+
def leaveEvent(self, e):
|
49
|
+
self.isEnter = False
|
50
|
+
self.isPressed = False
|
51
|
+
self.update()
|
52
|
+
|
53
|
+
def mousePressEvent(self, e):
|
54
|
+
super().mousePressEvent(e)
|
55
|
+
self.isPressed = True
|
56
|
+
self.update()
|
57
|
+
|
58
|
+
def mouseReleaseEvent(self, e):
|
59
|
+
super().mouseReleaseEvent(e)
|
60
|
+
self.isPressed = False
|
61
|
+
self.update()
|
62
|
+
self.clicked.emit(True)
|
63
|
+
|
64
|
+
def click(self):
|
65
|
+
self.clicked.emit(True)
|
66
|
+
|
67
|
+
def setCompacted(self, isCompacted: bool):
|
68
|
+
""" set whether the widget is compacted """
|
69
|
+
if isCompacted == self.isCompacted:
|
70
|
+
return
|
71
|
+
|
72
|
+
self.isCompacted = isCompacted
|
73
|
+
if isCompacted:
|
74
|
+
self.setFixedSize(40, 36)
|
75
|
+
else:
|
76
|
+
self.setFixedSize(self.EXPAND_WIDTH, 36)
|
77
|
+
|
78
|
+
self.update()
|
79
|
+
|
80
|
+
def setSelected(self, isSelected: bool):
|
81
|
+
""" set whether the button is selected
|
82
|
+
|
83
|
+
Parameters
|
84
|
+
----------
|
85
|
+
isSelected: bool
|
86
|
+
whether the button is selected
|
87
|
+
"""
|
88
|
+
if not self.isSelectable:
|
89
|
+
return
|
90
|
+
|
91
|
+
self.isSelected = isSelected
|
92
|
+
self.update()
|
93
|
+
self.selectedChanged.emit(isSelected)
|
94
|
+
|
95
|
+
def textColor(self):
|
96
|
+
return self.darkTextColor if isDarkTheme() else self.lightTextColor
|
97
|
+
|
98
|
+
def setLightTextColor(self, color):
|
99
|
+
""" set the text color in light theme mode """
|
100
|
+
self.lightTextColor = QColor(color)
|
101
|
+
self.update()
|
102
|
+
|
103
|
+
def setDarkTextColor(self, color):
|
104
|
+
""" set the text color in dark theme mode """
|
105
|
+
self.darkTextColor = QColor(color)
|
106
|
+
self.update()
|
107
|
+
|
108
|
+
def setTextColor(self, light, dark):
|
109
|
+
""" set the text color in light/dark theme mode """
|
110
|
+
self.setLightTextColor(light)
|
111
|
+
self.setDarkTextColor(dark)
|
112
|
+
|
113
|
+
|
114
|
+
class NavigationPushButton(NavigationWidget):
|
115
|
+
""" Navigation push button """
|
116
|
+
|
117
|
+
def __init__(self, icon: Union[str, QIcon, FIF], text: str, isSelectable: bool, parent=None):
|
118
|
+
"""
|
119
|
+
Parameters
|
120
|
+
----------
|
121
|
+
icon: str | QIcon | FluentIconBase
|
122
|
+
the icon to be drawn
|
123
|
+
|
124
|
+
text: str
|
125
|
+
the text of button
|
126
|
+
"""
|
127
|
+
super().__init__(isSelectable=isSelectable, parent=parent)
|
128
|
+
|
129
|
+
self._icon = icon
|
130
|
+
self._text = text
|
131
|
+
self.lightIndicatorColor = QColor()
|
132
|
+
self.darkIndicatorColor = QColor()
|
133
|
+
|
134
|
+
setFont(self)
|
135
|
+
|
136
|
+
def text(self):
|
137
|
+
return self._text
|
138
|
+
|
139
|
+
def setText(self, text: str):
|
140
|
+
self._text = text
|
141
|
+
self.update()
|
142
|
+
|
143
|
+
def icon(self):
|
144
|
+
return toQIcon(self._icon)
|
145
|
+
|
146
|
+
def setIcon(self, icon: Union[str, QIcon, FIF]):
|
147
|
+
self._icon = icon
|
148
|
+
self.update()
|
149
|
+
|
150
|
+
def _margins(self):
|
151
|
+
return QMargins(0, 0, 0, 0)
|
152
|
+
|
153
|
+
def _canDrawIndicator(self):
|
154
|
+
return self.isSelected
|
155
|
+
|
156
|
+
def setIndicatorColor(self, light, dark):
|
157
|
+
self.lightIndicatorColor = QColor(light)
|
158
|
+
self.darkIndicatorColor = QColor(dark)
|
159
|
+
self.update()
|
160
|
+
|
161
|
+
def paintEvent(self, e):
|
162
|
+
painter = QPainter(self)
|
163
|
+
painter.setRenderHints(QPainter.Antialiasing |
|
164
|
+
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
|
165
|
+
painter.setPen(Qt.NoPen)
|
166
|
+
|
167
|
+
if self.isPressed:
|
168
|
+
painter.setOpacity(0.7)
|
169
|
+
if not self.isEnabled():
|
170
|
+
painter.setOpacity(0.4)
|
171
|
+
|
172
|
+
# draw background
|
173
|
+
c = 255 if isDarkTheme() else 0
|
174
|
+
m = self._margins()
|
175
|
+
pl, pr = m.left(), m.right()
|
176
|
+
globalRect = QRect(self.mapToGlobal(QPoint()), self.size())
|
177
|
+
|
178
|
+
if self._canDrawIndicator():
|
179
|
+
painter.setBrush(QColor(c, c, c, 6 if self.isEnter else 10))
|
180
|
+
painter.drawRoundedRect(self.rect(), 5, 5)
|
181
|
+
|
182
|
+
# draw indicator
|
183
|
+
painter.setBrush(autoFallbackThemeColor(self.lightIndicatorColor, self.darkIndicatorColor))
|
184
|
+
painter.drawRoundedRect(pl, 10, 3, 16, 1.5, 1.5)
|
185
|
+
elif self.isEnter and self.isEnabled() and globalRect.contains(QCursor.pos()):
|
186
|
+
painter.setBrush(QColor(c, c, c, 10))
|
187
|
+
painter.drawRoundedRect(self.rect(), 5, 5)
|
188
|
+
|
189
|
+
drawIcon(self._icon, painter, QRectF(11.5+pl, 10, 16, 16))
|
190
|
+
|
191
|
+
# draw text
|
192
|
+
if self.isCompacted:
|
193
|
+
return
|
194
|
+
|
195
|
+
painter.setFont(self.font())
|
196
|
+
painter.setPen(self.textColor())
|
197
|
+
|
198
|
+
left = 44 + pl if not self.icon().isNull() else pl + 16
|
199
|
+
painter.drawText(QRectF(left, 0, self.width()-13-left-pr, self.height()), Qt.AlignVCenter, self.text())
|
200
|
+
|
201
|
+
|
202
|
+
class NavigationToolButton(NavigationPushButton):
|
203
|
+
""" Navigation tool button """
|
204
|
+
|
205
|
+
def __init__(self, icon: Union[str, QIcon, FIF], parent=None):
|
206
|
+
super().__init__(icon, '', False, parent)
|
207
|
+
|
208
|
+
def setCompacted(self, isCompacted: bool):
|
209
|
+
self.setFixedSize(40, 36)
|
210
|
+
|
211
|
+
|
212
|
+
class NavigationSeparator(NavigationWidget):
|
213
|
+
""" Navigation Separator """
|
214
|
+
|
215
|
+
def __init__(self, parent=None):
|
216
|
+
super().__init__(False, parent=parent)
|
217
|
+
self.setCompacted(True)
|
218
|
+
|
219
|
+
def setCompacted(self, isCompacted: bool):
|
220
|
+
if isCompacted:
|
221
|
+
self.setFixedSize(48, 3)
|
222
|
+
else:
|
223
|
+
self.setFixedSize(self.EXPAND_WIDTH + 10, 3)
|
224
|
+
|
225
|
+
self.update()
|
226
|
+
|
227
|
+
def paintEvent(self, e):
|
228
|
+
painter = QPainter(self)
|
229
|
+
c = 255 if isDarkTheme() else 0
|
230
|
+
pen = QPen(QColor(c, c, c, 15))
|
231
|
+
pen.setCosmetic(True)
|
232
|
+
painter.setPen(pen)
|
233
|
+
painter.drawLine(0, 1, self.width(), 1)
|
234
|
+
|
235
|
+
|
236
|
+
class NavigationTreeItem(NavigationPushButton):
|
237
|
+
""" Navigation tree item widget """
|
238
|
+
|
239
|
+
itemClicked = Signal(bool, bool) # triggerByUser, clickArrow
|
240
|
+
|
241
|
+
def __init__(self, icon: Union[str, QIcon, FIF], text: str, isSelectable: bool, parent=None):
|
242
|
+
super().__init__(icon, text, isSelectable, parent)
|
243
|
+
self._arrowAngle = 0
|
244
|
+
self.rotateAni = QPropertyAnimation(self, b'arrowAngle', self)
|
245
|
+
|
246
|
+
def setExpanded(self, isExpanded: bool):
|
247
|
+
self.rotateAni.stop()
|
248
|
+
self.rotateAni.setEndValue(180 if isExpanded else 0)
|
249
|
+
self.rotateAni.setDuration(150)
|
250
|
+
self.rotateAni.start()
|
251
|
+
|
252
|
+
def mouseReleaseEvent(self, e):
|
253
|
+
super().mouseReleaseEvent(e)
|
254
|
+
clickArrow = QRectF(self.width()-30, 8, 20, 20).contains(e.pos())
|
255
|
+
self.itemClicked.emit(True, clickArrow and not self.parent().isLeaf())
|
256
|
+
self.update()
|
257
|
+
|
258
|
+
def _canDrawIndicator(self):
|
259
|
+
p = self.parent() # type: NavigationTreeWidget
|
260
|
+
if p.isLeaf() or p.isSelected:
|
261
|
+
return p.isSelected
|
262
|
+
|
263
|
+
for child in p.treeChildren:
|
264
|
+
if child.itemWidget._canDrawIndicator() and not child.isVisible():
|
265
|
+
return True
|
266
|
+
|
267
|
+
return False
|
268
|
+
|
269
|
+
def _margins(self):
|
270
|
+
p = self.parent() # type: NavigationTreeWidget
|
271
|
+
return QMargins(p.nodeDepth*28, 0, 20*bool(p.treeChildren), 0)
|
272
|
+
|
273
|
+
def paintEvent(self, e):
|
274
|
+
super().paintEvent(e)
|
275
|
+
if self.isCompacted or not self.parent().treeChildren:
|
276
|
+
return
|
277
|
+
|
278
|
+
painter = QPainter(self)
|
279
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
280
|
+
painter.setPen(Qt.NoPen)
|
281
|
+
|
282
|
+
if self.isPressed:
|
283
|
+
painter.setOpacity(0.7)
|
284
|
+
if not self.isEnabled():
|
285
|
+
painter.setOpacity(0.4)
|
286
|
+
|
287
|
+
painter.translate(self.width() - 20, 18)
|
288
|
+
painter.rotate(self.arrowAngle)
|
289
|
+
FIF.ARROW_DOWN.render(painter, QRectF(-5, -5, 9.6, 9.6))
|
290
|
+
|
291
|
+
def getArrowAngle(self):
|
292
|
+
return self._arrowAngle
|
293
|
+
|
294
|
+
def setArrowAngle(self, angle):
|
295
|
+
self._arrowAngle = angle
|
296
|
+
self.update()
|
297
|
+
|
298
|
+
arrowAngle = Property(float, getArrowAngle, setArrowAngle)
|
299
|
+
|
300
|
+
|
301
|
+
class NavigationTreeWidgetBase(NavigationWidget):
|
302
|
+
""" Navigation tree widget base class """
|
303
|
+
|
304
|
+
def addChild(self, child):
|
305
|
+
""" add child
|
306
|
+
|
307
|
+
Parameters
|
308
|
+
----------
|
309
|
+
child: NavigationTreeWidgetBase
|
310
|
+
child item
|
311
|
+
"""
|
312
|
+
raise NotImplementedError
|
313
|
+
|
314
|
+
def insertChild(self, index: int, child: NavigationWidget):
|
315
|
+
""" insert child
|
316
|
+
|
317
|
+
Parameters
|
318
|
+
----------
|
319
|
+
child: NavigationTreeWidgetBase
|
320
|
+
child item
|
321
|
+
"""
|
322
|
+
raise NotImplementedError
|
323
|
+
|
324
|
+
def removeChild(self, child: NavigationWidget):
|
325
|
+
""" remove child
|
326
|
+
|
327
|
+
Parameters
|
328
|
+
----------
|
329
|
+
child: NavigationTreeWidgetBase
|
330
|
+
child item
|
331
|
+
"""
|
332
|
+
raise NotImplementedError
|
333
|
+
|
334
|
+
def isRoot(self):
|
335
|
+
""" is root node """
|
336
|
+
return True
|
337
|
+
|
338
|
+
def isLeaf(self):
|
339
|
+
""" is leaf node """
|
340
|
+
return True
|
341
|
+
|
342
|
+
def setExpanded(self, isExpanded: bool):
|
343
|
+
""" set the expanded status
|
344
|
+
|
345
|
+
Parameters
|
346
|
+
----------
|
347
|
+
isExpanded: bool
|
348
|
+
whether to expand node
|
349
|
+
"""
|
350
|
+
raise NotImplementedError
|
351
|
+
|
352
|
+
def childItems(self) -> list:
|
353
|
+
""" return child items """
|
354
|
+
raise NotImplementedError
|
355
|
+
|
356
|
+
|
357
|
+
class NavigationTreeWidget(NavigationTreeWidgetBase):
|
358
|
+
""" Navigation tree widget """
|
359
|
+
|
360
|
+
expanded = Signal()
|
361
|
+
|
362
|
+
def __init__(self, icon: Union[str, QIcon, FIF], text: str, isSelectable: bool, parent=None):
|
363
|
+
super().__init__(isSelectable, parent)
|
364
|
+
|
365
|
+
self.treeChildren = [] # type: List[NavigationTreeWidget]
|
366
|
+
self.isExpanded = False
|
367
|
+
self._icon = icon
|
368
|
+
|
369
|
+
self.itemWidget = NavigationTreeItem(icon, text, isSelectable, self)
|
370
|
+
self.vBoxLayout = QVBoxLayout(self)
|
371
|
+
self.expandAni = QPropertyAnimation(self, b'geometry', self)
|
372
|
+
|
373
|
+
self.__initWidget()
|
374
|
+
|
375
|
+
def __initWidget(self):
|
376
|
+
self.vBoxLayout.setSpacing(4)
|
377
|
+
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
|
378
|
+
self.vBoxLayout.addWidget(self.itemWidget, 0, Qt.AlignTop)
|
379
|
+
|
380
|
+
self.itemWidget.itemClicked.connect(self._onClicked)
|
381
|
+
self.setAttribute(Qt.WA_TranslucentBackground)
|
382
|
+
self.expandAni.valueChanged.connect(lambda g: self.setFixedSize(g.size()))
|
383
|
+
self.expandAni.valueChanged.connect(self.expanded)
|
384
|
+
self.expandAni.finished.connect(self.parentWidget().layout().invalidate)
|
385
|
+
|
386
|
+
def addChild(self, child):
|
387
|
+
self.insertChild(-1, child)
|
388
|
+
|
389
|
+
def text(self):
|
390
|
+
return self.itemWidget.text()
|
391
|
+
|
392
|
+
def icon(self):
|
393
|
+
return self.itemWidget.icon()
|
394
|
+
|
395
|
+
def setText(self, text):
|
396
|
+
self.itemWidget.setText(text)
|
397
|
+
|
398
|
+
def setIcon(self, icon: Union[str, QIcon, FIF]):
|
399
|
+
self.itemWidget.setIcon(icon)
|
400
|
+
|
401
|
+
def textColor(self):
|
402
|
+
return self.itemWidget.textColor()
|
403
|
+
|
404
|
+
def setLightTextColor(self, color):
|
405
|
+
""" set the text color in light theme mode """
|
406
|
+
self.itemWidget.setLightTextColor(color)
|
407
|
+
|
408
|
+
def setDarkTextColor(self, color):
|
409
|
+
""" set the text color in dark theme mode """
|
410
|
+
self.itemWidget.setDarkTextColor(color)
|
411
|
+
|
412
|
+
def setTextColor(self, light, dark):
|
413
|
+
""" set the text color in light/dark theme mode """
|
414
|
+
self.lightTextColor = QColor(light)
|
415
|
+
self.darkTextColor = QColor(dark)
|
416
|
+
self.itemWidget.setTextColor(light, dark)
|
417
|
+
|
418
|
+
def setIndicatorColor(self, light, dark):
|
419
|
+
""" set the indicator color in light/dark theme mode """
|
420
|
+
self.itemWidget.setIndicatorColor(light, dark)
|
421
|
+
|
422
|
+
def setFont(self, font: QFont):
|
423
|
+
super().setFont(font)
|
424
|
+
self.itemWidget.setFont(font)
|
425
|
+
|
426
|
+
def clone(self):
|
427
|
+
root = NavigationTreeWidget(self._icon, self.text(), self.isSelectable, self.parent())
|
428
|
+
root.setSelected(self.isSelected)
|
429
|
+
root.setFixedSize(self.size())
|
430
|
+
root.setTextColor(self.lightTextColor, self.darkTextColor)
|
431
|
+
root.setIndicatorColor(self.itemWidget.lightIndicatorColor, self.itemWidget.darkIndicatorColor)
|
432
|
+
root.nodeDepth = self.nodeDepth
|
433
|
+
|
434
|
+
root.clicked.connect(self.clicked)
|
435
|
+
self.selectedChanged.connect(root.setSelected)
|
436
|
+
|
437
|
+
for child in self.treeChildren:
|
438
|
+
root.addChild(child.clone())
|
439
|
+
|
440
|
+
return root
|
441
|
+
|
442
|
+
def suitableWidth(self):
|
443
|
+
m = self.itemWidget._margins()
|
444
|
+
left = 57 + m.left() if not self.icon().isNull() else m.left() + 29
|
445
|
+
tw = self.itemWidget.fontMetrics().boundingRect(self.text()).width()
|
446
|
+
return left + tw + m.right()
|
447
|
+
|
448
|
+
def insertChild(self, index, child):
|
449
|
+
if child in self.treeChildren:
|
450
|
+
return
|
451
|
+
|
452
|
+
child.treeParent = self
|
453
|
+
child.nodeDepth = self.nodeDepth + 1
|
454
|
+
child.setVisible(self.isExpanded)
|
455
|
+
child.expandAni.valueChanged.connect(lambda: self.setFixedSize(self.sizeHint()))
|
456
|
+
child.expandAni.valueChanged.connect(self.expanded)
|
457
|
+
|
458
|
+
# connect height changed signal to parent recursively
|
459
|
+
p = self.treeParent
|
460
|
+
while p:
|
461
|
+
child.expandAni.valueChanged.connect(lambda v, p=p: p.setFixedSize(p.sizeHint()))
|
462
|
+
p = p.treeParent
|
463
|
+
|
464
|
+
if index < 0:
|
465
|
+
index = len(self.treeChildren)
|
466
|
+
|
467
|
+
index += 1 # item widget should always be the first
|
468
|
+
self.treeChildren.insert(index, child)
|
469
|
+
self.vBoxLayout.insertWidget(index, child, 0, Qt.AlignTop)
|
470
|
+
|
471
|
+
def removeChild(self, child):
|
472
|
+
self.treeChildren.remove(child)
|
473
|
+
self.vBoxLayout.removeWidget(child)
|
474
|
+
|
475
|
+
def childItems(self) -> list:
|
476
|
+
return self.treeChildren
|
477
|
+
|
478
|
+
def setExpanded(self, isExpanded: bool, ani=False):
|
479
|
+
""" set the expanded status """
|
480
|
+
if isExpanded == self.isExpanded:
|
481
|
+
return
|
482
|
+
|
483
|
+
self.isExpanded = isExpanded
|
484
|
+
self.itemWidget.setExpanded(isExpanded)
|
485
|
+
|
486
|
+
for child in self.treeChildren:
|
487
|
+
child.setVisible(isExpanded)
|
488
|
+
child.setFixedSize(child.sizeHint())
|
489
|
+
|
490
|
+
if ani:
|
491
|
+
self.expandAni.stop()
|
492
|
+
self.expandAni.setStartValue(self.geometry())
|
493
|
+
self.expandAni.setEndValue(QRect(self.pos(), self.sizeHint()))
|
494
|
+
self.expandAni.setDuration(120)
|
495
|
+
self.expandAni.setEasingCurve(QEasingCurve.OutQuad)
|
496
|
+
self.expandAni.start()
|
497
|
+
else:
|
498
|
+
self.setFixedSize(self.sizeHint())
|
499
|
+
|
500
|
+
def isRoot(self):
|
501
|
+
return self.treeParent is None
|
502
|
+
|
503
|
+
def isLeaf(self):
|
504
|
+
return len(self.treeChildren) == 0
|
505
|
+
|
506
|
+
def setSelected(self, isSelected: bool):
|
507
|
+
super().setSelected(isSelected)
|
508
|
+
self.itemWidget.setSelected(isSelected)
|
509
|
+
|
510
|
+
def mouseReleaseEvent(self, e):
|
511
|
+
pass
|
512
|
+
|
513
|
+
def setCompacted(self, isCompacted: bool):
|
514
|
+
super().setCompacted(isCompacted)
|
515
|
+
self.itemWidget.setCompacted(isCompacted)
|
516
|
+
|
517
|
+
def _onClicked(self, triggerByUser, clickArrow):
|
518
|
+
if not self.isCompacted:
|
519
|
+
if self.isSelectable and not self.isSelected and not clickArrow:
|
520
|
+
self.setExpanded(True, ani=True)
|
521
|
+
else:
|
522
|
+
self.setExpanded(not self.isExpanded, ani=True)
|
523
|
+
|
524
|
+
if not clickArrow or self.isCompacted:
|
525
|
+
self.clicked.emit(triggerByUser)
|
526
|
+
|
527
|
+
|
528
|
+
class NavigationAvatarWidget(NavigationWidget):
|
529
|
+
""" Avatar widget """
|
530
|
+
|
531
|
+
def __init__(self, name: str, avatar: Union[str, QPixmap, QImage] = None, parent=None):
|
532
|
+
super().__init__(isSelectable=False, parent=parent)
|
533
|
+
self.name = name
|
534
|
+
self.avatar = AvatarWidget(self)
|
535
|
+
|
536
|
+
self.avatar.setRadius(12)
|
537
|
+
self.avatar.setText(name)
|
538
|
+
self.avatar.move(8, 6)
|
539
|
+
setFont(self)
|
540
|
+
|
541
|
+
if avatar:
|
542
|
+
self.setAvatar(avatar)
|
543
|
+
|
544
|
+
def setName(self, name: str):
|
545
|
+
self.name = name
|
546
|
+
self.avatar.setText(name)
|
547
|
+
self.update()
|
548
|
+
|
549
|
+
def setAvatar(self, avatar: Union[str, QPixmap, QImage]):
|
550
|
+
self.avatar.setImage(avatar)
|
551
|
+
self.avatar.setRadius(12)
|
552
|
+
self.update()
|
553
|
+
|
554
|
+
def paintEvent(self, e):
|
555
|
+
painter = QPainter(self)
|
556
|
+
painter.setRenderHints(
|
557
|
+
QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
|
558
|
+
|
559
|
+
painter.setPen(Qt.NoPen)
|
560
|
+
|
561
|
+
if self.isPressed:
|
562
|
+
painter.setOpacity(0.7)
|
563
|
+
|
564
|
+
# draw background
|
565
|
+
if self.isEnter:
|
566
|
+
c = 255 if isDarkTheme() else 0
|
567
|
+
painter.setBrush(QColor(c, c, c, 10))
|
568
|
+
painter.drawRoundedRect(self.rect(), 5, 5)
|
569
|
+
|
570
|
+
if not self.isCompacted:
|
571
|
+
painter.setPen(self.textColor())
|
572
|
+
painter.setFont(self.font())
|
573
|
+
painter.drawText(QRect(44, 0, 255, 36), Qt.AlignVCenter, self.name)
|
574
|
+
|
575
|
+
|
576
|
+
@InfoBadgeManager.register(InfoBadgePosition.NAVIGATION_ITEM)
|
577
|
+
class NavigationItemInfoBadgeManager(InfoBadgeManager):
|
578
|
+
""" Navigation item info badge manager """
|
579
|
+
|
580
|
+
def eventFilter(self, obj, e: QEvent):
|
581
|
+
if obj is self.target:
|
582
|
+
if e.type() == QEvent.Show:
|
583
|
+
self.badge.show()
|
584
|
+
|
585
|
+
return super().eventFilter(obj, e)
|
586
|
+
|
587
|
+
def position(self):
|
588
|
+
target = self.target
|
589
|
+
self.badge.setVisible(target.isVisible())
|
590
|
+
|
591
|
+
if target.isCompacted:
|
592
|
+
return target.geometry().topRight() - QPoint(self.badge.width() + 2, -2)
|
593
|
+
|
594
|
+
if isinstance(target, NavigationTreeWidget):
|
595
|
+
dx = 10 if target.isLeaf() else 35
|
596
|
+
x = target.geometry().right() - self.badge.width() - dx
|
597
|
+
y = target.y() + 18 - self.badge.height() // 2
|
598
|
+
else:
|
599
|
+
x = target.geometry().right() - self.badge.width() - 10
|
600
|
+
y = target.geometry().center().y() - self.badge.height() // 2
|
601
|
+
|
602
|
+
return QPoint(x, y)
|
603
|
+
|
604
|
+
|
605
|
+
class NavigationFlyoutMenu(ScrollArea):
|
606
|
+
""" Navigation flyout menu """
|
607
|
+
|
608
|
+
expanded = Signal()
|
609
|
+
|
610
|
+
def __init__(self, tree: NavigationTreeWidget, parent=None):
|
611
|
+
super().__init__(parent)
|
612
|
+
self.view = QWidget(self)
|
613
|
+
|
614
|
+
self.treeWidget = tree
|
615
|
+
self.treeChildren = []
|
616
|
+
|
617
|
+
self.vBoxLayout = QVBoxLayout(self.view)
|
618
|
+
|
619
|
+
self.setWidget(self.view)
|
620
|
+
self.setWidgetResizable(True)
|
621
|
+
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
622
|
+
self.setStyleSheet("ScrollArea{border:none;background:transparent}")
|
623
|
+
self.view.setStyleSheet("QWidget{border:none;background:transparent}")
|
624
|
+
|
625
|
+
self.vBoxLayout.setSpacing(5)
|
626
|
+
self.vBoxLayout.setContentsMargins(5, 8, 5, 8)
|
627
|
+
|
628
|
+
# add nodes to menu
|
629
|
+
for child in tree.treeChildren:
|
630
|
+
node = child.clone()
|
631
|
+
node.expanded.connect(self._adjustViewSize)
|
632
|
+
|
633
|
+
self.treeChildren.append(node)
|
634
|
+
self.vBoxLayout.addWidget(node)
|
635
|
+
|
636
|
+
self._initNode(self)
|
637
|
+
self._adjustViewSize(False)
|
638
|
+
|
639
|
+
def _initNode(self, root: NavigationTreeWidget):
|
640
|
+
for c in root.treeChildren:
|
641
|
+
c.nodeDepth -= 1
|
642
|
+
c.setCompacted(False)
|
643
|
+
|
644
|
+
if c.isLeaf():
|
645
|
+
c.clicked.connect(self.window().fadeOut)
|
646
|
+
|
647
|
+
self._initNode(c)
|
648
|
+
|
649
|
+
def _adjustViewSize(self, emit=True):
|
650
|
+
w = self._suitableWidth()
|
651
|
+
|
652
|
+
# adjust the width of node
|
653
|
+
for node in self.visibleTreeNodes():
|
654
|
+
node.setFixedWidth(w - 10)
|
655
|
+
node.itemWidget.setFixedWidth(w - 10)
|
656
|
+
|
657
|
+
self.view.setFixedSize(w, self.view.sizeHint().height())
|
658
|
+
|
659
|
+
h = min(self.window().parent().height() - 48, self.view.height())
|
660
|
+
|
661
|
+
self.setFixedSize(w, h)
|
662
|
+
|
663
|
+
if emit:
|
664
|
+
self.expanded.emit()
|
665
|
+
|
666
|
+
def _suitableWidth(self):
|
667
|
+
w = 0
|
668
|
+
|
669
|
+
for node in self.visibleTreeNodes():
|
670
|
+
if not node.isHidden():
|
671
|
+
w = max(w, node.suitableWidth() + 10)
|
672
|
+
|
673
|
+
window = self.window().parent() # type: QWidget
|
674
|
+
return min(window.width() // 2 - 25, w) + 10
|
675
|
+
|
676
|
+
def visibleTreeNodes(self):
|
677
|
+
nodes = []
|
678
|
+
queue = deque()
|
679
|
+
queue.extend(self.treeChildren)
|
680
|
+
|
681
|
+
while queue:
|
682
|
+
node = queue.popleft() # type: NavigationTreeWidget
|
683
|
+
nodes.append(node)
|
684
|
+
queue.extend([i for i in node.treeChildren if not i.isHidden()])
|
685
|
+
|
686
|
+
return nodes
|