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,551 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
from typing import List, Union
|
3
|
+
from PySide6.QtCore import QSize, Qt, QRectF, Signal, QPoint, QTimer, QEvent, QAbstractItemModel, Property, QModelIndex
|
4
|
+
from PySide6.QtGui import QPainter, QPainterPath, QIcon, QColor, QAction
|
5
|
+
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLineEdit, QToolButton, QTextEdit,
|
6
|
+
QPlainTextEdit, QCompleter, QStyle, QWidget, QTextBrowser)
|
7
|
+
|
8
|
+
|
9
|
+
from ...common.style_sheet import FluentStyleSheet, themeColor
|
10
|
+
from ...common.icon import isDarkTheme, FluentIconBase, drawIcon
|
11
|
+
from ...common.icon import FluentIcon as FIF
|
12
|
+
from ...common.font import setFont
|
13
|
+
from ...common.color import FluentSystemColor, autoFallbackThemeColor
|
14
|
+
from .tool_tip import ToolTipFilter
|
15
|
+
from .menu import LineEditMenu, TextEditMenu, RoundMenu, MenuAnimationType, IndicatorMenuItemDelegate
|
16
|
+
from .scroll_bar import SmoothScrollDelegate
|
17
|
+
|
18
|
+
|
19
|
+
class LineEditButton(QToolButton):
|
20
|
+
""" Line edit button """
|
21
|
+
|
22
|
+
def __init__(self, icon: Union[str, QIcon, FluentIconBase], parent=None):
|
23
|
+
super().__init__(parent=parent)
|
24
|
+
self._icon = icon
|
25
|
+
self._action = None
|
26
|
+
self.isPressed = False
|
27
|
+
self.setFixedSize(31, 23)
|
28
|
+
self.setIconSize(QSize(10, 10))
|
29
|
+
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
30
|
+
self.setObjectName('lineEditButton')
|
31
|
+
FluentStyleSheet.LINE_EDIT.apply(self)
|
32
|
+
|
33
|
+
def setAction(self, action: QAction):
|
34
|
+
self._action = action
|
35
|
+
self._onActionChanged()
|
36
|
+
|
37
|
+
self.clicked.connect(action.trigger)
|
38
|
+
action.toggled.connect(self.setChecked)
|
39
|
+
action.changed.connect(self._onActionChanged)
|
40
|
+
|
41
|
+
self.installEventFilter(ToolTipFilter(self, 700))
|
42
|
+
|
43
|
+
def _onActionChanged(self):
|
44
|
+
action = self.action()
|
45
|
+
self.setIcon(action.icon())
|
46
|
+
self.setToolTip(action.toolTip())
|
47
|
+
self.setEnabled(action.isEnabled())
|
48
|
+
self.setCheckable(action.isCheckable())
|
49
|
+
self.setChecked(action.isChecked())
|
50
|
+
|
51
|
+
def action(self):
|
52
|
+
return self._action
|
53
|
+
|
54
|
+
def setIcon(self, icon: Union[str, FluentIconBase, QIcon]):
|
55
|
+
self._icon = icon
|
56
|
+
self.update()
|
57
|
+
|
58
|
+
def mousePressEvent(self, e):
|
59
|
+
self.isPressed = True
|
60
|
+
super().mousePressEvent(e)
|
61
|
+
|
62
|
+
def mouseReleaseEvent(self, e):
|
63
|
+
self.isPressed = False
|
64
|
+
super().mouseReleaseEvent(e)
|
65
|
+
|
66
|
+
def paintEvent(self, e):
|
67
|
+
super().paintEvent(e)
|
68
|
+
painter = QPainter(self)
|
69
|
+
painter.setRenderHints(QPainter.Antialiasing |
|
70
|
+
QPainter.SmoothPixmapTransform)
|
71
|
+
|
72
|
+
iw, ih = self.iconSize().width(), self.iconSize().height()
|
73
|
+
w, h = self.width(), self.height()
|
74
|
+
rect = QRectF((w - iw)/2, (h - ih)/2, iw, ih)
|
75
|
+
|
76
|
+
if self.isPressed:
|
77
|
+
painter.setOpacity(0.7)
|
78
|
+
|
79
|
+
if isDarkTheme():
|
80
|
+
drawIcon(self._icon, painter, rect)
|
81
|
+
else:
|
82
|
+
drawIcon(self._icon, painter, rect, fill='#656565')
|
83
|
+
|
84
|
+
|
85
|
+
class LineEdit(QLineEdit):
|
86
|
+
""" Line edit """
|
87
|
+
|
88
|
+
def __init__(self, parent=None):
|
89
|
+
super().__init__(parent=parent)
|
90
|
+
self._isClearButtonEnabled = False
|
91
|
+
self._completer = None # type: QCompleter
|
92
|
+
self._completerMenu = None # type: CompleterMenu
|
93
|
+
self._isError = False
|
94
|
+
self.lightFocusedBorderColor = QColor()
|
95
|
+
self.darkFocusedBorderColor = QColor()
|
96
|
+
|
97
|
+
self.leftButtons = [] # type: List[LineEditButton]
|
98
|
+
self.rightButtons = [] # type: List[LineEditButton]
|
99
|
+
|
100
|
+
self.setProperty("transparent", True)
|
101
|
+
FluentStyleSheet.LINE_EDIT.apply(self)
|
102
|
+
self.setFixedHeight(33)
|
103
|
+
self.setAttribute(Qt.WA_MacShowFocusRect, False)
|
104
|
+
setFont(self)
|
105
|
+
|
106
|
+
self.hBoxLayout = QHBoxLayout(self)
|
107
|
+
self.clearButton = LineEditButton(FIF.CLOSE, self)
|
108
|
+
|
109
|
+
self.clearButton.setFixedSize(29, 25)
|
110
|
+
self.clearButton.hide()
|
111
|
+
|
112
|
+
self.hBoxLayout.setSpacing(3)
|
113
|
+
self.hBoxLayout.setContentsMargins(4, 4, 4, 4)
|
114
|
+
self.hBoxLayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
115
|
+
self.hBoxLayout.addWidget(self.clearButton, 0, Qt.AlignRight)
|
116
|
+
|
117
|
+
self.clearButton.clicked.connect(self.clear)
|
118
|
+
self.textChanged.connect(self.__onTextChanged)
|
119
|
+
self.textEdited.connect(self.__onTextEdited)
|
120
|
+
|
121
|
+
def isError(self):
|
122
|
+
return self._isError
|
123
|
+
|
124
|
+
def setError(self, isError: bool):
|
125
|
+
""" set the error status """
|
126
|
+
if isError == self.isError():
|
127
|
+
return
|
128
|
+
|
129
|
+
self._isError = isError
|
130
|
+
self.update()
|
131
|
+
|
132
|
+
def setCustomFocusedBorderColor(self, light, dark):
|
133
|
+
""" set the border color in focused status
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
light, dark: str | QColor | Qt.GlobalColor
|
138
|
+
border color in light/dark theme mode
|
139
|
+
"""
|
140
|
+
self.lightFocusedBorderColor = QColor(light)
|
141
|
+
self.darkFocusedBorderColor = QColor(dark)
|
142
|
+
self.update()
|
143
|
+
|
144
|
+
def focusedBorderColor(self):
|
145
|
+
if self.isError():
|
146
|
+
return FluentSystemColor.CRITICAL_FOREGROUND.color()
|
147
|
+
|
148
|
+
return autoFallbackThemeColor(self.lightFocusedBorderColor, self.darkFocusedBorderColor)
|
149
|
+
|
150
|
+
def setClearButtonEnabled(self, enable: bool):
|
151
|
+
self._isClearButtonEnabled = enable
|
152
|
+
self._adjustTextMargins()
|
153
|
+
|
154
|
+
def isClearButtonEnabled(self) -> bool:
|
155
|
+
return self._isClearButtonEnabled
|
156
|
+
|
157
|
+
def setCompleter(self, completer: QCompleter):
|
158
|
+
self._completer = completer
|
159
|
+
|
160
|
+
def completer(self):
|
161
|
+
return self._completer
|
162
|
+
|
163
|
+
def addAction(self, action: QAction, position=QLineEdit.ActionPosition.TrailingPosition):
|
164
|
+
QWidget.addAction(self, action)
|
165
|
+
|
166
|
+
button = LineEditButton(action.icon())
|
167
|
+
button.setAction(action)
|
168
|
+
button.setFixedWidth(29)
|
169
|
+
|
170
|
+
if position == QLineEdit.ActionPosition.LeadingPosition:
|
171
|
+
self.hBoxLayout.insertWidget(len(self.leftButtons), button, 0, Qt.AlignLeading)
|
172
|
+
if not self.leftButtons:
|
173
|
+
self.hBoxLayout.insertStretch(1, 1)
|
174
|
+
|
175
|
+
self.leftButtons.append(button)
|
176
|
+
else:
|
177
|
+
self.rightButtons.append(button)
|
178
|
+
self.hBoxLayout.addWidget(button, 0, Qt.AlignRight)
|
179
|
+
|
180
|
+
self._adjustTextMargins()
|
181
|
+
|
182
|
+
def addActions(self, actions, position=QLineEdit.ActionPosition.TrailingPosition):
|
183
|
+
for action in actions:
|
184
|
+
self.addAction(action, position)
|
185
|
+
|
186
|
+
def _adjustTextMargins(self):
|
187
|
+
left = len(self.leftButtons) * 30
|
188
|
+
right = len(self.rightButtons) * 30 + 28 * self.isClearButtonEnabled()
|
189
|
+
m = self.textMargins()
|
190
|
+
self.setTextMargins(left, m.top(), right, m.bottom())
|
191
|
+
|
192
|
+
def focusOutEvent(self, e):
|
193
|
+
super().focusOutEvent(e)
|
194
|
+
self.clearButton.hide()
|
195
|
+
|
196
|
+
def focusInEvent(self, e):
|
197
|
+
super().focusInEvent(e)
|
198
|
+
if self.isClearButtonEnabled():
|
199
|
+
self.clearButton.setVisible(bool(self.text()))
|
200
|
+
|
201
|
+
def __onTextChanged(self, text):
|
202
|
+
""" text changed slot """
|
203
|
+
if self.isClearButtonEnabled():
|
204
|
+
self.clearButton.setVisible(bool(text) and self.hasFocus())
|
205
|
+
|
206
|
+
def __onTextEdited(self, text):
|
207
|
+
if not self.completer():
|
208
|
+
return
|
209
|
+
|
210
|
+
if self.text():
|
211
|
+
QTimer.singleShot(50, self._showCompleterMenu)
|
212
|
+
elif self._completerMenu:
|
213
|
+
self._completerMenu.close()
|
214
|
+
|
215
|
+
def setCompleterMenu(self, menu):
|
216
|
+
""" set completer menu
|
217
|
+
|
218
|
+
Parameters
|
219
|
+
----------
|
220
|
+
menu: CompleterMenu
|
221
|
+
completer menu
|
222
|
+
"""
|
223
|
+
menu.activated.connect(self._completer.activated)
|
224
|
+
menu.indexActivated.connect(lambda idx: self._completer.activated[QModelIndex].emit(idx))
|
225
|
+
self._completerMenu = menu
|
226
|
+
|
227
|
+
def _showCompleterMenu(self):
|
228
|
+
if not self.completer() or not self.text():
|
229
|
+
return
|
230
|
+
|
231
|
+
# create menu
|
232
|
+
if not self._completerMenu:
|
233
|
+
self.setCompleterMenu(CompleterMenu(self))
|
234
|
+
|
235
|
+
# add menu items
|
236
|
+
self.completer().setCompletionPrefix(self.text())
|
237
|
+
changed = self._completerMenu.setCompletion(self.completer().completionModel(), self.completer().completionColumn())
|
238
|
+
self._completerMenu.setMaxVisibleItems(self.completer().maxVisibleItems())
|
239
|
+
|
240
|
+
# show menu
|
241
|
+
if changed:
|
242
|
+
self._completerMenu.popup()
|
243
|
+
|
244
|
+
def contextMenuEvent(self, e):
|
245
|
+
menu = LineEditMenu(self)
|
246
|
+
menu.exec(e.globalPos(), ani=True)
|
247
|
+
|
248
|
+
def paintEvent(self, e):
|
249
|
+
super().paintEvent(e)
|
250
|
+
if not self.hasFocus():
|
251
|
+
return
|
252
|
+
|
253
|
+
painter = QPainter(self)
|
254
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
255
|
+
painter.setPen(Qt.NoPen)
|
256
|
+
|
257
|
+
m = self.contentsMargins()
|
258
|
+
path = QPainterPath()
|
259
|
+
w, h = self.width()-m.left()-m.right(), self.height()
|
260
|
+
path.addRoundedRect(QRectF(m.left(), h-10, w, 10), 5, 5)
|
261
|
+
|
262
|
+
rectPath = QPainterPath()
|
263
|
+
rectPath.addRect(m.left(), h-10, w, 8)
|
264
|
+
path = path.subtracted(rectPath)
|
265
|
+
|
266
|
+
painter.fillPath(path, self.focusedBorderColor())
|
267
|
+
|
268
|
+
|
269
|
+
class CompleterMenu(RoundMenu):
|
270
|
+
""" Completer menu """
|
271
|
+
|
272
|
+
activated = Signal(str)
|
273
|
+
indexActivated = Signal(QModelIndex)
|
274
|
+
|
275
|
+
def __init__(self, lineEdit: LineEdit):
|
276
|
+
super().__init__()
|
277
|
+
self.items = []
|
278
|
+
self.indexes = []
|
279
|
+
self.lineEdit = lineEdit
|
280
|
+
|
281
|
+
self.view.setViewportMargins(0, 2, 0, 6)
|
282
|
+
self.view.setObjectName('completerListWidget')
|
283
|
+
self.view.setItemDelegate(IndicatorMenuItemDelegate())
|
284
|
+
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
285
|
+
|
286
|
+
self.installEventFilter(self)
|
287
|
+
self.setItemHeight(33)
|
288
|
+
|
289
|
+
def setCompletion(self, model: QAbstractItemModel, column=0):
|
290
|
+
""" set the completion model """
|
291
|
+
items = []
|
292
|
+
self.indexes.clear()
|
293
|
+
for i in range(model.rowCount()):
|
294
|
+
items.append(model.data(model.index(i, column)))
|
295
|
+
self.indexes.append(model.index(i, column))
|
296
|
+
|
297
|
+
if self.items == items and self.isVisible():
|
298
|
+
return False
|
299
|
+
|
300
|
+
self.setItems(items)
|
301
|
+
return True
|
302
|
+
|
303
|
+
def setItems(self, items: List[str]):
|
304
|
+
""" set completion items """
|
305
|
+
self.view.clear()
|
306
|
+
|
307
|
+
self.items = items
|
308
|
+
self.view.addItems(items)
|
309
|
+
|
310
|
+
for i in range(self.view.count()):
|
311
|
+
item = self.view.item(i)
|
312
|
+
item.setSizeHint(QSize(1, self.itemHeight))
|
313
|
+
|
314
|
+
def _onItemClicked(self, item):
|
315
|
+
self._hideMenu(False)
|
316
|
+
self._onCompletionItemSelected(item.text(), self.view.row(item))
|
317
|
+
|
318
|
+
def eventFilter(self, obj, e: QEvent):
|
319
|
+
if e.type() != QEvent.KeyPress:
|
320
|
+
return super().eventFilter(obj, e)
|
321
|
+
|
322
|
+
# redirect input to line edit
|
323
|
+
self.lineEdit.event(e)
|
324
|
+
self.view.event(e)
|
325
|
+
|
326
|
+
if e.key() == Qt.Key_Escape:
|
327
|
+
self.close()
|
328
|
+
if e.key() in [Qt.Key_Enter, Qt.Key_Return] and self.view.currentRow() >= 0:
|
329
|
+
self._onCompletionItemSelected(self.view.currentItem().text(), self.view.currentRow())
|
330
|
+
self.close()
|
331
|
+
|
332
|
+
return super().eventFilter(obj, e)
|
333
|
+
|
334
|
+
def _onCompletionItemSelected(self, text, row):
|
335
|
+
self.lineEdit.setText(text)
|
336
|
+
self.activated.emit(text)
|
337
|
+
|
338
|
+
if 0 <= row < len(self.indexes):
|
339
|
+
self.indexActivated.emit(self.indexes[row])
|
340
|
+
|
341
|
+
def exec(self, pos, ani=True, aniType=MenuAnimationType.DROP_DOWN):
|
342
|
+
return super().exec(pos, ani, aniType)
|
343
|
+
|
344
|
+
def popup(self):
|
345
|
+
""" show menu """
|
346
|
+
if not self.items:
|
347
|
+
return self.close()
|
348
|
+
|
349
|
+
# adjust menu size
|
350
|
+
p = self.lineEdit
|
351
|
+
if self.view.width() < p.width():
|
352
|
+
self.view.setMinimumWidth(p.width())
|
353
|
+
self.adjustSize()
|
354
|
+
|
355
|
+
# determine the animation type by choosing the maximum height of view
|
356
|
+
x = -self.width()//2 + self.layout().contentsMargins().left() + p.width()//2
|
357
|
+
y = p.height() - self.layout().contentsMargins().top() + 2
|
358
|
+
pd = p.mapToGlobal(QPoint(x, y))
|
359
|
+
hd = self.view.heightForAnimation(pd, MenuAnimationType.FADE_IN_DROP_DOWN)
|
360
|
+
|
361
|
+
pu = p.mapToGlobal(QPoint(x, 7))
|
362
|
+
hu = self.view.heightForAnimation(pu, MenuAnimationType.FADE_IN_PULL_UP)
|
363
|
+
|
364
|
+
if hd >= hu:
|
365
|
+
pos = pd
|
366
|
+
aniType = MenuAnimationType.FADE_IN_DROP_DOWN
|
367
|
+
else:
|
368
|
+
pos = pu
|
369
|
+
aniType = MenuAnimationType.FADE_IN_PULL_UP
|
370
|
+
|
371
|
+
self.view.adjustSize(pos, aniType)
|
372
|
+
|
373
|
+
# update border style
|
374
|
+
self.view.setProperty('dropDown', aniType == MenuAnimationType.FADE_IN_DROP_DOWN)
|
375
|
+
self.view.setStyle(QApplication.style())
|
376
|
+
|
377
|
+
self.adjustSize()
|
378
|
+
self.exec(pos, aniType=aniType)
|
379
|
+
|
380
|
+
# remove the focus of menu
|
381
|
+
self.view.setFocusPolicy(Qt.NoFocus)
|
382
|
+
self.setFocusPolicy(Qt.NoFocus)
|
383
|
+
p.setFocus()
|
384
|
+
|
385
|
+
|
386
|
+
class SearchLineEdit(LineEdit):
|
387
|
+
""" Search line edit """
|
388
|
+
|
389
|
+
searchSignal = Signal(str)
|
390
|
+
clearSignal = Signal()
|
391
|
+
|
392
|
+
def __init__(self, parent=None):
|
393
|
+
super().__init__(parent)
|
394
|
+
self.searchButton = LineEditButton(FIF.SEARCH, self)
|
395
|
+
|
396
|
+
self.hBoxLayout.addWidget(self.searchButton, 0, Qt.AlignRight)
|
397
|
+
self.setClearButtonEnabled(True)
|
398
|
+
self.setTextMargins(0, 0, 59, 0)
|
399
|
+
|
400
|
+
self.searchButton.clicked.connect(self.search)
|
401
|
+
self.clearButton.clicked.connect(self.clearSignal)
|
402
|
+
|
403
|
+
def search(self):
|
404
|
+
""" emit search signal """
|
405
|
+
text = self.text().strip()
|
406
|
+
if text:
|
407
|
+
self.searchSignal.emit(text)
|
408
|
+
else:
|
409
|
+
self.clearSignal.emit()
|
410
|
+
|
411
|
+
def setClearButtonEnabled(self, enable: bool):
|
412
|
+
self._isClearButtonEnabled = enable
|
413
|
+
self.setTextMargins(0, 0, 28*enable+30, 0)
|
414
|
+
|
415
|
+
|
416
|
+
class EditLayer(QWidget):
|
417
|
+
""" Edit layer """
|
418
|
+
|
419
|
+
def __init__(self, parent):
|
420
|
+
super().__init__(parent=parent)
|
421
|
+
self.setAttribute(Qt.WA_TransparentForMouseEvents)
|
422
|
+
parent.installEventFilter(self)
|
423
|
+
|
424
|
+
def eventFilter(self, obj, e):
|
425
|
+
if obj is self.parent() and e.type() == QEvent.Resize:
|
426
|
+
self.resize(e.size())
|
427
|
+
|
428
|
+
return super().eventFilter(obj, e)
|
429
|
+
|
430
|
+
def paintEvent(self, e):
|
431
|
+
if not self.parent().hasFocus():
|
432
|
+
return
|
433
|
+
|
434
|
+
painter = QPainter(self)
|
435
|
+
painter.setRenderHints(QPainter.Antialiasing)
|
436
|
+
painter.setPen(Qt.NoPen)
|
437
|
+
|
438
|
+
m = self.contentsMargins()
|
439
|
+
path = QPainterPath()
|
440
|
+
w, h = self.width()-m.left()-m.right(), self.height()
|
441
|
+
path.addRoundedRect(QRectF(m.left(), h-10, w, 10), 5, 5)
|
442
|
+
|
443
|
+
rectPath = QPainterPath()
|
444
|
+
rectPath.addRect(m.left(), h-10, w, 7.5)
|
445
|
+
path = path.subtracted(rectPath)
|
446
|
+
|
447
|
+
painter.fillPath(path, themeColor())
|
448
|
+
|
449
|
+
|
450
|
+
class TextEdit(QTextEdit):
|
451
|
+
""" Text edit """
|
452
|
+
|
453
|
+
def __init__(self, parent=None):
|
454
|
+
super().__init__(parent=parent)
|
455
|
+
self.layer = EditLayer(self)
|
456
|
+
self.scrollDelegate = SmoothScrollDelegate(self)
|
457
|
+
FluentStyleSheet.LINE_EDIT.apply(self)
|
458
|
+
setFont(self)
|
459
|
+
|
460
|
+
def contextMenuEvent(self, e):
|
461
|
+
menu = TextEditMenu(self)
|
462
|
+
menu.exec(e.globalPos(), ani=True)
|
463
|
+
|
464
|
+
|
465
|
+
class PlainTextEdit(QPlainTextEdit):
|
466
|
+
""" Plain text edit """
|
467
|
+
|
468
|
+
def __init__(self, parent=None):
|
469
|
+
super().__init__(parent=parent)
|
470
|
+
self.layer = EditLayer(self)
|
471
|
+
self.scrollDelegate = SmoothScrollDelegate(self)
|
472
|
+
FluentStyleSheet.LINE_EDIT.apply(self)
|
473
|
+
setFont(self)
|
474
|
+
|
475
|
+
def contextMenuEvent(self, e):
|
476
|
+
menu = TextEditMenu(self)
|
477
|
+
menu.exec(e.globalPos())
|
478
|
+
|
479
|
+
|
480
|
+
class TextBrowser(QTextBrowser):
|
481
|
+
""" Text browser """
|
482
|
+
|
483
|
+
def __init__(self, parent=None):
|
484
|
+
super().__init__(parent)
|
485
|
+
self.layer = EditLayer(self)
|
486
|
+
self.scrollDelegate = SmoothScrollDelegate(self)
|
487
|
+
FluentStyleSheet.LINE_EDIT.apply(self)
|
488
|
+
setFont(self)
|
489
|
+
|
490
|
+
def contextMenuEvent(self, e):
|
491
|
+
menu = TextEditMenu(self)
|
492
|
+
menu.exec(e.globalPos())
|
493
|
+
|
494
|
+
|
495
|
+
class PasswordLineEdit(LineEdit):
|
496
|
+
""" Password line edit """
|
497
|
+
|
498
|
+
def __init__(self, parent=None):
|
499
|
+
super().__init__(parent)
|
500
|
+
self.viewButton = LineEditButton(FIF.VIEW, self)
|
501
|
+
|
502
|
+
self.setEchoMode(QLineEdit.Password)
|
503
|
+
self.setContextMenuPolicy(Qt.NoContextMenu)
|
504
|
+
self.hBoxLayout.addWidget(self.viewButton, 0, Qt.AlignRight)
|
505
|
+
self.setClearButtonEnabled(False)
|
506
|
+
|
507
|
+
self.viewButton.installEventFilter(self)
|
508
|
+
self.viewButton.setIconSize(QSize(13, 13))
|
509
|
+
self.viewButton.setFixedSize(29, 25)
|
510
|
+
|
511
|
+
def setPasswordVisible(self, isVisible: bool):
|
512
|
+
""" set the visibility of password """
|
513
|
+
if isVisible:
|
514
|
+
self.setEchoMode(QLineEdit.Normal)
|
515
|
+
else:
|
516
|
+
self.setEchoMode(QLineEdit.Password)
|
517
|
+
|
518
|
+
def isPasswordVisible(self):
|
519
|
+
return self.echoMode() == QLineEdit.Normal
|
520
|
+
|
521
|
+
def setClearButtonEnabled(self, enable: bool):
|
522
|
+
self._isClearButtonEnabled = enable
|
523
|
+
|
524
|
+
if self.viewButton.isHidden():
|
525
|
+
self.setTextMargins(0, 0, 28*enable, 0)
|
526
|
+
else:
|
527
|
+
self.setTextMargins(0, 0, 28*enable + 30, 0)
|
528
|
+
|
529
|
+
def setViewPasswordButtonVisible(self, isVisible: bool):
|
530
|
+
""" set the visibility of view password button """
|
531
|
+
self.viewButton.setVisible(isVisible)
|
532
|
+
|
533
|
+
def eventFilter(self, obj, e):
|
534
|
+
if obj is not self.viewButton or not self.isEnabled():
|
535
|
+
return super().eventFilter(obj, e)
|
536
|
+
|
537
|
+
if e.type() == QEvent.MouseButtonPress:
|
538
|
+
self.setPasswordVisible(True)
|
539
|
+
elif e.type() == QEvent.MouseButtonRelease:
|
540
|
+
self.setPasswordVisible(False)
|
541
|
+
|
542
|
+
return super().eventFilter(obj, e)
|
543
|
+
|
544
|
+
def inputMethodQuery(self, query: Qt.InputMethodQuery):
|
545
|
+
# Disable IME for PasswordLineEdit
|
546
|
+
if query == Qt.InputMethodQuery.ImEnabled:
|
547
|
+
return False
|
548
|
+
else:
|
549
|
+
return super().inputMethodQuery(query)
|
550
|
+
|
551
|
+
passwordVisible = Property(bool, isPasswordVisible, setPasswordVisible)
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
from typing import List, Union
|
3
|
+
|
4
|
+
from PySide6.QtCore import Qt, QModelIndex, Property
|
5
|
+
from PySide6.QtGui import QPainter
|
6
|
+
from PySide6.QtWidgets import QStyleOptionViewItem, QListView, QListView, QListWidget, QWidget
|
7
|
+
|
8
|
+
from .scroll_bar import SmoothScrollDelegate
|
9
|
+
from .table_view import TableItemDelegate
|
10
|
+
from ...common.style_sheet import FluentStyleSheet, themeColor
|
11
|
+
from ...common.color import autoFallbackThemeColor
|
12
|
+
|
13
|
+
|
14
|
+
class ListItemDelegate(TableItemDelegate):
|
15
|
+
""" List item delegate """
|
16
|
+
|
17
|
+
def __init__(self, parent: QListView):
|
18
|
+
super().__init__(parent)
|
19
|
+
|
20
|
+
def _drawBackground(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
|
21
|
+
painter.drawRoundedRect(option.rect, 5, 5)
|
22
|
+
|
23
|
+
def _drawIndicator(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
|
24
|
+
y, h = option.rect.y(), option.rect.height()
|
25
|
+
ph = round(0.35*h if self.pressedRow == index.row() else 0.257*h)
|
26
|
+
painter.setBrush(autoFallbackThemeColor(self.lightCheckedColor, self.darkCheckedColor))
|
27
|
+
painter.drawRoundedRect(0, ph + y, 3, h - 2*ph, 1.5, 1.5)
|
28
|
+
|
29
|
+
|
30
|
+
class ListBase:
|
31
|
+
|
32
|
+
def __init__(self, *args, **kwargs):
|
33
|
+
super().__init__(*args, **kwargs)
|
34
|
+
self.delegate = ListItemDelegate(self)
|
35
|
+
self.scrollDelegate = SmoothScrollDelegate(self)
|
36
|
+
self._isSelectRightClickedRow = False
|
37
|
+
|
38
|
+
FluentStyleSheet.LIST_VIEW.apply(self)
|
39
|
+
self.setItemDelegate(self.delegate)
|
40
|
+
self.setMouseTracking(True)
|
41
|
+
|
42
|
+
self.entered.connect(lambda i: self._setHoverRow(i.row()))
|
43
|
+
self.pressed.connect(lambda i: self._setPressedRow(i.row()))
|
44
|
+
|
45
|
+
def _setHoverRow(self, row: int):
|
46
|
+
""" set hovered row """
|
47
|
+
self.delegate.setHoverRow(row)
|
48
|
+
self.viewport().update()
|
49
|
+
|
50
|
+
def _setPressedRow(self, row: int):
|
51
|
+
""" set pressed row """
|
52
|
+
if self.selectionMode() == QListView.SelectionMode.NoSelection:
|
53
|
+
return
|
54
|
+
|
55
|
+
self.delegate.setPressedRow(row)
|
56
|
+
self.viewport().update()
|
57
|
+
|
58
|
+
def _setSelectedRows(self, indexes: List[QModelIndex]):
|
59
|
+
if self.selectionMode() == QListView.SelectionMode.NoSelection:
|
60
|
+
return
|
61
|
+
|
62
|
+
self.delegate.setSelectedRows(indexes)
|
63
|
+
self.viewport().update()
|
64
|
+
|
65
|
+
def leaveEvent(self, e):
|
66
|
+
QListView.leaveEvent(self, e)
|
67
|
+
self._setHoverRow(-1)
|
68
|
+
|
69
|
+
def resizeEvent(self, e):
|
70
|
+
QListView.resizeEvent(self, e)
|
71
|
+
self.viewport().update()
|
72
|
+
|
73
|
+
def keyPressEvent(self, e):
|
74
|
+
QListView.keyPressEvent(self, e)
|
75
|
+
self.updateSelectedRows()
|
76
|
+
|
77
|
+
def mousePressEvent(self, e):
|
78
|
+
if e.button() == Qt.LeftButton or self._isSelectRightClickedRow:
|
79
|
+
return QListView.mousePressEvent(self, e)
|
80
|
+
|
81
|
+
index = self.indexAt(e.pos())
|
82
|
+
if index.isValid():
|
83
|
+
self._setPressedRow(index.row())
|
84
|
+
|
85
|
+
QWidget.mousePressEvent(self, e)
|
86
|
+
|
87
|
+
def mouseReleaseEvent(self, e):
|
88
|
+
QListView.mouseReleaseEvent(self, e)
|
89
|
+
self.updateSelectedRows()
|
90
|
+
|
91
|
+
if self.indexAt(e.pos()).row() < 0 or e.button() == Qt.RightButton:
|
92
|
+
self._setPressedRow(-1)
|
93
|
+
|
94
|
+
def setItemDelegate(self, delegate: ListItemDelegate):
|
95
|
+
self.delegate = delegate
|
96
|
+
super().setItemDelegate(delegate)
|
97
|
+
|
98
|
+
def clearSelection(self):
|
99
|
+
QListView.clearSelection(self)
|
100
|
+
self.updateSelectedRows()
|
101
|
+
|
102
|
+
def setCurrentIndex(self, index: QModelIndex):
|
103
|
+
QListView.setCurrentIndex(self, index)
|
104
|
+
self.updateSelectedRows()
|
105
|
+
|
106
|
+
def updateSelectedRows(self):
|
107
|
+
self._setSelectedRows(self.selectedIndexes())
|
108
|
+
|
109
|
+
def setCheckedColor(self, light, dark):
|
110
|
+
""" set the color in checked status
|
111
|
+
|
112
|
+
Parameters
|
113
|
+
----------
|
114
|
+
light, dark: str | QColor | Qt.GlobalColor
|
115
|
+
color in light/dark theme mode
|
116
|
+
"""
|
117
|
+
self.delegate.setCheckedColor(light, dark)
|
118
|
+
|
119
|
+
|
120
|
+
class ListWidget(ListBase, QListWidget):
|
121
|
+
""" List widget """
|
122
|
+
|
123
|
+
def __init__(self, parent=None):
|
124
|
+
super().__init__(parent)
|
125
|
+
|
126
|
+
def setCurrentItem(self, item, command=None):
|
127
|
+
self.setCurrentRow(self.row(item), command)
|
128
|
+
|
129
|
+
def setCurrentRow(self, row: int, command=None):
|
130
|
+
if not command:
|
131
|
+
super().setCurrentRow(row)
|
132
|
+
else:
|
133
|
+
super().setCurrentRow(row, command)
|
134
|
+
|
135
|
+
self.updateSelectedRows()
|
136
|
+
|
137
|
+
def isSelectRightClickedRow(self):
|
138
|
+
return self._isSelectRightClickedRow
|
139
|
+
|
140
|
+
def setSelectRightClickedRow(self, isSelect: bool):
|
141
|
+
self._isSelectRightClickedRow = isSelect
|
142
|
+
|
143
|
+
selectRightClickedRow = Property(bool, isSelectRightClickedRow, setSelectRightClickedRow)
|
144
|
+
|
145
|
+
|
146
|
+
class ListView(ListBase, QListView):
|
147
|
+
""" List view """
|
148
|
+
|
149
|
+
def __init__(self, parent=None):
|
150
|
+
super().__init__(parent)
|
151
|
+
|
152
|
+
def isSelectRightClickedRow(self):
|
153
|
+
return self._isSelectRightClickedRow
|
154
|
+
|
155
|
+
def setSelectRightClickedRow(self, isSelect: bool):
|
156
|
+
self._isSelectRightClickedRow = isSelect
|
157
|
+
|
158
|
+
selectRightClickedRow = Property(bool, isSelectRightClickedRow, setSelectRightClickedRow)
|