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,632 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from typing import Iterable, List
|
3
|
+
|
4
|
+
from PySide6.QtCore import Qt, Signal, QSize, QRectF, QPoint, QPropertyAnimation, QEasingCurve, QObject
|
5
|
+
from PySide6.QtGui import QColor, QPainter, QCursor, QRegion
|
6
|
+
from PySide6.QtWidgets import (QApplication, QWidget, QFrame, QVBoxLayout, QHBoxLayout,
|
7
|
+
QGraphicsDropShadowEffect, QSizePolicy, QPushButton, QListWidgetItem)
|
8
|
+
|
9
|
+
from ..widgets.cycle_list_widget import CycleListWidget
|
10
|
+
from ..widgets.button import TransparentToolButton
|
11
|
+
from ...common.icon import FluentIcon
|
12
|
+
from ...common.screen import getCurrentScreenGeometry
|
13
|
+
from ...common.style_sheet import FluentStyleSheet, themeColor, isDarkTheme
|
14
|
+
from ...common.color import autoFallbackThemeColor
|
15
|
+
|
16
|
+
|
17
|
+
class SeparatorWidget(QWidget):
|
18
|
+
""" Separator widget """
|
19
|
+
|
20
|
+
def __init__(self, orient: Qt.Orientation, parent=None):
|
21
|
+
super().__init__(parent=parent)
|
22
|
+
if orient == Qt.Horizontal:
|
23
|
+
self.setFixedHeight(1)
|
24
|
+
else:
|
25
|
+
self.setFixedWidth(1)
|
26
|
+
|
27
|
+
self.setAttribute(Qt.WA_StyledBackground)
|
28
|
+
FluentStyleSheet.TIME_PICKER.apply(self)
|
29
|
+
|
30
|
+
|
31
|
+
class ItemMaskWidget(QWidget):
|
32
|
+
""" Item mask widget """
|
33
|
+
|
34
|
+
def __init__(self, listWidgets: List[CycleListWidget], parent=None):
|
35
|
+
super().__init__(parent=parent)
|
36
|
+
self.listWidgets = listWidgets
|
37
|
+
self.setFixedHeight(37)
|
38
|
+
self.lightBackgroundColor = QColor()
|
39
|
+
self.darkBackgroundColor = QColor()
|
40
|
+
FluentStyleSheet.TIME_PICKER.apply(self)
|
41
|
+
|
42
|
+
def setCustomBackgroundColor(self, light, dark):
|
43
|
+
self.lightBackgroundColor = QColor(light)
|
44
|
+
self.darkBackgroundColor = QColor(dark)
|
45
|
+
self.update()
|
46
|
+
|
47
|
+
def paintEvent(self, e):
|
48
|
+
painter = QPainter(self)
|
49
|
+
painter.setRenderHints(QPainter.Antialiasing |
|
50
|
+
QPainter.TextAntialiasing)
|
51
|
+
|
52
|
+
# draw background
|
53
|
+
painter.setPen(Qt.NoPen)
|
54
|
+
painter.setBrush(autoFallbackThemeColor(self.lightBackgroundColor, self.darkBackgroundColor))
|
55
|
+
painter.drawRoundedRect(self.rect().adjusted(4, 0, -3, 0), 5, 5)
|
56
|
+
|
57
|
+
# draw text
|
58
|
+
painter.setPen(Qt.black if isDarkTheme() else Qt.white)
|
59
|
+
painter.setFont(self.font())
|
60
|
+
w, h = 0, self.height()
|
61
|
+
for i, p in enumerate(self.listWidgets):
|
62
|
+
painter.save()
|
63
|
+
|
64
|
+
# draw first item's text
|
65
|
+
x = p.itemSize.width()//2 + 4 + self.x()
|
66
|
+
item1 = p.itemAt(QPoint(x, self.y() + 6))
|
67
|
+
if not item1:
|
68
|
+
painter.restore()
|
69
|
+
continue
|
70
|
+
|
71
|
+
iw = item1.sizeHint().width()
|
72
|
+
y = p.visualItemRect(item1).y()
|
73
|
+
painter.translate(w, y - self.y() + 7)
|
74
|
+
self._drawText(item1, painter, 0)
|
75
|
+
|
76
|
+
# draw second item's text
|
77
|
+
item2 = p.itemAt(self.pos() + QPoint(x, h - 6))
|
78
|
+
self._drawText(item2, painter, h)
|
79
|
+
|
80
|
+
painter.restore()
|
81
|
+
w += (iw + 8) # margin: 0 4px;
|
82
|
+
|
83
|
+
def _drawText(self, item: QListWidgetItem, painter: QPainter, y: int):
|
84
|
+
align = item.textAlignment()
|
85
|
+
w, h = item.sizeHint().width(), item.sizeHint().height()
|
86
|
+
if align & Qt.AlignLeft:
|
87
|
+
rect = QRectF(15, y, w, h) # padding-left: 11px
|
88
|
+
elif align & Qt.AlignRight:
|
89
|
+
rect = QRectF(4, y, w-15, h) # padding-right: 11px
|
90
|
+
elif align & Qt.AlignCenter:
|
91
|
+
rect = QRectF(4, y, w, h)
|
92
|
+
|
93
|
+
painter.drawText(rect, align, item.text())
|
94
|
+
|
95
|
+
|
96
|
+
class PickerColumnFormatter(QObject):
|
97
|
+
""" Picker column formatter """
|
98
|
+
|
99
|
+
def __init__(self):
|
100
|
+
super().__init__()
|
101
|
+
|
102
|
+
def encode(self, value):
|
103
|
+
""" convert original value to formatted value """
|
104
|
+
return str(value)
|
105
|
+
|
106
|
+
def decode(self, value: str):
|
107
|
+
""" convert formatted value to original value """
|
108
|
+
return str(value)
|
109
|
+
|
110
|
+
|
111
|
+
class DigitFormatter(PickerColumnFormatter):
|
112
|
+
""" Digit formatter """
|
113
|
+
|
114
|
+
def decode(self, value):
|
115
|
+
return int(value)
|
116
|
+
|
117
|
+
|
118
|
+
class PickerColumnButton(QPushButton):
|
119
|
+
""" Picker column button """
|
120
|
+
|
121
|
+
def __init__(self, name: str, items: Iterable, width: int, align=Qt.AlignLeft, formatter=None, parent=None):
|
122
|
+
super().__init__(text=name, parent=parent)
|
123
|
+
self._name = name
|
124
|
+
self._value = None # type: str
|
125
|
+
|
126
|
+
self.setItems(items)
|
127
|
+
self.setAlignment(align)
|
128
|
+
self.setFormatter(formatter)
|
129
|
+
self.setFixedSize(width, 30)
|
130
|
+
self.setObjectName('pickerButton')
|
131
|
+
self.setProperty('hasBorder', False)
|
132
|
+
self.setAttribute(Qt.WA_TransparentForMouseEvents)
|
133
|
+
|
134
|
+
def align(self):
|
135
|
+
return self._align
|
136
|
+
|
137
|
+
def setAlignment(self, align=Qt.AlignCenter):
|
138
|
+
""" set the text alignment """
|
139
|
+
if align == Qt.AlignLeft:
|
140
|
+
self.setProperty('align', 'left')
|
141
|
+
elif align == Qt.AlignRight:
|
142
|
+
self.setProperty('align', 'right')
|
143
|
+
else:
|
144
|
+
self.setProperty('align', 'center')
|
145
|
+
|
146
|
+
self._align = align
|
147
|
+
self.setStyle(QApplication.style())
|
148
|
+
|
149
|
+
def value(self) -> str:
|
150
|
+
if self._value is None:
|
151
|
+
return None
|
152
|
+
|
153
|
+
return self.formatter().encode(self._value)
|
154
|
+
|
155
|
+
def setValue(self, v):
|
156
|
+
self._value = v
|
157
|
+
if v is None:
|
158
|
+
self.setText(self.name())
|
159
|
+
self.setProperty('hasValue', False)
|
160
|
+
else:
|
161
|
+
self.setText(self.value())
|
162
|
+
self.setProperty('hasValue', True)
|
163
|
+
|
164
|
+
self.setStyle(QApplication.style())
|
165
|
+
|
166
|
+
def items(self):
|
167
|
+
return [self._formatter.encode(i) for i in self._items]
|
168
|
+
|
169
|
+
def setItems(self, items: Iterable):
|
170
|
+
self._items = list(items)
|
171
|
+
|
172
|
+
def formatter(self):
|
173
|
+
return self._formatter
|
174
|
+
|
175
|
+
def setFormatter(self, formatter):
|
176
|
+
self._formatter = formatter or PickerColumnFormatter()
|
177
|
+
|
178
|
+
def name(self):
|
179
|
+
return self._name
|
180
|
+
|
181
|
+
def setName(self, name: str):
|
182
|
+
if self.text() == self.name():
|
183
|
+
self.setText(name)
|
184
|
+
|
185
|
+
self._name = name
|
186
|
+
|
187
|
+
|
188
|
+
def checkColumnIndex(func):
|
189
|
+
""" check whether the index is out of range """
|
190
|
+
|
191
|
+
def wrapper(picker, index: int, *args, **kwargs):
|
192
|
+
if not 0 <= index < len(picker.columns):
|
193
|
+
return
|
194
|
+
|
195
|
+
return func(picker, index, *args, **kwargs)
|
196
|
+
|
197
|
+
return wrapper
|
198
|
+
|
199
|
+
|
200
|
+
class PickerBase(QPushButton):
|
201
|
+
""" Picker base class """
|
202
|
+
|
203
|
+
def __init__(self, parent=None):
|
204
|
+
super().__init__(parent=parent)
|
205
|
+
self.columns = [] # type: List[PickerColumnButton]
|
206
|
+
|
207
|
+
self.lightSelectedBackgroundColor = QColor()
|
208
|
+
self.darkSelectedBackgroundColor = QColor()
|
209
|
+
|
210
|
+
self._isResetEnabled = False
|
211
|
+
self.hBoxLayout = QHBoxLayout(self)
|
212
|
+
|
213
|
+
self.hBoxLayout.setSpacing(0)
|
214
|
+
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
|
215
|
+
self.hBoxLayout.setSizeConstraint(QHBoxLayout.SetFixedSize)
|
216
|
+
|
217
|
+
FluentStyleSheet.TIME_PICKER.apply(self)
|
218
|
+
self.clicked.connect(self._showPanel)
|
219
|
+
|
220
|
+
def setSelectedBackgroundColor(self, light, dark):
|
221
|
+
""" set the background color of selected row """
|
222
|
+
self.lightSelectedBackgroundColor = QColor(light)
|
223
|
+
self.darkSelectedBackgroundColor = QColor(dark)
|
224
|
+
|
225
|
+
def addColumn(self, name: str, items: Iterable, width: int, align=Qt.AlignCenter,
|
226
|
+
formatter: PickerColumnFormatter = None):
|
227
|
+
""" add column
|
228
|
+
|
229
|
+
Parameters
|
230
|
+
----------
|
231
|
+
name: str
|
232
|
+
the name of column
|
233
|
+
|
234
|
+
items: Iterable
|
235
|
+
the items of column
|
236
|
+
|
237
|
+
width: int
|
238
|
+
the width of column
|
239
|
+
|
240
|
+
align: Qt.AlignmentFlag
|
241
|
+
the text alignment of button
|
242
|
+
|
243
|
+
formatter: PickerColumnFormatter
|
244
|
+
the formatter of column
|
245
|
+
"""
|
246
|
+
# create column button
|
247
|
+
button = PickerColumnButton(name, items, width, align, formatter, self)
|
248
|
+
self.columns.append(button)
|
249
|
+
|
250
|
+
self.hBoxLayout.addWidget(button, 0, Qt.AlignLeft)
|
251
|
+
|
252
|
+
# update the style of buttons
|
253
|
+
for btn in self.columns[:-1]:
|
254
|
+
btn.setProperty('hasBorder', True)
|
255
|
+
btn.setStyle(QApplication.style())
|
256
|
+
|
257
|
+
@checkColumnIndex
|
258
|
+
def setColumnAlignment(self, index: int, align=Qt.AlignCenter):
|
259
|
+
""" set the text alignment of specified column """
|
260
|
+
self.columns[index].setAlignment(align)
|
261
|
+
|
262
|
+
@checkColumnIndex
|
263
|
+
def setColumnWidth(self, index: int, width: int):
|
264
|
+
""" set the width of specified column """
|
265
|
+
self.columns[index].setFixedWidth(width)
|
266
|
+
|
267
|
+
@checkColumnIndex
|
268
|
+
def setColumnTight(self, index: int):
|
269
|
+
""" make the specified column to be tight """
|
270
|
+
fm = self.fontMetrics()
|
271
|
+
w = max(fm.width(i) for i in self.columns[index].items) + 30
|
272
|
+
self.setColumnWidth(index, w)
|
273
|
+
|
274
|
+
@checkColumnIndex
|
275
|
+
def setColumnVisible(self, index: int, isVisible: bool):
|
276
|
+
""" set the text alignment of specified column """
|
277
|
+
self.columns[index].setVisible(isVisible)
|
278
|
+
|
279
|
+
def value(self):
|
280
|
+
return [c.value() for c in self.columns if c.isVisible()]
|
281
|
+
|
282
|
+
def initialValue(self):
|
283
|
+
return [c.initialValue() for c in self.columns if c.isVisible()]
|
284
|
+
|
285
|
+
@checkColumnIndex
|
286
|
+
def setColumnValue(self, index: int, value):
|
287
|
+
self.columns[index].setValue(value)
|
288
|
+
|
289
|
+
@checkColumnIndex
|
290
|
+
def setColumnInitialValue(self, index: int, value):
|
291
|
+
self.columns[index].setInitialValue(value)
|
292
|
+
|
293
|
+
@checkColumnIndex
|
294
|
+
def setColumnFormatter(self, index: int, formatter: PickerColumnFormatter):
|
295
|
+
self.columns[index].setFormatter(formatter)
|
296
|
+
|
297
|
+
@checkColumnIndex
|
298
|
+
def setColumnItems(self, index: int, items: Iterable):
|
299
|
+
self.columns[index].setItems(items)
|
300
|
+
|
301
|
+
@checkColumnIndex
|
302
|
+
def encodeValue(self, index: int, value):
|
303
|
+
""" convert original value to formatted value """
|
304
|
+
return self.columns[index].formatter().encode(value)
|
305
|
+
|
306
|
+
@checkColumnIndex
|
307
|
+
def decodeValue(self, index: int, value):
|
308
|
+
""" convert formatted value to origin value """
|
309
|
+
return self.columns[index].formatter().decode(value)
|
310
|
+
|
311
|
+
@checkColumnIndex
|
312
|
+
def setColumn(self, index: int, name: str, items: Iterable, width: int, align=Qt.AlignCenter):
|
313
|
+
""" set column
|
314
|
+
|
315
|
+
Parameters
|
316
|
+
----------
|
317
|
+
index: int
|
318
|
+
the index of column
|
319
|
+
|
320
|
+
name: str
|
321
|
+
the name of column
|
322
|
+
|
323
|
+
items: Iterable
|
324
|
+
the items of column
|
325
|
+
|
326
|
+
width: int
|
327
|
+
the width of column
|
328
|
+
|
329
|
+
align: Qt.AlignmentFlag
|
330
|
+
the text alignment of button
|
331
|
+
"""
|
332
|
+
button = self.columns[index]
|
333
|
+
button.setText(name)
|
334
|
+
button.setFixedWidth(width)
|
335
|
+
button.setAlignment(align)
|
336
|
+
|
337
|
+
def clearColumns(self):
|
338
|
+
""" clear columns """
|
339
|
+
while self.columns:
|
340
|
+
btn = self.columns.pop()
|
341
|
+
self.hBoxLayout.removeWidget(btn)
|
342
|
+
# The parent of btn should be explicitly set to None to remove references from its parent.
|
343
|
+
# Otherwise, GC will not collect and remove it until the end of it parent life-cycle
|
344
|
+
btn.setParent(None)
|
345
|
+
btn.deleteLater()
|
346
|
+
|
347
|
+
def enterEvent(self, e):
|
348
|
+
self._setButtonProperty('enter', True)
|
349
|
+
|
350
|
+
def leaveEvent(self, e):
|
351
|
+
self._setButtonProperty('enter', False)
|
352
|
+
|
353
|
+
def mousePressEvent(self, e):
|
354
|
+
self._setButtonProperty('pressed', True)
|
355
|
+
super().mousePressEvent(e)
|
356
|
+
|
357
|
+
def mouseReleaseEvent(self, e):
|
358
|
+
self._setButtonProperty('pressed', False)
|
359
|
+
super().mouseReleaseEvent(e)
|
360
|
+
|
361
|
+
def _setButtonProperty(self, name, value):
|
362
|
+
""" send event to picker buttons """
|
363
|
+
for button in self.columns:
|
364
|
+
button.setProperty(name, value)
|
365
|
+
button.setStyle(QApplication.style())
|
366
|
+
|
367
|
+
def panelInitialValue(self):
|
368
|
+
""" initial value of panel """
|
369
|
+
return self.value()
|
370
|
+
|
371
|
+
def _showPanel(self):
|
372
|
+
""" show panel """
|
373
|
+
panel = PickerPanel(self)
|
374
|
+
for column in self.columns:
|
375
|
+
if column.isVisible():
|
376
|
+
panel.addColumn(column.items(), column.width(), column.align())
|
377
|
+
|
378
|
+
panel.setValue(self.panelInitialValue())
|
379
|
+
panel.setResetEnabled(self.isRestEnabled())
|
380
|
+
panel.setSelectedBackgroundColor(
|
381
|
+
self.lightSelectedBackgroundColor, self.darkSelectedBackgroundColor)
|
382
|
+
|
383
|
+
panel.confirmed.connect(self._onConfirmed)
|
384
|
+
panel.resetted.connect(self.reset)
|
385
|
+
panel.columnValueChanged.connect(
|
386
|
+
lambda i, v: self._onColumnValueChanged(panel, i, v))
|
387
|
+
|
388
|
+
w = panel.vBoxLayout.sizeHint().width() - self.width()
|
389
|
+
panel.exec(self.mapToGlobal(QPoint(-w//2, -37 * 4)))
|
390
|
+
|
391
|
+
def _onConfirmed(self, value: list):
|
392
|
+
for i, v in enumerate(value):
|
393
|
+
self.setColumnValue(i, v)
|
394
|
+
|
395
|
+
def reset(self):
|
396
|
+
for i in range(len(self.columns)):
|
397
|
+
self.setColumnValue(i, None)
|
398
|
+
|
399
|
+
def _onColumnValueChanged(self, panel, index: int, value: str):
|
400
|
+
""" column value changed slot """
|
401
|
+
pass
|
402
|
+
|
403
|
+
def isRestEnabled(self):
|
404
|
+
return self._isResetEnabled
|
405
|
+
|
406
|
+
def setResetEnabled(self, isEnabled: bool):
|
407
|
+
""" set the visibility of reset button """
|
408
|
+
self._isResetEnabled = isEnabled
|
409
|
+
|
410
|
+
|
411
|
+
class PickerToolButton(TransparentToolButton):
|
412
|
+
""" Picker tool button """
|
413
|
+
|
414
|
+
def _drawIcon(self, icon, painter, rect):
|
415
|
+
if self.isPressed:
|
416
|
+
painter.setOpacity(1)
|
417
|
+
|
418
|
+
super()._drawIcon(icon, painter, rect)
|
419
|
+
|
420
|
+
|
421
|
+
class PickerPanel(QWidget):
|
422
|
+
""" picker panel """
|
423
|
+
|
424
|
+
confirmed = Signal(list)
|
425
|
+
resetted = Signal()
|
426
|
+
columnValueChanged = Signal(int, str)
|
427
|
+
|
428
|
+
def __init__(self, parent=None):
|
429
|
+
super().__init__(parent=parent)
|
430
|
+
self.itemHeight = 37
|
431
|
+
self.listWidgets = [] # type: List[CycleListWidget]
|
432
|
+
|
433
|
+
self.view = QFrame(self)
|
434
|
+
self.itemMaskWidget = ItemMaskWidget(self.listWidgets, self)
|
435
|
+
self.hSeparatorWidget = SeparatorWidget(Qt.Horizontal, self.view)
|
436
|
+
self.yesButton = PickerToolButton(FluentIcon.ACCEPT, self.view)
|
437
|
+
self.resetButton = PickerToolButton(FluentIcon.CANCEL, self.view)
|
438
|
+
self.cancelButton = PickerToolButton(FluentIcon.CLOSE, self.view)
|
439
|
+
|
440
|
+
self.hBoxLayout = QHBoxLayout(self)
|
441
|
+
self.listLayout = QHBoxLayout()
|
442
|
+
self.buttonLayout = QHBoxLayout()
|
443
|
+
self.vBoxLayout = QVBoxLayout(self.view)
|
444
|
+
|
445
|
+
self.__initWidget()
|
446
|
+
|
447
|
+
def __initWidget(self):
|
448
|
+
self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint |
|
449
|
+
Qt.NoDropShadowWindowHint)
|
450
|
+
self.setAttribute(Qt.WA_TranslucentBackground)
|
451
|
+
|
452
|
+
self.setShadowEffect()
|
453
|
+
self.yesButton.setIconSize(QSize(16, 16))
|
454
|
+
self.resetButton.setIconSize(QSize(16, 16))
|
455
|
+
self.cancelButton.setIconSize(QSize(13, 13))
|
456
|
+
self.yesButton.setFixedHeight(33)
|
457
|
+
self.cancelButton.setFixedHeight(33)
|
458
|
+
self.resetButton.setFixedHeight(33)
|
459
|
+
|
460
|
+
self.hBoxLayout.setContentsMargins(12, 8, 12, 20)
|
461
|
+
self.hBoxLayout.addWidget(self.view, 1, Qt.AlignCenter)
|
462
|
+
self.hBoxLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize)
|
463
|
+
|
464
|
+
self.vBoxLayout.setSpacing(0)
|
465
|
+
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
|
466
|
+
self.vBoxLayout.addLayout(self.listLayout, 1)
|
467
|
+
self.vBoxLayout.addWidget(self.hSeparatorWidget)
|
468
|
+
self.vBoxLayout.addLayout(self.buttonLayout, 1)
|
469
|
+
self.vBoxLayout.setSizeConstraint(QVBoxLayout.SetMinimumSize)
|
470
|
+
|
471
|
+
self.buttonLayout.setSpacing(6)
|
472
|
+
self.buttonLayout.setContentsMargins(3, 3, 3, 3)
|
473
|
+
self.buttonLayout.addWidget(self.yesButton)
|
474
|
+
self.buttonLayout.addWidget(self.resetButton)
|
475
|
+
self.buttonLayout.addWidget(self.cancelButton)
|
476
|
+
self.yesButton.setSizePolicy(
|
477
|
+
QSizePolicy.Expanding, QSizePolicy.Expanding)
|
478
|
+
self.resetButton.setSizePolicy(
|
479
|
+
QSizePolicy.Expanding, QSizePolicy.Expanding)
|
480
|
+
self.cancelButton.setSizePolicy(
|
481
|
+
QSizePolicy.Expanding, QSizePolicy.Expanding)
|
482
|
+
|
483
|
+
self.yesButton.clicked.connect(self._fadeOut)
|
484
|
+
self.yesButton.clicked.connect(
|
485
|
+
lambda: self.confirmed.emit(self.value()))
|
486
|
+
self.cancelButton.clicked.connect(self._fadeOut)
|
487
|
+
self.resetButton.clicked.connect(self.resetted)
|
488
|
+
self.resetButton.clicked.connect(self._fadeOut)
|
489
|
+
|
490
|
+
self.setResetEnabled(False)
|
491
|
+
|
492
|
+
self.view.setObjectName('view')
|
493
|
+
FluentStyleSheet.TIME_PICKER.apply(self)
|
494
|
+
|
495
|
+
def setShadowEffect(self, blurRadius=30, offset=(0, 8), color=QColor(0, 0, 0, 30)):
|
496
|
+
""" add shadow to dialog """
|
497
|
+
self.shadowEffect = QGraphicsDropShadowEffect(self.view)
|
498
|
+
self.shadowEffect.setBlurRadius(blurRadius)
|
499
|
+
self.shadowEffect.setOffset(*offset)
|
500
|
+
self.shadowEffect.setColor(color)
|
501
|
+
self.view.setGraphicsEffect(None)
|
502
|
+
self.view.setGraphicsEffect(self.shadowEffect)
|
503
|
+
|
504
|
+
def setResetEnabled(self, isEnabled: bool):
|
505
|
+
""" set the visibility of reset button """
|
506
|
+
self.resetButton.setVisible(isEnabled)
|
507
|
+
|
508
|
+
def setSelectedBackgroundColor(self, light, dark):
|
509
|
+
self.itemMaskWidget.setCustomBackgroundColor(light, dark)
|
510
|
+
|
511
|
+
def isResetEnabled(self):
|
512
|
+
return self.resetButton.isVisible()
|
513
|
+
|
514
|
+
def addColumn(self, items: Iterable, width: int, align=Qt.AlignCenter):
|
515
|
+
""" add one column to view
|
516
|
+
|
517
|
+
Parameters
|
518
|
+
----------
|
519
|
+
items: Iterable[Any]
|
520
|
+
the items to be added
|
521
|
+
|
522
|
+
width: int
|
523
|
+
the width of item
|
524
|
+
|
525
|
+
align: Qt.AlignmentFlag
|
526
|
+
the text alignment of item
|
527
|
+
"""
|
528
|
+
if self.listWidgets:
|
529
|
+
self.listLayout.addWidget(SeparatorWidget(Qt.Vertical))
|
530
|
+
|
531
|
+
w = CycleListWidget(items, QSize(width, self.itemHeight), align, self)
|
532
|
+
w.vScrollBar.valueChanged.connect(self.itemMaskWidget.update)
|
533
|
+
|
534
|
+
N = len(self.listWidgets)
|
535
|
+
w.currentItemChanged.connect(
|
536
|
+
lambda i, n=N: self.columnValueChanged.emit(n, i.text()))
|
537
|
+
|
538
|
+
self.listWidgets.append(w)
|
539
|
+
self.listLayout.addWidget(w)
|
540
|
+
|
541
|
+
def resizeEvent(self, e):
|
542
|
+
self.itemMaskWidget.resize(self.view.width()-3, self.itemHeight)
|
543
|
+
m = self.hBoxLayout.contentsMargins()
|
544
|
+
self.itemMaskWidget.move(m.left()+2, m.top() + 148)
|
545
|
+
|
546
|
+
def value(self):
|
547
|
+
""" return the value of columns """
|
548
|
+
return [i.currentItem().text() for i in self.listWidgets]
|
549
|
+
|
550
|
+
def setValue(self, value: list):
|
551
|
+
""" set the value of columns """
|
552
|
+
if len(value) != len(self.listWidgets):
|
553
|
+
return
|
554
|
+
|
555
|
+
for v, w in zip(value, self.listWidgets):
|
556
|
+
w.setSelectedItem(v)
|
557
|
+
|
558
|
+
def columnValue(self, index: int) -> str:
|
559
|
+
""" return the value of specified column """
|
560
|
+
if not 0 <= index < len(self.listWidgets):
|
561
|
+
return
|
562
|
+
|
563
|
+
return self.listWidgets[index].currentItem().text()
|
564
|
+
|
565
|
+
def setColumnValue(self, index: int, value: str):
|
566
|
+
""" set the value of specified column """
|
567
|
+
if not 0 <= index < len(self.listWidgets):
|
568
|
+
return
|
569
|
+
|
570
|
+
self.listWidgets[index].setSelectedItem(value)
|
571
|
+
|
572
|
+
def column(self, index: int):
|
573
|
+
""" return the list widget of specified column """
|
574
|
+
return self.listWidgets[index]
|
575
|
+
|
576
|
+
def exec(self, pos, ani=True):
|
577
|
+
""" show panel
|
578
|
+
|
579
|
+
Parameters
|
580
|
+
----------
|
581
|
+
pos: QPoint
|
582
|
+
pop-up position
|
583
|
+
|
584
|
+
ani: bool
|
585
|
+
Whether to show pop-up animation
|
586
|
+
"""
|
587
|
+
if self.isVisible():
|
588
|
+
return
|
589
|
+
|
590
|
+
# show before running animation, or the height calculation will be wrong
|
591
|
+
self.show()
|
592
|
+
|
593
|
+
rect = getCurrentScreenGeometry()
|
594
|
+
w, h = self.width() + 5, self.height()
|
595
|
+
pos.setX(
|
596
|
+
min(pos.x() - self.layout().contentsMargins().left(), rect.right() - w))
|
597
|
+
pos.setY(max(rect.top(), min(pos.y() - 4, rect.bottom() - h + 5)))
|
598
|
+
self.move(pos)
|
599
|
+
|
600
|
+
if not ani:
|
601
|
+
return
|
602
|
+
|
603
|
+
self.isExpanded = False
|
604
|
+
self.ani = QPropertyAnimation(self.view, b'windowOpacity', self)
|
605
|
+
self.ani.valueChanged.connect(self._onAniValueChanged)
|
606
|
+
self.ani.setStartValue(0)
|
607
|
+
self.ani.setEndValue(1)
|
608
|
+
self.ani.setDuration(150)
|
609
|
+
self.ani.setEasingCurve(QEasingCurve.OutQuad)
|
610
|
+
self.ani.start()
|
611
|
+
|
612
|
+
def _onAniValueChanged(self, opacity):
|
613
|
+
m = self.layout().contentsMargins()
|
614
|
+
w = self.view.width() + m.left() + m.right() + 120
|
615
|
+
h = self.view.height() + m.top() + m.bottom() + 12
|
616
|
+
if not self.isExpanded:
|
617
|
+
y = int(h / 2 * (1 - opacity))
|
618
|
+
self.setMask(QRegion(0, y, w, h-y*2))
|
619
|
+
else:
|
620
|
+
y = int(h / 3 * (1 - opacity))
|
621
|
+
self.setMask(QRegion(0, y, w, h-y*2))
|
622
|
+
|
623
|
+
def _fadeOut(self):
|
624
|
+
self.isExpanded = True
|
625
|
+
self.ani = QPropertyAnimation(self, b'windowOpacity', self)
|
626
|
+
self.ani.valueChanged.connect(self._onAniValueChanged)
|
627
|
+
self.ani.finished.connect(self.deleteLater)
|
628
|
+
self.ani.setStartValue(1)
|
629
|
+
self.ani.setEndValue(0)
|
630
|
+
self.ani.setDuration(150)
|
631
|
+
self.ani.setEasingCurve(QEasingCurve.OutQuad)
|
632
|
+
self.ani.start()
|