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,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