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