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,657 @@
1
+ # coding:utf-8
2
+ from enum import Enum
3
+ from typing import Dict, Union
4
+
5
+ from PySide6.QtCore import Qt, QPropertyAnimation, QRect, QSize, QEvent, QEasingCurve, Signal, QPoint
6
+ from PySide6.QtGui import QResizeEvent, QIcon, QColor, QPainterPath
7
+ from PySide6.QtWidgets import QWidget, QVBoxLayout, QFrame, QApplication, QHBoxLayout
8
+
9
+ from .navigation_widget import (NavigationTreeWidgetBase, NavigationToolButton, NavigationWidget, NavigationSeparator,
10
+ NavigationTreeWidget, NavigationFlyoutMenu)
11
+ from ..widgets.acrylic_label import AcrylicBrush
12
+ from ..widgets.scroll_area import ScrollArea
13
+ from ..widgets.tool_tip import ToolTipFilter
14
+ from ..widgets.flyout import Flyout, FlyoutAnimationType, FlyoutViewBase, SlideRightFlyoutAnimationManager
15
+ from ..material.acrylic_flyout import AcrylicFlyout, AcrylicFlyoutViewBase
16
+ from ...common.router import qrouter
17
+ from ...common.style_sheet import FluentStyleSheet, isDarkTheme
18
+ from ...common.icon import FluentIconBase
19
+ from ...common.icon import FluentIcon as FIF
20
+
21
+
22
+ class NavigationDisplayMode(Enum):
23
+ """ Navigation display mode """
24
+ MINIMAL = 0
25
+ COMPACT = 1
26
+ EXPAND = 2
27
+ MENU = 3
28
+
29
+
30
+ class NavigationItemPosition(Enum):
31
+ """ Navigation item position """
32
+ TOP = 0
33
+ SCROLL = 1
34
+ BOTTOM = 2
35
+
36
+
37
+ class NavigationToolTipFilter(ToolTipFilter):
38
+ """ Navigation tool tip filter """
39
+
40
+ def _canShowToolTip(self) -> bool:
41
+ isVisible = super()._canShowToolTip()
42
+ parent = self.parent() # type: NavigationWidget
43
+ return isVisible and parent.isCompacted
44
+
45
+
46
+ class RouteKeyError(Exception):
47
+ """ Route key error """
48
+
49
+
50
+ class NavigationItem:
51
+ """ Navigation item """
52
+
53
+ def __init__(self, routeKey: str, parentRouteKey: str, widget: NavigationWidget):
54
+ self.routeKey = routeKey
55
+ self.parentRouteKey = parentRouteKey
56
+ self.widget = widget
57
+
58
+
59
+ class NavigationPanel(QFrame):
60
+ """ Navigation panel """
61
+
62
+ displayModeChanged = Signal(NavigationDisplayMode)
63
+
64
+ def __init__(self, parent=None, isMinimalEnabled=False):
65
+ super().__init__(parent=parent)
66
+ self._parent = parent # type: QWidget
67
+ self._isMenuButtonVisible = True
68
+ self._isReturnButtonVisible = False
69
+ self._isCollapsible = True
70
+ self._isAcrylicEnabled = False
71
+
72
+ self.acrylicBrush = AcrylicBrush(self, 30)
73
+
74
+ self.scrollArea = ScrollArea(self)
75
+ self.scrollWidget = QWidget()
76
+
77
+ self.menuButton = NavigationToolButton(FIF.MENU, self)
78
+ self.returnButton = NavigationToolButton(FIF.RETURN, self)
79
+
80
+ self.vBoxLayout = NavigationItemLayout(self)
81
+ self.topLayout = NavigationItemLayout()
82
+ self.bottomLayout = NavigationItemLayout()
83
+ self.scrollLayout = NavigationItemLayout(self.scrollWidget)
84
+
85
+ self.items = {} # type: Dict[str, NavigationItem]
86
+ self.history = qrouter
87
+
88
+ self.expandAni = QPropertyAnimation(self, b'geometry', self)
89
+ self.expandWidth = 322
90
+ self.minimumExpandWidth = 1008
91
+
92
+ self.isMinimalEnabled = isMinimalEnabled
93
+ if isMinimalEnabled:
94
+ self.displayMode = NavigationDisplayMode.MINIMAL
95
+ else:
96
+ self.displayMode = NavigationDisplayMode.COMPACT
97
+
98
+ self.__initWidget()
99
+
100
+ def __initWidget(self):
101
+ self.resize(48, self.height())
102
+ self.setAttribute(Qt.WA_StyledBackground)
103
+ self.window().installEventFilter(self)
104
+
105
+ self.returnButton.hide()
106
+ self.returnButton.setDisabled(True)
107
+
108
+ self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
109
+ self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
110
+ self.scrollArea.horizontalScrollBar().setEnabled(False)
111
+ self.scrollArea.setWidget(self.scrollWidget)
112
+ self.scrollArea.setWidgetResizable(True)
113
+
114
+ self.expandAni.setEasingCurve(QEasingCurve.OutQuad)
115
+ self.expandAni.setDuration(150)
116
+
117
+ self.menuButton.clicked.connect(self.toggle)
118
+ self.expandAni.finished.connect(self._onExpandAniFinished)
119
+ self.history.emptyChanged.connect(self.returnButton.setDisabled)
120
+ self.returnButton.clicked.connect(self.history.pop)
121
+
122
+ # add tool tip
123
+ self.returnButton.installEventFilter(ToolTipFilter(self.returnButton, 1000))
124
+ self.returnButton.setToolTip(self.tr('Back'))
125
+
126
+ self.menuButton.installEventFilter(ToolTipFilter(self.menuButton, 1000))
127
+ self.menuButton.setToolTip(self.tr('Open Navigation'))
128
+
129
+ self.scrollWidget.setObjectName('scrollWidget')
130
+ self.setProperty('menu', False)
131
+ FluentStyleSheet.NAVIGATION_INTERFACE.apply(self)
132
+ FluentStyleSheet.NAVIGATION_INTERFACE.apply(self.scrollWidget)
133
+ self.__initLayout()
134
+
135
+ def __initLayout(self):
136
+ self.vBoxLayout.setContentsMargins(0, 5, 0, 5)
137
+ self.topLayout.setContentsMargins(4, 0, 4, 0)
138
+ self.bottomLayout.setContentsMargins(4, 0, 4, 0)
139
+ self.scrollLayout.setContentsMargins(4, 0, 4, 0)
140
+ self.vBoxLayout.setSpacing(4)
141
+ self.topLayout.setSpacing(4)
142
+ self.bottomLayout.setSpacing(4)
143
+ self.scrollLayout.setSpacing(4)
144
+
145
+ self.vBoxLayout.addLayout(self.topLayout, 0)
146
+ self.vBoxLayout.addWidget(self.scrollArea, 1)
147
+ self.vBoxLayout.addLayout(self.bottomLayout, 0)
148
+
149
+ self.vBoxLayout.setAlignment(Qt.AlignTop)
150
+ self.topLayout.setAlignment(Qt.AlignTop)
151
+ self.scrollLayout.setAlignment(Qt.AlignTop)
152
+ self.bottomLayout.setAlignment(Qt.AlignBottom)
153
+
154
+ self.topLayout.addWidget(self.returnButton, 0, Qt.AlignTop)
155
+ self.topLayout.addWidget(self.menuButton, 0, Qt.AlignTop)
156
+
157
+ def _updateAcrylicColor(self):
158
+ if isDarkTheme():
159
+ tintColor = QColor(32, 32, 32, 200)
160
+ luminosityColor = QColor(0, 0, 0, 0)
161
+ else:
162
+ tintColor = QColor(255, 255, 255, 180)
163
+ luminosityColor = QColor(255, 255, 255, 0)
164
+
165
+ self.acrylicBrush.tintColor = tintColor
166
+ self.acrylicBrush.luminosityColor = luminosityColor
167
+
168
+ def widget(self, routeKey: str):
169
+ if routeKey not in self.items:
170
+ raise RouteKeyError(f"`{routeKey}` is illegal.")
171
+
172
+ return self.items[routeKey].widget
173
+
174
+ def addItem(self, routeKey: str, icon: Union[str, QIcon, FluentIconBase], text: str, onClick=None, selectable=True,
175
+ position=NavigationItemPosition.TOP, tooltip: str = None, parentRouteKey: str = None):
176
+ """ add navigation item
177
+
178
+ Parameters
179
+ ----------
180
+ routeKey: str
181
+ the unique name of item
182
+
183
+ icon: str | QIcon | FluentIconBase
184
+ the icon of navigation item
185
+
186
+ text: str
187
+ the text of navigation item
188
+
189
+ onClick: callable
190
+ the slot connected to item clicked signal
191
+
192
+ position: NavigationItemPosition
193
+ where the button is added
194
+
195
+ selectable: bool
196
+ whether the item is selectable
197
+
198
+ tooltip: str
199
+ the tooltip of item
200
+
201
+ parentRouteKey: str
202
+ the route key of parent item, the parent widget should be `NavigationTreeWidget`
203
+ """
204
+ return self.insertItem(-1, routeKey, icon, text, onClick, selectable, position, tooltip, parentRouteKey)
205
+
206
+ def addWidget(self, routeKey: str, widget: NavigationWidget, onClick=None, position=NavigationItemPosition.TOP,
207
+ tooltip: str = None, parentRouteKey: str = None):
208
+ """ add custom widget
209
+
210
+ Parameters
211
+ ----------
212
+ routeKey: str
213
+ the unique name of item
214
+
215
+ widget: NavigationWidget
216
+ the custom widget to be added
217
+
218
+ onClick: callable
219
+ the slot connected to item clicked signal
220
+
221
+ position: NavigationItemPosition
222
+ where the button is added
223
+
224
+ tooltip: str
225
+ the tooltip of widget
226
+
227
+ parentRouteKey: str
228
+ the route key of parent item, the parent item should be `NavigationTreeWidget`
229
+ """
230
+ self.insertWidget(-1, routeKey, widget, onClick, position, tooltip, parentRouteKey)
231
+
232
+ def insertItem(self, index: int, routeKey: str, icon: Union[str, QIcon, FluentIconBase], text: str, onClick=None,
233
+ selectable=True, position=NavigationItemPosition.TOP, tooltip: str = None, parentRouteKey=None):
234
+ """ insert navigation tree item
235
+
236
+ Parameters
237
+ ----------
238
+ index: int
239
+ the insert position of parent widget
240
+
241
+ routeKey: str
242
+ the unique name of item
243
+
244
+ icon: str | QIcon | FluentIconBase
245
+ the icon of navigation item
246
+
247
+ text: str
248
+ the text of navigation item
249
+
250
+ onClick: callable
251
+ the slot connected to item clicked signal
252
+
253
+ position: NavigationItemPosition
254
+ where the button is added
255
+
256
+ selectable: bool
257
+ whether the item is selectable
258
+
259
+ tooltip: str
260
+ the tooltip of item
261
+
262
+ parentRouteKey: str
263
+ the route key of parent item, the parent item should be `NavigationTreeWidget`
264
+ """
265
+ if routeKey in self.items:
266
+ return
267
+
268
+ w = NavigationTreeWidget(icon, text, selectable, self)
269
+ self.insertWidget(index, routeKey, w, onClick, position, tooltip, parentRouteKey)
270
+ return w
271
+
272
+ def insertWidget(self, index: int, routeKey: str, widget: NavigationWidget, onClick=None,
273
+ position=NavigationItemPosition.TOP, tooltip: str = None, parentRouteKey: str = None):
274
+ """ insert custom widget
275
+
276
+ Parameters
277
+ ----------
278
+ index: int
279
+ insert position
280
+
281
+ routeKey: str
282
+ the unique name of item
283
+
284
+ widget: NavigationWidget
285
+ the custom widget to be added
286
+
287
+ onClick: callable
288
+ the slot connected to item clicked signal
289
+
290
+ position: NavigationItemPosition
291
+ where the button is added
292
+
293
+ tooltip: str
294
+ the tooltip of widget
295
+
296
+ parentRouteKey: str
297
+ the route key of parent item, the parent item should be `NavigationTreeWidget`
298
+ """
299
+ if routeKey in self.items:
300
+ return
301
+
302
+ self._registerWidget(routeKey, parentRouteKey, widget, onClick, tooltip)
303
+ if parentRouteKey:
304
+ self.widget(parentRouteKey).insertChild(index, widget)
305
+ else:
306
+ self._insertWidgetToLayout(index, widget, position)
307
+
308
+ def addSeparator(self, position=NavigationItemPosition.TOP):
309
+ """ add separator
310
+
311
+ Parameters
312
+ ----------
313
+ position: NavigationPostion
314
+ where to add the separator
315
+ """
316
+ self.insertSeparator(-1, position)
317
+
318
+ def insertSeparator(self, index: int, position=NavigationItemPosition.TOP):
319
+ """ add separator
320
+
321
+ Parameters
322
+ ----------
323
+ index: int
324
+ insert position
325
+
326
+ position: NavigationPostion
327
+ where to add the separator
328
+ """
329
+ separator = NavigationSeparator(self)
330
+ self._insertWidgetToLayout(index, separator, position)
331
+
332
+ def _registerWidget(self, routeKey: str, parentRouteKey: str, widget: NavigationWidget, onClick, tooltip: str):
333
+ """ register widget """
334
+ widget.clicked.connect(self._onWidgetClicked)
335
+
336
+ if onClick is not None:
337
+ widget.clicked.connect(onClick)
338
+
339
+ widget.setProperty('routeKey', routeKey)
340
+ widget.setProperty('parentRouteKey', parentRouteKey)
341
+ self.items[routeKey] = NavigationItem(routeKey, parentRouteKey, widget)
342
+
343
+ if self.displayMode in [NavigationDisplayMode.EXPAND, NavigationDisplayMode.MENU]:
344
+ widget.setCompacted(False)
345
+
346
+ if tooltip:
347
+ widget.setToolTip(tooltip)
348
+ widget.installEventFilter(NavigationToolTipFilter(widget, 1000))
349
+
350
+ def _insertWidgetToLayout(self, index: int, widget: NavigationWidget, position: NavigationItemPosition):
351
+ """ insert widget to layout """
352
+ if position == NavigationItemPosition.TOP:
353
+ widget.setParent(self)
354
+ self.topLayout.insertWidget(index, widget, 0, Qt.AlignTop)
355
+ elif position == NavigationItemPosition.SCROLL:
356
+ widget.setParent(self.scrollWidget)
357
+ self.scrollLayout.insertWidget(index, widget, 0, Qt.AlignTop)
358
+ else:
359
+ widget.setParent(self)
360
+ self.bottomLayout.insertWidget(index, widget, 0, Qt.AlignBottom)
361
+
362
+ widget.show()
363
+
364
+ def removeWidget(self, routeKey: str):
365
+ """ remove widget
366
+
367
+ Parameters
368
+ ----------
369
+ routeKey: str
370
+ the unique name of item
371
+ """
372
+ if routeKey not in self.items:
373
+ return
374
+
375
+ item = self.items.pop(routeKey)
376
+
377
+ if item.parentRouteKey is not None:
378
+ self.widget(item.parentRouteKey).removeChild(item.widget)
379
+
380
+ if isinstance(item.widget, NavigationTreeWidgetBase):
381
+ for child in item.widget.findChildren(NavigationWidget, options=Qt.FindChildrenRecursively):
382
+ key = child.property('routeKey')
383
+ if key is None:
384
+ continue
385
+
386
+ self.items.pop(key)
387
+ child.deleteLater()
388
+ self.history.remove(key)
389
+
390
+ item.widget.deleteLater()
391
+ self.history.remove(routeKey)
392
+
393
+ def setMenuButtonVisible(self, isVisible: bool):
394
+ """ set whether the menu button is visible """
395
+ self._isMenuButtonVisible = isVisible
396
+ self.menuButton.setVisible(isVisible)
397
+
398
+ def setReturnButtonVisible(self, isVisible: bool):
399
+ """ set whether the return button is visible """
400
+ self._isReturnButtonVisible = isVisible
401
+ self.returnButton.setVisible(isVisible)
402
+
403
+ def setCollapsible(self, on: bool):
404
+ self._isCollapsible = on
405
+ if not on and self.displayMode != NavigationDisplayMode.EXPAND:
406
+ self.expand(False)
407
+
408
+ def setExpandWidth(self, width: int):
409
+ """ set the maximum width """
410
+ if width <= 42:
411
+ return
412
+
413
+ self.expandWidth = width
414
+ NavigationWidget.EXPAND_WIDTH = width - 10
415
+
416
+ def setMinimumExpandWidth(self, width: int):
417
+ """ Set the minimum window width that allows panel to be expanded """
418
+ self.minimumExpandWidth = width
419
+
420
+ def setAcrylicEnabled(self, isEnabled: bool):
421
+ if isEnabled == self.isAcrylicEnabled():
422
+ return
423
+
424
+ self._isAcrylicEnabled = isEnabled
425
+ self.setProperty("transparent", self._canDrawAcrylic())
426
+ self.setStyle(QApplication.style())
427
+ self.update()
428
+
429
+ def isAcrylicEnabled(self):
430
+ """ whether the acrylic effect is enabled """
431
+ return self._isAcrylicEnabled
432
+
433
+ def expand(self, useAni=True):
434
+ """ expand navigation panel """
435
+ self._setWidgetCompacted(False)
436
+ self.expandAni.setProperty('expand', True)
437
+ self.menuButton.setToolTip(self.tr('Close Navigation'))
438
+
439
+ # determine the display mode according to the width of window
440
+ # https://learn.microsoft.com/en-us/windows/apps/design/controls/navigationview#default
441
+ expandWidth = self.minimumExpandWidth + self.expandWidth - 322
442
+ if (self.window().width() >= expandWidth and not self.isMinimalEnabled) or not self._isCollapsible:
443
+ self.displayMode = NavigationDisplayMode.EXPAND
444
+ else:
445
+ self.setProperty('menu', True)
446
+ self.setStyle(QApplication.style())
447
+ self.displayMode = NavigationDisplayMode.MENU
448
+
449
+ # grab acrylic image
450
+ if self._canDrawAcrylic():
451
+ self.acrylicBrush.grabImage(
452
+ QRect(self.mapToGlobal(QPoint()), QSize(self.expandWidth, self.height())))
453
+
454
+ if not self._parent.isWindow():
455
+ pos = self.parent().pos()
456
+ self.setParent(self.window())
457
+ self.move(pos)
458
+
459
+ self.show()
460
+
461
+ if useAni:
462
+ self.displayModeChanged.emit(self.displayMode)
463
+ self.expandAni.setStartValue(
464
+ QRect(self.pos(), QSize(48, self.height())))
465
+ self.expandAni.setEndValue(
466
+ QRect(self.pos(), QSize(self.expandWidth, self.height())))
467
+ self.expandAni.start()
468
+ else:
469
+ self.resize(self.expandWidth, self.height())
470
+ self._onExpandAniFinished()
471
+
472
+ def collapse(self):
473
+ """ collapse navigation panel """
474
+ if self.expandAni.state() == QPropertyAnimation.Running:
475
+ return
476
+
477
+ for item in self.items.values():
478
+ w = item.widget
479
+ if isinstance(w, NavigationTreeWidgetBase) and w.isRoot():
480
+ w.setExpanded(False)
481
+
482
+ self.expandAni.setStartValue(
483
+ QRect(self.pos(), QSize(self.width(), self.height())))
484
+ self.expandAni.setEndValue(
485
+ QRect(self.pos(), QSize(48, self.height())))
486
+ self.expandAni.setProperty('expand', False)
487
+ self.expandAni.start()
488
+
489
+ self.menuButton.setToolTip(self.tr('Open Navigation'))
490
+
491
+ def toggle(self):
492
+ """ toggle navigation panel """
493
+ if self.displayMode in [NavigationDisplayMode.COMPACT, NavigationDisplayMode.MINIMAL]:
494
+ self.expand()
495
+ else:
496
+ self.collapse()
497
+
498
+ def setCurrentItem(self, routeKey: str):
499
+ """ set current selected item
500
+
501
+ Parameters
502
+ ----------
503
+ routeKey: str
504
+ the unique name of item
505
+ """
506
+ if routeKey not in self.items:
507
+ return
508
+
509
+ for k, item in self.items.items():
510
+ item.widget.setSelected(k == routeKey)
511
+
512
+ def _onWidgetClicked(self):
513
+ widget = self.sender() # type: NavigationWidget
514
+ if not widget.isSelectable:
515
+ return self._showFlyoutNavigationMenu(widget)
516
+
517
+ self.setCurrentItem(widget.property('routeKey'))
518
+
519
+ isLeaf = not isinstance(widget, NavigationTreeWidgetBase) or widget.isLeaf()
520
+ if self.displayMode == NavigationDisplayMode.MENU and isLeaf:
521
+ self.collapse()
522
+ elif self.isCollapsed():
523
+ self._showFlyoutNavigationMenu(widget)
524
+
525
+ def _showFlyoutNavigationMenu(self, widget: NavigationTreeWidget):
526
+ """ show flyout navigation menu """
527
+ if not (self.isCollapsed() and isinstance(widget, NavigationTreeWidget)):
528
+ return
529
+
530
+ if not widget.isRoot() or widget.isLeaf():
531
+ return
532
+
533
+ layout = QHBoxLayout()
534
+
535
+ if self._canDrawAcrylic():
536
+ view = AcrylicFlyoutViewBase()
537
+ view.setLayout(layout)
538
+ flyout = AcrylicFlyout(view, self.window())
539
+ else:
540
+ view = FlyoutViewBase()
541
+ view.setLayout(layout)
542
+ flyout = Flyout(view, self.window())
543
+
544
+ # add navigation menu to flyout
545
+ menu = NavigationFlyoutMenu(widget, view)
546
+ layout.setContentsMargins(0, 0, 0, 0)
547
+ layout.addWidget(menu)
548
+
549
+ # execuse flyout animation
550
+ flyout.resize(flyout.sizeHint())
551
+ pos = SlideRightFlyoutAnimationManager(flyout).position(widget)
552
+ flyout.exec(pos, FlyoutAnimationType.SLIDE_RIGHT)
553
+
554
+ menu.expanded.connect(lambda: self._adjustFlyoutMenuSize(flyout, widget, menu))
555
+
556
+ def _adjustFlyoutMenuSize(self, flyout: Flyout, widget: NavigationTreeWidget, menu: NavigationFlyoutMenu):
557
+ flyout.view.setFixedSize(menu.size())
558
+ flyout.setFixedSize(flyout.layout().sizeHint())
559
+
560
+ manager = flyout.aniManager
561
+ pos = manager.position(widget)
562
+
563
+ rect = self.window().geometry()
564
+ w, h = flyout.sizeHint().width() + 5, flyout.sizeHint().height()
565
+ x = max(rect.left(), min(pos.x(), rect.right() - w))
566
+ y = max(rect.top() + 42, min(pos.y() - 4, rect.bottom() - h + 5))
567
+ flyout.move(x, y)
568
+
569
+ def isCollapsed(self):
570
+ return self.displayMode == NavigationDisplayMode.COMPACT
571
+
572
+ def eventFilter(self, obj, e: QEvent):
573
+ if obj is not self.window() or not self._isCollapsible:
574
+ return super().eventFilter(obj, e)
575
+
576
+ if e.type() == QEvent.MouseButtonRelease:
577
+ if not self.geometry().contains(e.pos()) and self.displayMode == NavigationDisplayMode.MENU:
578
+ self.collapse()
579
+ elif e.type() == QEvent.Resize:
580
+ w = QResizeEvent(e).size().width()
581
+ if w < self.minimumExpandWidth and self.displayMode == NavigationDisplayMode.EXPAND:
582
+ self.collapse()
583
+ elif w >= self.minimumExpandWidth and self.displayMode == NavigationDisplayMode.COMPACT and \
584
+ not self._isMenuButtonVisible:
585
+ self.expand()
586
+
587
+ return super().eventFilter(obj, e)
588
+
589
+ def _onExpandAniFinished(self):
590
+ if not self.expandAni.property('expand'):
591
+ if self.isMinimalEnabled:
592
+ self.displayMode = NavigationDisplayMode.MINIMAL
593
+ else:
594
+ self.displayMode = NavigationDisplayMode.COMPACT
595
+
596
+ self.displayModeChanged.emit(self.displayMode)
597
+
598
+ if self.displayMode == NavigationDisplayMode.MINIMAL:
599
+ self.hide()
600
+ self.setProperty('menu', False)
601
+ self.setStyle(QApplication.style())
602
+ elif self.displayMode == NavigationDisplayMode.COMPACT:
603
+ self.setProperty('menu', False)
604
+ self.setStyle(QApplication.style())
605
+
606
+ for item in self.items.values():
607
+ item.widget.setCompacted(True)
608
+
609
+ if not self._parent.isWindow():
610
+ self.setParent(self._parent)
611
+ self.move(0, 0)
612
+ self.show()
613
+
614
+ def _setWidgetCompacted(self, isCompacted: bool):
615
+ """ set whether the navigation widget is compacted """
616
+ for item in self.findChildren(NavigationWidget):
617
+ item.setCompacted(isCompacted)
618
+
619
+ def layoutMinHeight(self):
620
+ th = self.topLayout.minimumSize().height()
621
+ bh = self.bottomLayout.minimumSize().height()
622
+ sh = sum(w.height() for w in self.findChildren(NavigationSeparator))
623
+ spacing = self.topLayout.count() * self.topLayout.spacing()
624
+ spacing += self.bottomLayout.count() * self.bottomLayout.spacing()
625
+ return 36 + th + bh + sh + spacing
626
+
627
+ def _canDrawAcrylic(self):
628
+ return self.acrylicBrush.isAvailable() and self.isAcrylicEnabled()
629
+
630
+ def paintEvent(self, e):
631
+ if not self._canDrawAcrylic() or self.displayMode != NavigationDisplayMode.MENU:
632
+ return super().paintEvent(e)
633
+
634
+ path = QPainterPath()
635
+ path.setFillRule(Qt.WindingFill)
636
+ path.addRoundedRect(0, 1, self.width() - 1, self.height() - 1, 7, 7)
637
+ path.addRect(0, 1, 8, self.height() - 1)
638
+ self.acrylicBrush.setClipPath(path)
639
+
640
+ self._updateAcrylicColor()
641
+ self.acrylicBrush.paint()
642
+
643
+ super().paintEvent(e)
644
+
645
+
646
+
647
+ class NavigationItemLayout(QVBoxLayout):
648
+ """ Navigation layout """
649
+
650
+ def setGeometry(self, rect: QRect):
651
+ super().setGeometry(rect)
652
+ for i in range(self.count()):
653
+ item = self.itemAt(i)
654
+ if isinstance(item.widget(), NavigationSeparator):
655
+ geo = item.geometry()
656
+ item.widget().setGeometry(0, geo.y(), geo.width(), geo.height())
657
+