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,673 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from enum import Enum
|
3
|
+
from PySide6.QtCore import (QEvent, QEasingCurve, Qt, Signal, QPropertyAnimation, Property, QRectF,
|
4
|
+
QTimer, QPoint, QObject)
|
5
|
+
from PySide6.QtGui import QPainter, QColor, QMouseEvent
|
6
|
+
from PySide6.QtWidgets import (QWidget, QToolButton, QAbstractScrollArea, QGraphicsOpacityEffect,
|
7
|
+
QHBoxLayout, QVBoxLayout, QApplication, QAbstractItemView, QListView)
|
8
|
+
|
9
|
+
from ...common.icon import FluentIcon
|
10
|
+
from ...common.style_sheet import isDarkTheme
|
11
|
+
from ...common.smooth_scroll import SmoothScroll
|
12
|
+
|
13
|
+
class ArrowButton(QToolButton):
|
14
|
+
""" Arrow button """
|
15
|
+
|
16
|
+
def __init__(self, icon: FluentIcon, parent=None):
|
17
|
+
super().__init__(parent=parent)
|
18
|
+
self.setFixedSize(10, 10)
|
19
|
+
self.lightColor = QColor(0, 0, 0, 114)
|
20
|
+
self.darkColor = QColor(255, 255, 255, 139)
|
21
|
+
self._icon = icon
|
22
|
+
self.opacity = 1
|
23
|
+
|
24
|
+
def setOpacity(self, opacity):
|
25
|
+
self.opacity = opacity
|
26
|
+
self.update()
|
27
|
+
|
28
|
+
def setLightColor(self, color):
|
29
|
+
self.lightColor = QColor(color)
|
30
|
+
self.update()
|
31
|
+
|
32
|
+
def setDarkColor(self, color):
|
33
|
+
self.darkColor = QColor(color)
|
34
|
+
self.update()
|
35
|
+
|
36
|
+
def paintEvent(self, e):
|
37
|
+
painter = QPainter(self)
|
38
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
39
|
+
|
40
|
+
color = self.darkColor if isDarkTheme() else self.lightColor
|
41
|
+
painter.setOpacity(self.opacity * color.alpha() / 255)
|
42
|
+
|
43
|
+
s = 7 if self.isDown() else 8
|
44
|
+
x = (self.width() - s) / 2
|
45
|
+
self._icon.render(painter, QRectF(x, x, s, s), fill=color.name())
|
46
|
+
|
47
|
+
|
48
|
+
class ScrollBarGroove(QWidget):
|
49
|
+
""" Scroll bar groove """
|
50
|
+
|
51
|
+
def __init__(self, orient: Qt.Orientation, parent):
|
52
|
+
super().__init__(parent=parent)
|
53
|
+
self._opacity = 1
|
54
|
+
self.lightBackgroundColor = QColor(252, 252, 252, 217)
|
55
|
+
self.darkBackgroundColor = QColor(44, 44, 44, 245)
|
56
|
+
|
57
|
+
if orient == Qt.Vertical:
|
58
|
+
self.setFixedWidth(12)
|
59
|
+
self.upButton = ArrowButton(FluentIcon.CARE_UP_SOLID, self)
|
60
|
+
self.downButton = ArrowButton(FluentIcon.CARE_DOWN_SOLID, self)
|
61
|
+
self.setLayout(QVBoxLayout(self))
|
62
|
+
self.layout().addWidget(self.upButton, 0, Qt.AlignHCenter)
|
63
|
+
self.layout().addStretch(1)
|
64
|
+
self.layout().addWidget(self.downButton, 0, Qt.AlignHCenter)
|
65
|
+
self.layout().setContentsMargins(0, 3, 0, 3)
|
66
|
+
else:
|
67
|
+
self.setFixedHeight(12)
|
68
|
+
self.upButton = ArrowButton(FluentIcon.CARE_LEFT_SOLID, self)
|
69
|
+
self.downButton = ArrowButton(FluentIcon.CARE_RIGHT_SOLID, self)
|
70
|
+
self.setLayout(QHBoxLayout(self))
|
71
|
+
self.layout().addWidget(self.upButton, 0, Qt.AlignVCenter)
|
72
|
+
self.layout().addStretch(1)
|
73
|
+
self.layout().addWidget(self.downButton, 0, Qt.AlignVCenter)
|
74
|
+
self.layout().setContentsMargins(3, 0, 3, 0)
|
75
|
+
|
76
|
+
self.opacityAni = QPropertyAnimation(self, b'opacity', self)
|
77
|
+
self.setOpacity(0)
|
78
|
+
|
79
|
+
def setLightBackgroundColor(self, color):
|
80
|
+
self.lightBackgroundColor = QColor(color)
|
81
|
+
self.update()
|
82
|
+
|
83
|
+
def setDarkBackgroundColor(self, color):
|
84
|
+
self.darkBackgroundColor = QColor(color)
|
85
|
+
self.update()
|
86
|
+
|
87
|
+
def fadeIn(self):
|
88
|
+
self.opacityAni.stop()
|
89
|
+
self.opacityAni.setStartValue(self.opacity)
|
90
|
+
self.opacityAni.setEndValue(1)
|
91
|
+
self.opacityAni.setDuration(150)
|
92
|
+
self.opacityAni.start()
|
93
|
+
|
94
|
+
def fadeOut(self):
|
95
|
+
self.opacityAni.stop()
|
96
|
+
self.opacityAni.setStartValue(self.opacity)
|
97
|
+
self.opacityAni.setEndValue(0)
|
98
|
+
self.opacityAni.setDuration(150)
|
99
|
+
self.opacityAni.start()
|
100
|
+
|
101
|
+
def paintEvent(self, e):
|
102
|
+
painter = QPainter(self)
|
103
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
104
|
+
painter.setOpacity(self.opacity)
|
105
|
+
painter.setPen(Qt.NoPen)
|
106
|
+
|
107
|
+
painter.setBrush(self.darkBackgroundColor if isDarkTheme() else self.lightBackgroundColor)
|
108
|
+
painter.drawRoundedRect(self.rect(), 6, 6)
|
109
|
+
|
110
|
+
def setOpacity(self, opacity: float):
|
111
|
+
self._opacity = opacity
|
112
|
+
self.upButton.setOpacity(opacity)
|
113
|
+
self.downButton.setOpacity(opacity)
|
114
|
+
self.update()
|
115
|
+
|
116
|
+
def getOpacity(self) -> float:
|
117
|
+
return self._opacity
|
118
|
+
|
119
|
+
opacity = Property(float, getOpacity, setOpacity)
|
120
|
+
|
121
|
+
|
122
|
+
class ScrollBarHandle(QWidget):
|
123
|
+
""" Scroll bar handle """
|
124
|
+
|
125
|
+
def __init__(self, orient: Qt.Orientation, parent=None):
|
126
|
+
super().__init__(parent)
|
127
|
+
self._opacity = 1
|
128
|
+
self.opacityAni = QPropertyAnimation(self, b'opacity', self)
|
129
|
+
self.lightColor = QColor(0, 0, 0, 114)
|
130
|
+
self.darkColor = QColor(255, 255, 255, 139)
|
131
|
+
self.orient = orient
|
132
|
+
if orient == Qt.Vertical:
|
133
|
+
self.setFixedWidth(3)
|
134
|
+
else:
|
135
|
+
self.setFixedHeight(3)
|
136
|
+
|
137
|
+
def setLightColor(self, color):
|
138
|
+
self.lightColor = QColor(color)
|
139
|
+
self.update()
|
140
|
+
|
141
|
+
def setDarkColor(self, color):
|
142
|
+
self.darkColor = QColor(color)
|
143
|
+
self.update()
|
144
|
+
|
145
|
+
def paintEvent(self, e):
|
146
|
+
painter = QPainter(self)
|
147
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
148
|
+
painter.setPen(Qt.NoPen)
|
149
|
+
|
150
|
+
r = self.width() / 2 if self.orient == Qt.Vertical else self.height() / 2
|
151
|
+
painter.setOpacity(self.opacity)
|
152
|
+
painter.setBrush(self.darkColor if isDarkTheme() else self.lightColor)
|
153
|
+
painter.drawRoundedRect(self.rect(), r, r)
|
154
|
+
|
155
|
+
def fadeIn(self):
|
156
|
+
self.opacityAni.stop()
|
157
|
+
self.opacityAni.setStartValue(self.opacity)
|
158
|
+
self.opacityAni.setEndValue(1)
|
159
|
+
self.opacityAni.setDuration(150)
|
160
|
+
self.opacityAni.start()
|
161
|
+
|
162
|
+
def fadeOut(self):
|
163
|
+
self.opacityAni.stop()
|
164
|
+
self.opacityAni.setStartValue(self.opacity)
|
165
|
+
self.opacityAni.setEndValue(0)
|
166
|
+
self.opacityAni.setDuration(150)
|
167
|
+
self.opacityAni.start()
|
168
|
+
|
169
|
+
def setOpacity(self, opacity: float):
|
170
|
+
self._opacity = opacity
|
171
|
+
self.update()
|
172
|
+
|
173
|
+
def getOpacity(self) -> float:
|
174
|
+
return self._opacity
|
175
|
+
|
176
|
+
opacity = Property(float, getOpacity, setOpacity)
|
177
|
+
|
178
|
+
|
179
|
+
class ScrollBarHandleDisplayMode(Enum):
|
180
|
+
"""Scroll bar handle display mode"""
|
181
|
+
|
182
|
+
ALWAYS = 0
|
183
|
+
ON_HOVER = 1
|
184
|
+
|
185
|
+
|
186
|
+
class ScrollBar(QWidget):
|
187
|
+
""" Fluent scroll bar """
|
188
|
+
|
189
|
+
rangeChanged = Signal(tuple)
|
190
|
+
valueChanged = Signal(int)
|
191
|
+
sliderPressed = Signal()
|
192
|
+
sliderReleased = Signal()
|
193
|
+
sliderMoved = Signal()
|
194
|
+
|
195
|
+
def __init__(self, orient: Qt.Orientation, parent: QAbstractScrollArea):
|
196
|
+
super().__init__(parent)
|
197
|
+
self.groove = ScrollBarGroove(orient, self)
|
198
|
+
self.handle = ScrollBarHandle(orient, self)
|
199
|
+
self.timer = QTimer(self)
|
200
|
+
|
201
|
+
self._orientation = orient
|
202
|
+
self._singleStep = 1
|
203
|
+
self._pageStep = 50
|
204
|
+
self._padding = 14
|
205
|
+
|
206
|
+
self._minimum = 0
|
207
|
+
self._maximum = 0
|
208
|
+
self._value = 0
|
209
|
+
|
210
|
+
self._isPressed = False
|
211
|
+
self._isEnter = False
|
212
|
+
self._isExpanded = False
|
213
|
+
self._pressedPos = QPoint()
|
214
|
+
self._isForceHidden = False
|
215
|
+
self.handleDisplayMode = ScrollBarHandleDisplayMode.ALWAYS
|
216
|
+
|
217
|
+
if orient == Qt.Vertical:
|
218
|
+
self.partnerBar = parent.verticalScrollBar()
|
219
|
+
QAbstractScrollArea.setVerticalScrollBarPolicy(parent, Qt.ScrollBarAlwaysOff)
|
220
|
+
else:
|
221
|
+
self.partnerBar = parent.horizontalScrollBar()
|
222
|
+
QAbstractScrollArea.setHorizontalScrollBarPolicy(parent, Qt.ScrollBarAlwaysOff)
|
223
|
+
|
224
|
+
self.__initWidget(parent)
|
225
|
+
|
226
|
+
def __initWidget(self, parent):
|
227
|
+
self.groove.upButton.clicked.connect(self._onPageUp)
|
228
|
+
self.groove.downButton.clicked.connect(self._onPageDown)
|
229
|
+
self.groove.opacityAni.valueChanged.connect(self._onOpacityAniValueChanged)
|
230
|
+
|
231
|
+
self.partnerBar.rangeChanged.connect(self.setRange)
|
232
|
+
self.partnerBar.valueChanged.connect(self._onValueChanged)
|
233
|
+
self.valueChanged.connect(self.partnerBar.setValue)
|
234
|
+
|
235
|
+
parent.installEventFilter(self)
|
236
|
+
|
237
|
+
self.setRange(self.partnerBar.minimum(), self.partnerBar.maximum())
|
238
|
+
self.setVisible(self.maximum() > 0 and not self._isForceHidden)
|
239
|
+
self._adjustPos(self.parent().size())
|
240
|
+
|
241
|
+
def _onPageUp(self):
|
242
|
+
self.setValue(self.value() - self.pageStep())
|
243
|
+
|
244
|
+
def _onPageDown(self):
|
245
|
+
self.setValue(self.value() + self.pageStep())
|
246
|
+
|
247
|
+
def _onValueChanged(self, value):
|
248
|
+
self.val = value
|
249
|
+
|
250
|
+
def value(self):
|
251
|
+
return self._value
|
252
|
+
|
253
|
+
@Property(int, notify=valueChanged)
|
254
|
+
def val(self):
|
255
|
+
return self._value
|
256
|
+
|
257
|
+
@val.setter
|
258
|
+
def val(self, value: int):
|
259
|
+
if value == self.value():
|
260
|
+
return
|
261
|
+
|
262
|
+
value = max(self.minimum(), min(value, self.maximum()))
|
263
|
+
self._value = value
|
264
|
+
self.valueChanged.emit(value)
|
265
|
+
|
266
|
+
# adjust the position of handle
|
267
|
+
self._adjustHandlePos()
|
268
|
+
|
269
|
+
def minimum(self):
|
270
|
+
return self._minimum
|
271
|
+
|
272
|
+
def maximum(self):
|
273
|
+
return self._maximum
|
274
|
+
|
275
|
+
def orientation(self):
|
276
|
+
return self._orientation
|
277
|
+
|
278
|
+
def pageStep(self):
|
279
|
+
return self._pageStep
|
280
|
+
|
281
|
+
def singleStep(self):
|
282
|
+
return self._singleStep
|
283
|
+
|
284
|
+
def isSliderDown(self):
|
285
|
+
return self._isPressed
|
286
|
+
|
287
|
+
def setValue(self, value: int):
|
288
|
+
self.val = value
|
289
|
+
|
290
|
+
def setMinimum(self, min: int):
|
291
|
+
if min == self.minimum():
|
292
|
+
return
|
293
|
+
|
294
|
+
self._minimum = min
|
295
|
+
self.rangeChanged.emit((min, self.maximum()))
|
296
|
+
|
297
|
+
def setMaximum(self, max: int):
|
298
|
+
if max == self.maximum():
|
299
|
+
return
|
300
|
+
|
301
|
+
self._maximum = max
|
302
|
+
self.rangeChanged.emit((self.minimum(), max))
|
303
|
+
|
304
|
+
def setRange(self, min: int, max: int):
|
305
|
+
if min > max or (min == self.minimum() and max == self.maximum()):
|
306
|
+
return
|
307
|
+
|
308
|
+
self.setMinimum(min)
|
309
|
+
self.setMaximum(max)
|
310
|
+
|
311
|
+
self._adjustHandleSize()
|
312
|
+
self._adjustHandlePos()
|
313
|
+
self.setVisible(max > 0 and not self._isForceHidden)
|
314
|
+
|
315
|
+
self.rangeChanged.emit((min, max))
|
316
|
+
|
317
|
+
def setPageStep(self, step: int):
|
318
|
+
if step >= 1:
|
319
|
+
self._pageStep = step
|
320
|
+
|
321
|
+
def setSingleStep(self, step: int):
|
322
|
+
if step >= 1:
|
323
|
+
self._singleStep = step
|
324
|
+
|
325
|
+
def setSliderDown(self, isDown: bool):
|
326
|
+
self._isPressed = True
|
327
|
+
if isDown:
|
328
|
+
self.sliderPressed.emit()
|
329
|
+
else:
|
330
|
+
self.sliderReleased.emit()
|
331
|
+
|
332
|
+
def setHandleColor(self, light, dark):
|
333
|
+
"""set the color of handle
|
334
|
+
|
335
|
+
Parameters
|
336
|
+
----------
|
337
|
+
light, dark: QColor | str | Qt.GlobalColor
|
338
|
+
the color in light/dark theme mode
|
339
|
+
"""
|
340
|
+
self.handle.setLightColor(light)
|
341
|
+
self.handle.setDarkColor(dark)
|
342
|
+
|
343
|
+
def setArrowColor(self, light, dark):
|
344
|
+
"""set the color of arrow button
|
345
|
+
|
346
|
+
Parameters
|
347
|
+
----------
|
348
|
+
light, dark: QColor | str | Qt.GlobalColor
|
349
|
+
the color in light/dark theme mode
|
350
|
+
"""
|
351
|
+
self.groove.upButton.setLightColor(light)
|
352
|
+
self.groove.upButton.setDarkColor(dark)
|
353
|
+
self.groove.downButton.setLightColor(light)
|
354
|
+
self.groove.downButton.setDarkColor(dark)
|
355
|
+
|
356
|
+
def setGrooveColor(self, light, dark):
|
357
|
+
"""set the color of groove
|
358
|
+
|
359
|
+
Parameters
|
360
|
+
----------
|
361
|
+
light, dark: QColor | str | Qt.GlobalColor
|
362
|
+
the color in light/dark theme mode
|
363
|
+
"""
|
364
|
+
self.groove.setLightBackgroundColor(light)
|
365
|
+
self.groove.setDarkBackgroundColor(dark)
|
366
|
+
|
367
|
+
def setHandleDisplayMode(self, mode: ScrollBarHandleDisplayMode):
|
368
|
+
"""set the display mode of handle"""
|
369
|
+
if mode == self.handleDisplayMode:
|
370
|
+
return
|
371
|
+
|
372
|
+
self.handleDisplayMode = mode
|
373
|
+
if mode == ScrollBarHandleDisplayMode.ON_HOVER and not self._isEnter:
|
374
|
+
self.handle.fadeOut()
|
375
|
+
elif mode == ScrollBarHandleDisplayMode.ALWAYS:
|
376
|
+
self.handle.fadeIn()
|
377
|
+
|
378
|
+
def expand(self):
|
379
|
+
""" expand scroll bar """
|
380
|
+
if self._isExpanded or not self._isEnter:
|
381
|
+
return
|
382
|
+
|
383
|
+
self._isExpanded = True
|
384
|
+
self.groove.fadeIn()
|
385
|
+
self.handle.fadeIn()
|
386
|
+
|
387
|
+
def collapse(self):
|
388
|
+
""" collapse scroll bar """
|
389
|
+
if not self._isExpanded or self._isEnter:
|
390
|
+
return
|
391
|
+
|
392
|
+
self._isExpanded = False
|
393
|
+
self.groove.fadeOut()
|
394
|
+
|
395
|
+
if self.handleDisplayMode == ScrollBarHandleDisplayMode.ON_HOVER:
|
396
|
+
self.handle.fadeOut()
|
397
|
+
|
398
|
+
def enterEvent(self, e):
|
399
|
+
self._isEnter = True
|
400
|
+
self.timer.stop()
|
401
|
+
self.timer.singleShot(200, self.expand)
|
402
|
+
|
403
|
+
def leaveEvent(self, e):
|
404
|
+
self._isEnter = False
|
405
|
+
self.timer.stop()
|
406
|
+
self.timer.singleShot(200, self.collapse)
|
407
|
+
|
408
|
+
def eventFilter(self, obj, e: QEvent):
|
409
|
+
if obj is not self.parent():
|
410
|
+
return super().eventFilter(obj, e)
|
411
|
+
|
412
|
+
# adjust the position of slider
|
413
|
+
if e.type() == QEvent.Resize:
|
414
|
+
self._adjustPos(e.size())
|
415
|
+
|
416
|
+
return super().eventFilter(obj, e)
|
417
|
+
|
418
|
+
def resizeEvent(self, e):
|
419
|
+
self.groove.resize(self.size())
|
420
|
+
|
421
|
+
def mousePressEvent(self, e: QMouseEvent):
|
422
|
+
super().mousePressEvent(e)
|
423
|
+
self._isPressed = True
|
424
|
+
self._pressedPos = e.pos()
|
425
|
+
|
426
|
+
if self.childAt(e.pos()) is self.handle or not self._isSlideResion(e.pos()):
|
427
|
+
return
|
428
|
+
|
429
|
+
if self.orientation() == Qt.Vertical:
|
430
|
+
if e.pos().y() > self.handle.geometry().bottom():
|
431
|
+
value = e.pos().y() - self.handle.height() - self._padding
|
432
|
+
else:
|
433
|
+
value = e.pos().y() - self._padding
|
434
|
+
else:
|
435
|
+
if e.pos().x() > self.handle.geometry().right():
|
436
|
+
value = e.pos().x() - self.handle.width() - self._padding
|
437
|
+
else:
|
438
|
+
value = e.pos().x() - self._padding
|
439
|
+
|
440
|
+
self.setValue(int(value / max(self._slideLength(), 1) * self.maximum()))
|
441
|
+
self.sliderPressed.emit()
|
442
|
+
|
443
|
+
def mouseReleaseEvent(self, e):
|
444
|
+
super().mouseReleaseEvent(e)
|
445
|
+
self._isPressed = False
|
446
|
+
self.sliderReleased.emit()
|
447
|
+
|
448
|
+
def mouseMoveEvent(self, e: QMouseEvent):
|
449
|
+
if self.orientation() == Qt.Vertical:
|
450
|
+
dv = e.pos().y() - self._pressedPos.y()
|
451
|
+
else:
|
452
|
+
dv = e.pos().x() - self._pressedPos.x()
|
453
|
+
|
454
|
+
# don't use `self.setValue()`, because it could be reimplemented
|
455
|
+
dv = int(dv / max(self._slideLength(), 1) * (self.maximum() - self.minimum()))
|
456
|
+
ScrollBar.setValue(self, self.value() + dv)
|
457
|
+
|
458
|
+
self._pressedPos = e.pos()
|
459
|
+
self.sliderMoved.emit()
|
460
|
+
|
461
|
+
def _adjustPos(self, size):
|
462
|
+
if self.orientation() == Qt.Vertical:
|
463
|
+
self.resize(12, size.height() - 2)
|
464
|
+
self.move(size.width() - 13, 1)
|
465
|
+
else:
|
466
|
+
self.resize(size.width() - 2, 12)
|
467
|
+
self.move(1, size.height() - 13)
|
468
|
+
|
469
|
+
def _adjustHandleSize(self):
|
470
|
+
p = self.parent()
|
471
|
+
if self.orientation() == Qt.Vertical:
|
472
|
+
total = self.maximum() - self.minimum() + p.height()
|
473
|
+
s = int(self._grooveLength() * p.height() / max(total, 1))
|
474
|
+
self.handle.setFixedHeight(max(30, s))
|
475
|
+
else:
|
476
|
+
total = self.maximum() - self.minimum() + p.width()
|
477
|
+
s = int(self._grooveLength() * p.width() / max(total, 1))
|
478
|
+
self.handle.setFixedWidth(max(30, s))
|
479
|
+
|
480
|
+
def _adjustHandlePos(self):
|
481
|
+
total = max(self.maximum() - self.minimum(), 1)
|
482
|
+
delta = int(self.value() / total * self._slideLength())
|
483
|
+
|
484
|
+
if self.orientation() == Qt.Vertical:
|
485
|
+
x = self.width() - self.handle.width() - 3
|
486
|
+
self.handle.move(x, self._padding + delta)
|
487
|
+
else:
|
488
|
+
y = self.height() - self.handle.height() - 3
|
489
|
+
self.handle.move(self._padding + delta, y)
|
490
|
+
|
491
|
+
def _grooveLength(self):
|
492
|
+
if self.orientation() == Qt.Vertical:
|
493
|
+
return self.height() - 2 * self._padding
|
494
|
+
|
495
|
+
return self.width() - 2 * self._padding
|
496
|
+
|
497
|
+
def _slideLength(self):
|
498
|
+
if self.orientation() == Qt.Vertical:
|
499
|
+
return self._grooveLength() - self.handle.height()
|
500
|
+
|
501
|
+
return self._grooveLength() - self.handle.width()
|
502
|
+
|
503
|
+
def _isSlideResion(self, pos: QPoint):
|
504
|
+
if self.orientation() == Qt.Vertical:
|
505
|
+
return self._padding <= pos.y() <= self.height() - self._padding
|
506
|
+
|
507
|
+
return self._padding <= pos.x() <= self.width() - self._padding
|
508
|
+
|
509
|
+
def _onOpacityAniValueChanged(self):
|
510
|
+
opacity = self.groove.opacity
|
511
|
+
if self.orientation() == Qt.Vertical:
|
512
|
+
self.handle.setFixedWidth(int(3 + opacity * 3))
|
513
|
+
else:
|
514
|
+
self.handle.setFixedHeight(int(3 + opacity * 3))
|
515
|
+
|
516
|
+
self._adjustHandlePos()
|
517
|
+
|
518
|
+
def setForceHidden(self, isHidden: bool):
|
519
|
+
""" whether to force the scrollbar to be hidden """
|
520
|
+
self._isForceHidden = isHidden
|
521
|
+
self.setVisible(self.maximum() > 0 and not isHidden)
|
522
|
+
|
523
|
+
def wheelEvent(self, e):
|
524
|
+
QApplication.sendEvent(self.parent().viewport(), e)
|
525
|
+
|
526
|
+
|
527
|
+
class SmoothScrollBar(ScrollBar):
|
528
|
+
""" Smooth scroll bar """
|
529
|
+
|
530
|
+
def __init__(self, orient: Qt.Orientation, parent):
|
531
|
+
super().__init__(orient, parent)
|
532
|
+
self.duration = 500
|
533
|
+
self.ani = QPropertyAnimation()
|
534
|
+
self.ani.setTargetObject(self)
|
535
|
+
self.ani.setPropertyName(b"val")
|
536
|
+
self.ani.setEasingCurve(QEasingCurve.OutCubic)
|
537
|
+
self.ani.setDuration(self.duration)
|
538
|
+
|
539
|
+
self.__value = self.value()
|
540
|
+
|
541
|
+
def setValue(self, value, useAni=True):
|
542
|
+
if value == self.value():
|
543
|
+
return
|
544
|
+
|
545
|
+
# stop running animation
|
546
|
+
self.ani.stop()
|
547
|
+
|
548
|
+
if not useAni:
|
549
|
+
self.val = value
|
550
|
+
return
|
551
|
+
|
552
|
+
# adjust the duration
|
553
|
+
dv = abs(value - self.value())
|
554
|
+
if dv < 50:
|
555
|
+
self.ani.setDuration(int(self.duration * dv / 70))
|
556
|
+
else:
|
557
|
+
self.ani.setDuration(self.duration)
|
558
|
+
|
559
|
+
self.ani.setStartValue(self.value())
|
560
|
+
self.ani.setEndValue(value)
|
561
|
+
self.ani.start()
|
562
|
+
|
563
|
+
def scrollValue(self, value, useAni=True):
|
564
|
+
""" scroll the specified distance """
|
565
|
+
self.__value += value
|
566
|
+
self.__value = max(self.minimum(), self.__value)
|
567
|
+
self.__value = min(self.maximum(), self.__value)
|
568
|
+
self.setValue(self.__value, useAni)
|
569
|
+
|
570
|
+
def scrollTo(self, value, useAni=True):
|
571
|
+
""" scroll to the specified position """
|
572
|
+
self.__value = value
|
573
|
+
self.__value = max(self.minimum(), self.__value)
|
574
|
+
self.__value = min(self.maximum(), self.__value)
|
575
|
+
self.setValue(self.__value, useAni)
|
576
|
+
|
577
|
+
def resetValue(self, value):
|
578
|
+
self.__value = value
|
579
|
+
|
580
|
+
def mousePressEvent(self, e):
|
581
|
+
self.ani.stop()
|
582
|
+
super().mousePressEvent(e)
|
583
|
+
self.__value = self.value()
|
584
|
+
|
585
|
+
def mouseMoveEvent(self, e):
|
586
|
+
self.ani.stop()
|
587
|
+
super().mouseMoveEvent(e)
|
588
|
+
self.__value = self.value()
|
589
|
+
|
590
|
+
def setScrollAnimation(self, duration, easing=QEasingCurve.OutCubic):
|
591
|
+
""" set scroll animation
|
592
|
+
|
593
|
+
Parameters
|
594
|
+
----------
|
595
|
+
duration: int
|
596
|
+
scroll duration
|
597
|
+
|
598
|
+
easing: QEasingCurve
|
599
|
+
animation type
|
600
|
+
"""
|
601
|
+
self.duration = duration
|
602
|
+
self.ani.setDuration(duration)
|
603
|
+
self.ani.setEasingCurve(easing)
|
604
|
+
|
605
|
+
|
606
|
+
class SmoothScrollDelegate(QObject):
|
607
|
+
""" Smooth scroll delegate """
|
608
|
+
|
609
|
+
def __init__(self, parent: QAbstractScrollArea, useAni=False):
|
610
|
+
"""
|
611
|
+
Parameters
|
612
|
+
----------
|
613
|
+
parent: QAbstractScrollArea
|
614
|
+
the scrolling area being delegated
|
615
|
+
|
616
|
+
useAni: bool
|
617
|
+
whether to use `QPropertyAnimation` to achieve smooth scrolling
|
618
|
+
"""
|
619
|
+
super().__init__(parent)
|
620
|
+
self.useAni = useAni
|
621
|
+
self.vScrollBar = SmoothScrollBar(Qt.Vertical, parent)
|
622
|
+
self.hScrollBar = SmoothScrollBar(Qt.Horizontal, parent)
|
623
|
+
self.verticalSmoothScroll = SmoothScroll(parent, Qt.Vertical)
|
624
|
+
self.horizonSmoothScroll = SmoothScroll(parent, Qt.Horizontal)
|
625
|
+
|
626
|
+
if isinstance(parent, QAbstractItemView):
|
627
|
+
parent.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
|
628
|
+
parent.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
|
629
|
+
if isinstance(parent, QListView):
|
630
|
+
parent.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
631
|
+
parent.horizontalScrollBar().setStyleSheet("QScrollBar:horizontal{height: 0px}")
|
632
|
+
|
633
|
+
parent.viewport().installEventFilter(self)
|
634
|
+
parent.setVerticalScrollBarPolicy = self.setVerticalScrollBarPolicy
|
635
|
+
parent.setHorizontalScrollBarPolicy = self.setHorizontalScrollBarPolicy
|
636
|
+
|
637
|
+
def eventFilter(self, obj, e: QEvent):
|
638
|
+
if e.type() == QEvent.Type.Wheel:
|
639
|
+
# Check if the vertical scroll is at its limit
|
640
|
+
verticalAtEnd = (e.angleDelta().y() < 0 and self.vScrollBar.value() == self.vScrollBar.maximum()) or \
|
641
|
+
(e.angleDelta().y() > 0 and self.vScrollBar.value() == self.vScrollBar.minimum())
|
642
|
+
|
643
|
+
# Check if the horizontal scroll is at its limit
|
644
|
+
horizontalAtEnd = (e.angleDelta().x() < 0 and self.hScrollBar.value() == self.hScrollBar.maximum()) or \
|
645
|
+
(e.angleDelta().x() > 0 and self.hScrollBar.value() == self.hScrollBar.minimum())
|
646
|
+
|
647
|
+
if verticalAtEnd or horizontalAtEnd:
|
648
|
+
return False
|
649
|
+
|
650
|
+
if e.angleDelta().y() != 0:
|
651
|
+
if not self.useAni:
|
652
|
+
self.verticalSmoothScroll.wheelEvent(e)
|
653
|
+
else:
|
654
|
+
self.vScrollBar.scrollValue(-e.angleDelta().y())
|
655
|
+
else:
|
656
|
+
if not self.useAni:
|
657
|
+
self.horizonSmoothScroll.wheelEvent(e)
|
658
|
+
else:
|
659
|
+
self.hScrollBar.scrollValue(-e.angleDelta().x())
|
660
|
+
|
661
|
+
e.setAccepted(True)
|
662
|
+
return True
|
663
|
+
|
664
|
+
return super().eventFilter(obj, e)
|
665
|
+
|
666
|
+
def setVerticalScrollBarPolicy(self, policy):
|
667
|
+
QAbstractScrollArea.setVerticalScrollBarPolicy(self.parent(), Qt.ScrollBarAlwaysOff)
|
668
|
+
self.vScrollBar.setForceHidden(policy == Qt.ScrollBarAlwaysOff)
|
669
|
+
|
670
|
+
def setHorizontalScrollBarPolicy(self, policy):
|
671
|
+
QAbstractScrollArea.setHorizontalScrollBarPolicy(self.parent(), Qt.ScrollBarAlwaysOff)
|
672
|
+
self.hScrollBar.setForceHidden(policy == Qt.ScrollBarAlwaysOff)
|
673
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from PySide6.QtGui import QPixmap, QPainter, QColor
|
3
|
+
from PySide6.QtWidgets import QWidget
|
4
|
+
|
5
|
+
from ...common.style_sheet import isDarkTheme
|
6
|
+
|
7
|
+
|
8
|
+
class HorizontalSeparator(QWidget):
|
9
|
+
""" Horizontal separator """
|
10
|
+
|
11
|
+
def __init__(self, parent=None):
|
12
|
+
super().__init__(parent=parent)
|
13
|
+
self.setFixedHeight(3)
|
14
|
+
|
15
|
+
def paintEvent(self, e):
|
16
|
+
painter = QPainter(self)
|
17
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
18
|
+
|
19
|
+
if isDarkTheme():
|
20
|
+
painter.setPen(QColor(255, 255, 255, 51))
|
21
|
+
else:
|
22
|
+
painter.setPen(QColor(0, 0, 0, 22))
|
23
|
+
|
24
|
+
painter.drawLine(0, 1, self.width(), 1)
|
25
|
+
|
26
|
+
|
27
|
+
class VerticalSeparator(QWidget):
|
28
|
+
""" Vertical separator """
|
29
|
+
|
30
|
+
def __init__(self, parent=None):
|
31
|
+
super().__init__(parent=parent)
|
32
|
+
self.setFixedWidth(3)
|
33
|
+
|
34
|
+
def paintEvent(self, e):
|
35
|
+
painter = QPainter(self)
|
36
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
37
|
+
|
38
|
+
if isDarkTheme():
|
39
|
+
painter.setPen(QColor(255, 255, 255, 51))
|
40
|
+
else:
|
41
|
+
painter.setPen(QColor(0, 0, 0, 22))
|
42
|
+
|
43
|
+
painter.drawLine(1, 0, 1, self.height())
|