Python-FastUI-Widgets 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. fastuiwidgets/__init__.py +12 -0
  2. fastuiwidgets/_rc/__init__.py +0 -0
  3. fastuiwidgets/_rc/resource.py +98835 -0
  4. fastuiwidgets/common/__init__.py +12 -0
  5. fastuiwidgets/common/animation.py +530 -0
  6. fastuiwidgets/common/auto_wrap.py +164 -0
  7. fastuiwidgets/common/color.py +95 -0
  8. fastuiwidgets/common/config.py +423 -0
  9. fastuiwidgets/common/exception_handler.py +31 -0
  10. fastuiwidgets/common/font.py +38 -0
  11. fastuiwidgets/common/icon.py +703 -0
  12. fastuiwidgets/common/image_utils.py +198 -0
  13. fastuiwidgets/common/overload.py +47 -0
  14. fastuiwidgets/common/router.py +133 -0
  15. fastuiwidgets/common/screen.py +25 -0
  16. fastuiwidgets/common/smooth_scroll.py +141 -0
  17. fastuiwidgets/common/style_sheet.py +512 -0
  18. fastuiwidgets/common/theme_listener.py +27 -0
  19. fastuiwidgets/common/translator.py +14 -0
  20. fastuiwidgets/components/__init__.py +6 -0
  21. fastuiwidgets/components/date_time/__init__.py +4 -0
  22. fastuiwidgets/components/date_time/calendar_picker.py +121 -0
  23. fastuiwidgets/components/date_time/calendar_view.py +671 -0
  24. fastuiwidgets/components/date_time/date_picker.py +245 -0
  25. fastuiwidgets/components/date_time/fast_calendar_view.py +487 -0
  26. fastuiwidgets/components/date_time/picker_base.py +632 -0
  27. fastuiwidgets/components/date_time/time_picker.py +223 -0
  28. fastuiwidgets/components/dialog_box/__init__.py +6 -0
  29. fastuiwidgets/components/dialog_box/color_dialog.py +414 -0
  30. fastuiwidgets/components/dialog_box/dialog.py +167 -0
  31. fastuiwidgets/components/dialog_box/folder_list_dialog.py +307 -0
  32. fastuiwidgets/components/dialog_box/mask_dialog_base.py +120 -0
  33. fastuiwidgets/components/dialog_box/message_box_base.py +92 -0
  34. fastuiwidgets/components/dialog_box/message_dialog.py +65 -0
  35. fastuiwidgets/components/layout/__init__.py +3 -0
  36. fastuiwidgets/components/layout/expand_layout.py +96 -0
  37. fastuiwidgets/components/layout/flow_layout.py +236 -0
  38. fastuiwidgets/components/layout/v_box_layout.py +41 -0
  39. fastuiwidgets/components/material/__init__.py +6 -0
  40. fastuiwidgets/components/material/acrylic_combo_box.py +96 -0
  41. fastuiwidgets/components/material/acrylic_flyout.py +105 -0
  42. fastuiwidgets/components/material/acrylic_line_edit.py +27 -0
  43. fastuiwidgets/components/material/acrylic_menu.py +204 -0
  44. fastuiwidgets/components/material/acrylic_tool_tip.py +39 -0
  45. fastuiwidgets/components/material/acrylic_widget.py +42 -0
  46. fastuiwidgets/components/navigation/__init__.py +9 -0
  47. fastuiwidgets/components/navigation/breadcrumb.py +350 -0
  48. fastuiwidgets/components/navigation/navigation_bar.py +416 -0
  49. fastuiwidgets/components/navigation/navigation_interface.py +268 -0
  50. fastuiwidgets/components/navigation/navigation_panel.py +657 -0
  51. fastuiwidgets/components/navigation/navigation_widget.py +686 -0
  52. fastuiwidgets/components/navigation/pivot.py +272 -0
  53. fastuiwidgets/components/navigation/segmented_widget.py +174 -0
  54. fastuiwidgets/components/settings/__init__.py +8 -0
  55. fastuiwidgets/components/settings/custom_color_setting_card.py +139 -0
  56. fastuiwidgets/components/settings/expand_setting_card.py +390 -0
  57. fastuiwidgets/components/settings/folder_list_setting_card.py +134 -0
  58. fastuiwidgets/components/settings/options_setting_card.py +86 -0
  59. fastuiwidgets/components/settings/setting_card.py +449 -0
  60. fastuiwidgets/components/settings/setting_card_group.py +48 -0
  61. fastuiwidgets/components/widgets/__init__.py +41 -0
  62. fastuiwidgets/components/widgets/acrylic_label.py +261 -0
  63. fastuiwidgets/components/widgets/button.py +1059 -0
  64. fastuiwidgets/components/widgets/card_widget.py +369 -0
  65. fastuiwidgets/components/widgets/check_box.py +203 -0
  66. fastuiwidgets/components/widgets/combo_box.py +556 -0
  67. fastuiwidgets/components/widgets/command_bar.py +636 -0
  68. fastuiwidgets/components/widgets/cycle_list_widget.py +251 -0
  69. fastuiwidgets/components/widgets/flip_view.py +430 -0
  70. fastuiwidgets/components/widgets/flyout.py +521 -0
  71. fastuiwidgets/components/widgets/frameless_window.py +49 -0
  72. fastuiwidgets/components/widgets/icon_widget.py +53 -0
  73. fastuiwidgets/components/widgets/info_badge.py +483 -0
  74. fastuiwidgets/components/widgets/info_bar.py +596 -0
  75. fastuiwidgets/components/widgets/label.py +553 -0
  76. fastuiwidgets/components/widgets/line_edit.py +551 -0
  77. fastuiwidgets/components/widgets/list_view.py +158 -0
  78. fastuiwidgets/components/widgets/menu.py +1318 -0
  79. fastuiwidgets/components/widgets/pips_pager.py +331 -0
  80. fastuiwidgets/components/widgets/progress_bar.py +311 -0
  81. fastuiwidgets/components/widgets/progress_ring.py +212 -0
  82. fastuiwidgets/components/widgets/scroll_area.py +125 -0
  83. fastuiwidgets/components/widgets/scroll_bar.py +673 -0
  84. fastuiwidgets/components/widgets/separator.py +43 -0
  85. fastuiwidgets/components/widgets/slider.py +307 -0
  86. fastuiwidgets/components/widgets/spin_box.py +306 -0
  87. fastuiwidgets/components/widgets/stacked_widget.py +211 -0
  88. fastuiwidgets/components/widgets/state_tool_tip.py +188 -0
  89. fastuiwidgets/components/widgets/switch_button.py +312 -0
  90. fastuiwidgets/components/widgets/tab_view.py +804 -0
  91. fastuiwidgets/components/widgets/table_view.py +360 -0
  92. fastuiwidgets/components/widgets/teaching_tip.py +657 -0
  93. fastuiwidgets/components/widgets/tool_tip.py +460 -0
  94. fastuiwidgets/components/widgets/tree_view.py +216 -0
  95. fastuiwidgets/multimedia/__init__.py +3 -0
  96. fastuiwidgets/multimedia/media_play_bar.py +319 -0
  97. fastuiwidgets/multimedia/media_player.py +124 -0
  98. fastuiwidgets/multimedia/video_widget.py +93 -0
  99. fastuiwidgets/window/__init__.py +2 -0
  100. fastuiwidgets/window/fluent_window.py +413 -0
  101. fastuiwidgets/window/splash_screen.py +92 -0
  102. fastuiwidgets/window/stacked_widget.py +66 -0
  103. python_fastui_widgets-1.0.0.dist-info/METADATA +30 -0
  104. python_fastui_widgets-1.0.0.dist-info/RECORD +107 -0
  105. python_fastui_widgets-1.0.0.dist-info/WHEEL +5 -0
  106. python_fastui_widgets-1.0.0.dist-info/licenses/LICENSE +674 -0
  107. python_fastui_widgets-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,251 @@
1
+ # coding:utf-8
2
+ from typing import Iterable
3
+
4
+ from PySide6.QtCore import Qt, Signal, QSize, QEvent, QRectF
5
+ from PySide6.QtGui import QPainter
6
+ from PySide6.QtWidgets import QListWidget, QListWidgetItem, QToolButton
7
+
8
+ from .scroll_area import SmoothScrollBar
9
+ from ...common.icon import FluentIcon, isDarkTheme
10
+
11
+
12
+ class ScrollButton(QToolButton):
13
+ """ Scroll button """
14
+
15
+ def __init__(self, icon: FluentIcon, parent=None):
16
+ super().__init__(parent=parent)
17
+ self._icon = icon
18
+ self.isPressed = False
19
+ self.installEventFilter(self)
20
+
21
+ def eventFilter(self, obj, e: QEvent):
22
+ if obj is self:
23
+ if e.type() == QEvent.MouseButtonPress:
24
+ self.isPressed = True
25
+ self.update()
26
+ elif e.type() == QEvent.MouseButtonRelease:
27
+ self.isPressed = False
28
+ self.update()
29
+
30
+ return super().eventFilter(obj, e)
31
+
32
+ def paintEvent(self, e):
33
+ super().paintEvent(e)
34
+ painter = QPainter(self)
35
+ painter.setRenderHints(QPainter.Antialiasing)
36
+
37
+ if not self.isPressed:
38
+ w, h = 10, 10
39
+ else:
40
+ w, h = 8, 8
41
+
42
+ x = (self.width() - w) / 2
43
+ y = (self.height() - h) / 2
44
+
45
+ if not isDarkTheme():
46
+ self._icon.render(painter, QRectF(x, y, w, h), fill="#5e5e5e")
47
+ else:
48
+ self._icon.render(painter, QRectF(x, y, w, h))
49
+
50
+
51
+ class CycleListWidget(QListWidget):
52
+ """ Cycle list widget """
53
+
54
+ currentItemChanged = Signal(QListWidgetItem)
55
+
56
+ def __init__(self, items: Iterable, itemSize: QSize, align=Qt.AlignCenter, parent=None):
57
+ """
58
+ Parameters
59
+ ----------
60
+ items: Iterable[Any]
61
+ the items to be added
62
+
63
+ itemSize: QSize
64
+ the size of item
65
+
66
+ align: Qt.AlignmentFlag
67
+ the text alignment of item
68
+
69
+ parent: QWidget
70
+ parent widget
71
+ """
72
+ super().__init__(parent=parent)
73
+ self.itemSize = itemSize
74
+ self.align = align
75
+
76
+ self.upButton = ScrollButton(FluentIcon.CARE_UP_SOLID, self)
77
+ self.downButton = ScrollButton(FluentIcon.CARE_DOWN_SOLID, self)
78
+ self.scrollDuration = 250
79
+ self.originItems = list(items)
80
+
81
+ self.vScrollBar = SmoothScrollBar(Qt.Vertical, self)
82
+ self.visibleNumber = 9
83
+
84
+ # repeat adding items to achieve circular scrolling
85
+ self.setItems(items)
86
+
87
+ self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel)
88
+ self.vScrollBar.setScrollAnimation(self.scrollDuration)
89
+ self.vScrollBar.setForceHidden(True)
90
+
91
+ self.setViewportMargins(0, 0, 0, 0)
92
+ self.setFixedSize(itemSize.width()+8,
93
+ itemSize.height()*self.visibleNumber)
94
+
95
+ # hide scroll bar
96
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
97
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
98
+ self.upButton.hide()
99
+ self.downButton.hide()
100
+
101
+ self.upButton.clicked.connect(self.scrollUp)
102
+ self.downButton.clicked.connect(self.scrollDown)
103
+ self.itemClicked.connect(self._onItemClicked)
104
+
105
+ self.installEventFilter(self)
106
+
107
+ def setItems(self, items: list):
108
+ """ set items in the list
109
+
110
+ Parameters
111
+ ----------
112
+ items: Iterable[Any]
113
+ the items to be added
114
+
115
+ itemSize: QSize
116
+ the size of item
117
+
118
+ align: Qt.AlignmentFlag
119
+ the text alignment of item
120
+ """
121
+ self.clear()
122
+ self._createItems(items)
123
+
124
+ def _createItems(self, items: list):
125
+ N = len(items)
126
+ self.isCycle = N > self.visibleNumber
127
+
128
+ if self.isCycle:
129
+ for _ in range(2):
130
+ self._addColumnItems(items)
131
+
132
+ self._currentIndex = len(items)
133
+ super().scrollToItem(
134
+ self.item(self.currentIndex()-self.visibleNumber//2), QListWidget.PositionAtTop)
135
+ else:
136
+ n = self.visibleNumber // 2 # add empty items to enable scrolling
137
+
138
+ self._addColumnItems(['']*n, True)
139
+ self._addColumnItems(items)
140
+ self._addColumnItems(['']*n, True)
141
+
142
+ self._currentIndex = n
143
+
144
+ def _addColumnItems(self, items, disabled=False):
145
+ for i in items:
146
+ item = QListWidgetItem(str(i), self)
147
+ item.setSizeHint(self.itemSize)
148
+ item.setTextAlignment(self.align | Qt.AlignVCenter)
149
+ if disabled:
150
+ item.setFlags(Qt.NoItemFlags)
151
+
152
+ self.addItem(item)
153
+
154
+ def _onItemClicked(self, item):
155
+ self.setCurrentIndex(self.row(item))
156
+ self.scrollToItem(self.currentItem())
157
+
158
+ def setSelectedItem(self, text: str):
159
+ """ set the selected item """
160
+ if text is None:
161
+ return
162
+
163
+ items = self.findItems(str(text), Qt.MatchExactly)
164
+ if not items:
165
+ return
166
+
167
+ if len(items) >= 2:
168
+ self.setCurrentIndex(self.row(items[1]))
169
+ else:
170
+ self.setCurrentIndex(self.row(items[0]))
171
+
172
+ super().scrollToItem(self.currentItem(), QListWidget.ScrollHint.PositionAtCenter)
173
+
174
+ def scrollToItem(self, item: QListWidgetItem, hint=QListWidget.ScrollHint.PositionAtCenter):
175
+ """ scroll to item """
176
+ # scroll to center position
177
+ index = self.row(item)
178
+ y = item.sizeHint().height() * (index - self.visibleNumber // 2)
179
+ self.vScrollBar.scrollTo(y)
180
+
181
+ # clear selection
182
+ self.clearSelection()
183
+ item.setSelected(False)
184
+
185
+ self.currentItemChanged.emit(item)
186
+
187
+ def wheelEvent(self, e):
188
+ if e.angleDelta().y() < 0:
189
+ self.scrollDown()
190
+ else:
191
+ self.scrollUp()
192
+
193
+ def scrollDown(self):
194
+ """ scroll down an item """
195
+ self.setCurrentIndex(self.currentIndex() + 1)
196
+ self.scrollToItem(self.currentItem())
197
+
198
+ def scrollUp(self):
199
+ """ scroll up an item """
200
+ self.setCurrentIndex(self.currentIndex() - 1)
201
+ self.scrollToItem(self.currentItem())
202
+
203
+ def enterEvent(self, e):
204
+ self.upButton.show()
205
+ self.downButton.show()
206
+
207
+ def leaveEvent(self, e):
208
+ self.upButton.hide()
209
+ self.downButton.hide()
210
+
211
+ def resizeEvent(self, e):
212
+ self.upButton.resize(self.width(), 34)
213
+ self.downButton.resize(self.width(), 34)
214
+ self.downButton.move(0, self.height() - 34)
215
+
216
+ def eventFilter(self, obj, e: QEvent):
217
+ if obj is not self or e.type() != QEvent.KeyPress:
218
+ return super().eventFilter(obj, e)
219
+
220
+ if e.key() == Qt.Key_Down:
221
+ self.scrollDown()
222
+ return True
223
+ elif e.key() == Qt.Key_Up:
224
+ self.scrollUp()
225
+ return True
226
+
227
+ return super().eventFilter(obj, e)
228
+
229
+ def currentItem(self):
230
+ return self.item(self.currentIndex())
231
+
232
+ def currentIndex(self):
233
+ return self._currentIndex
234
+
235
+ def setCurrentIndex(self, index: int):
236
+ if not self.isCycle:
237
+ n = self.visibleNumber // 2
238
+ self._currentIndex = max(
239
+ n, min(n + len(self.originItems) - 1, index))
240
+ else:
241
+ N = self.count() // 2
242
+ m = (self.visibleNumber + 1) // 2
243
+ self._currentIndex = index
244
+
245
+ # scroll to center to achieve circular scrolling
246
+ if index >= self.count() - m:
247
+ self._currentIndex = N + index - self.count()
248
+ super().scrollToItem(self.item(self.currentIndex() - 1), self.ScrollHint.PositionAtCenter)
249
+ elif index <= m - 1:
250
+ self._currentIndex = N + index
251
+ super().scrollToItem(self.item(N + index + 1), self.ScrollHint.PositionAtCenter)
@@ -0,0 +1,430 @@
1
+ # coding:utf-8
2
+ from typing import List, Union
3
+
4
+ from PySide6.QtCore import Qt, Signal, QModelIndex, QSize, Property, QRectF, QPropertyAnimation, QSizeF
5
+ from PySide6.QtGui import QPixmap, QPainter, QColor, QImage, QWheelEvent, QPainterPath, QImageReader
6
+ from PySide6.QtWidgets import QStyleOptionViewItem, QListWidget, QStyledItemDelegate, QListWidgetItem
7
+
8
+ from ...common.overload import singledispatchmethod
9
+ from ...common.style_sheet import isDarkTheme, FluentStyleSheet
10
+ from ...common.icon import drawIcon, FluentIcon
11
+ from .scroll_bar import SmoothScrollBar
12
+ from .button import ToolButton
13
+
14
+
15
+ class ScrollButton(ToolButton):
16
+ """ Scroll button """
17
+
18
+ def _postInit(self):
19
+ self._opacity = 0
20
+ self.opacityAni = QPropertyAnimation(self, b'opacity', self)
21
+ self.opacityAni.setDuration(150)
22
+
23
+ def getOpacity(self):
24
+ return self._opacity
25
+
26
+ def setOpacity(self, o: float):
27
+ self._opacity = o
28
+ self.update()
29
+
30
+ def isTransparent(self):
31
+ return self.opacity == 0
32
+
33
+ def fadeIn(self):
34
+ self.opacityAni.setStartValue(self.opacity)
35
+ self.opacityAni.setEndValue(1)
36
+ self.opacityAni.start()
37
+
38
+ def fadeOut(self):
39
+ self.opacityAni.setStartValue(self.opacity)
40
+ self.opacityAni.setEndValue(0)
41
+ self.opacityAni.start()
42
+
43
+ def paintEvent(self, e):
44
+ painter = QPainter(self)
45
+ painter.setRenderHints(QPainter.Antialiasing)
46
+ painter.setPen(Qt.NoPen)
47
+ painter.setOpacity(self.opacity)
48
+
49
+ # draw background
50
+ if not isDarkTheme():
51
+ painter.setBrush(QColor(252, 252, 252, 217))
52
+ else:
53
+ painter.setBrush(QColor(44, 44, 44, 245))
54
+
55
+ painter.drawRoundedRect(self.rect(), 4, 4)
56
+
57
+ # draw icon
58
+ if isDarkTheme():
59
+ color = QColor(255, 255, 255)
60
+ opacity = 0.773 if self.isHover or self.isPressed else 0.541
61
+ else:
62
+ color = QColor(0, 0, 0)
63
+ opacity = 0.616 if self.isHover or self.isPressed else 0.45
64
+
65
+ painter.setOpacity(self.opacity * opacity)
66
+
67
+ s = 6 if self.isPressed else 8
68
+ w, h = self.width(), self.height()
69
+ x, y = (w - s) / 2, (h - s) / 2
70
+ drawIcon(self._icon, painter, QRectF(x, y, s, s), fill=color.name())
71
+
72
+ opacity = Property(float, getOpacity, setOpacity)
73
+
74
+
75
+ class FlipImageDelegate(QStyledItemDelegate):
76
+ """ Flip view image delegate """
77
+
78
+ def __init__(self, parent=None):
79
+ super().__init__(parent)
80
+ self.borderRadius = 0
81
+
82
+ def itemSize(self, index: int):
83
+ p = self.parent() # type: FlipView
84
+ return p.item(index).sizeHint()
85
+
86
+ def setBorderRadius(self, radius: int):
87
+ self.borderRadius = radius
88
+ self.parent().viewport().update()
89
+
90
+ def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
91
+ painter.save()
92
+ painter.setRenderHints(QPainter.Antialiasing)
93
+ painter.setPen(Qt.NoPen)
94
+
95
+ size = self.itemSize(index.row()) # type: QSize
96
+ p = self.parent() # type: FlipView
97
+
98
+ # draw image
99
+ r = p.devicePixelRatioF()
100
+ image = index.data(Qt.UserRole) # type: QImage
101
+ if image is None:
102
+ return painter.restore()
103
+
104
+ # lazy load image
105
+ if image.isNull() and index.data(Qt.ItemDataRole.DisplayRole):
106
+ image.load(index.data(Qt.ItemDataRole.DisplayRole))
107
+ index.model().setData(index, image, Qt.ItemDataRole.UserRole)
108
+
109
+ x = option.rect.x() + int((option.rect.width() - size.width()) / 2)
110
+ y = option.rect.y() + int((option.rect.height() - size.height()) / 2)
111
+ rect = QRectF(x, y, size.width(), size.height())
112
+
113
+ # clipped path
114
+ path = QPainterPath()
115
+ path.addRoundedRect(rect, self.borderRadius, self.borderRadius)
116
+ subPath = QPainterPath()
117
+ subPath.addRoundedRect(QRectF(p.rect()), self.borderRadius, self.borderRadius)
118
+ path = path.intersected(subPath)
119
+
120
+ image = image.scaled(size * r, p.aspectRatioMode, Qt.SmoothTransformation)
121
+ painter.setClipPath(path)
122
+
123
+ # center crop image
124
+ if p.aspectRatioMode == Qt.AspectRatioMode.KeepAspectRatioByExpanding:
125
+ iw, ih = image.width(), image.height()
126
+ size = QSizeF(size) * r
127
+ x, y = (iw - size.width()) / 2, (ih - size.height()) / 2
128
+ image = image.copy(int(x), int(y), int(size.width()), int(size.height()))
129
+
130
+ painter.drawImage(rect, image)
131
+ painter.restore()
132
+
133
+
134
+ class FlipView(QListWidget):
135
+ """ Flip view
136
+
137
+ Constructors
138
+ ------------
139
+ * FlipView(`parent`: QWidget = None)
140
+ * FlipView(`orient`: Qt.Orientation, `parent`: QWidget = None)
141
+ """
142
+
143
+ currentIndexChanged = Signal(int)
144
+
145
+ @singledispatchmethod
146
+ def __init__(self, parent=None):
147
+ super().__init__(parent=parent)
148
+ self.orientation = Qt.Horizontal
149
+ self._postInit()
150
+
151
+ @__init__.register
152
+ def _(self, orientation: Qt.Orientation, parent=None):
153
+ super().__init__(parent=parent)
154
+ self.orientation = orientation
155
+ self._postInit()
156
+
157
+ def _postInit(self):
158
+ self.isHover = False
159
+ self._currentIndex = -1
160
+ self._aspectRatioMode = Qt.AspectRatioMode.IgnoreAspectRatio
161
+ self._itemSize = QSize(480, 270) # 16:9
162
+
163
+ self.delegate = FlipImageDelegate(self)
164
+ self.scrollBar = SmoothScrollBar(self.orientation, self)
165
+
166
+ self.scrollBar.setScrollAnimation(500)
167
+ self.scrollBar.setForceHidden(True)
168
+
169
+ # self.setUniformItemSizes(True)
170
+ self.setMinimumSize(self.itemSize)
171
+ self.setItemDelegate(self.delegate)
172
+ self.setMovement(QListWidget.Static)
173
+ self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel)
174
+ self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel)
175
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
176
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
177
+ FluentStyleSheet.FLIP_VIEW.apply(self)
178
+
179
+ if self.isHorizontal():
180
+ self.setFlow(QListWidget.LeftToRight)
181
+ self.preButton = ScrollButton(FluentIcon.CARE_LEFT_SOLID, self)
182
+ self.nextButton = ScrollButton(FluentIcon.CARE_RIGHT_SOLID, self)
183
+ self.preButton.setFixedSize(16, 38)
184
+ self.nextButton.setFixedSize(16, 38)
185
+ else:
186
+ self.preButton = ScrollButton(FluentIcon.CARE_UP_SOLID, self)
187
+ self.nextButton = ScrollButton(FluentIcon.CARE_DOWN_SOLID, self)
188
+ self.preButton.setFixedSize(38, 16)
189
+ self.nextButton.setFixedSize(38, 16)
190
+
191
+ # connect signal to slot
192
+ self.preButton.clicked.connect(self.scrollPrevious)
193
+ self.nextButton.clicked.connect(self.scrollNext)
194
+
195
+ def isHorizontal(self):
196
+ return self.orientation == Qt.Horizontal
197
+
198
+ def setItemSize(self, size: QSize):
199
+ """ set the size of item """
200
+ if size == self.itemSize:
201
+ return
202
+
203
+ self._itemSize = size
204
+
205
+ for i in range(self.count()):
206
+ self._adjustItemSize(self.item(i))
207
+
208
+ self.viewport().update()
209
+
210
+ def getItemSize(self):
211
+ """ get the size of item """
212
+ return self._itemSize
213
+
214
+ def setBorderRadius(self, radius: int):
215
+ """ set the border radius of item """
216
+ self.delegate.setBorderRadius(radius)
217
+
218
+ def getBorderRadius(self):
219
+ return self.delegate.borderRadius
220
+
221
+ def scrollPrevious(self):
222
+ """ scroll to previous item """
223
+ self.setCurrentIndex(self.currentIndex() - 1)
224
+
225
+ def scrollNext(self):
226
+ """ scroll to next item """
227
+ self.setCurrentIndex(self.currentIndex() + 1)
228
+
229
+ def setCurrentIndex(self, index: int):
230
+ """ set current index """
231
+ if not 0 <= index < self.count() or index == self.currentIndex():
232
+ return
233
+
234
+ self.scrollToIndex(index)
235
+
236
+ # update the visibility of scroll button
237
+ if index == 0:
238
+ self.preButton.fadeOut()
239
+ elif self.preButton.isTransparent() and self.isHover:
240
+ self.preButton.fadeIn()
241
+
242
+ if index == self.count() - 1:
243
+ self.nextButton.fadeOut()
244
+ elif self.nextButton.isTransparent() and self.isHover:
245
+ self.nextButton.fadeIn()
246
+
247
+ # fire signal
248
+ self.currentIndexChanged.emit(index)
249
+
250
+ def scrollToIndex(self, index):
251
+ if not 0 <= index < self.count():
252
+ return
253
+
254
+ self._currentIndex = index
255
+
256
+ if self.isHorizontal():
257
+ value = sum(self.item(i).sizeHint().width() for i in range(index))
258
+ else:
259
+ value = sum(self.item(i).sizeHint().height() for i in range(index))
260
+
261
+ value += (2 * index + 1) * self.spacing()
262
+ self.scrollBar.scrollTo(value)
263
+
264
+ def currentIndex(self):
265
+ return self._currentIndex
266
+
267
+ def image(self, index: int):
268
+ if not 0 <= index < self.count():
269
+ return QImage()
270
+
271
+ return self.item(index).data(Qt.UserRole)
272
+
273
+ def addImage(self, image: Union[QImage, QPixmap, str]):
274
+ """ add image """
275
+ self.addImages([image])
276
+
277
+ def addImages(self, images: List[Union[QImage, QPixmap, str]], targetSize: QSize = None):
278
+ """ add images """
279
+ if not images:
280
+ return
281
+
282
+ N = self.count()
283
+ self.addItems([''] * len(images))
284
+
285
+ for i in range(N, self.count()):
286
+ self.setItemImage(i, images[i - N], targetSize=targetSize)
287
+
288
+ if self.currentIndex() < 0:
289
+ self._currentIndex = 0
290
+
291
+ def setItemImage(self, index: int, image: Union[QImage, QPixmap, str], targetSize: QSize = None):
292
+ """ set the image of specified item """
293
+ if not 0 <= index < self.count():
294
+ return
295
+
296
+ item = self.item(index)
297
+
298
+ # convert image to QImage
299
+ if isinstance(image, QPixmap):
300
+ image = image.toImage()
301
+
302
+ # lazy load
303
+ if isinstance(image, QImage):
304
+ item.setData(Qt.ItemDataRole.UserRole, image)
305
+ else:
306
+ item.setData(Qt.ItemDataRole.UserRole, QImage())
307
+ item.setData(Qt.ItemDataRole.DisplayRole, image)
308
+
309
+ self._adjustItemSize(item)
310
+
311
+ def _adjustItemSize(self, item: QListWidgetItem):
312
+ image = self.itemImage(self.row(item), load=False)
313
+
314
+ if not image.isNull():
315
+ size = image.size()
316
+ else:
317
+ imagePath = item.data(Qt.ItemDataRole.DisplayRole) or ""
318
+ size = QImageReader(imagePath).size().expandedTo(QSize(1, 1))
319
+
320
+ if self.aspectRatioMode == Qt.AspectRatioMode.KeepAspectRatio:
321
+ if self.isHorizontal():
322
+ h = self.itemSize.height()
323
+ w = int(size.width() * h / size.height())
324
+ else:
325
+ w = self.itemSize.width()
326
+ h = int(size.height() * w / size.width())
327
+ else:
328
+ w, h = self.itemSize.width(), self.itemSize.height()
329
+
330
+ item.setSizeHint(QSize(w, h))
331
+
332
+ def itemImage(self, index: int, load=True) -> QImage:
333
+ """ get the image of specified item
334
+
335
+ Parameters
336
+ ----------
337
+ index: int
338
+ the index of image
339
+
340
+ load: bool
341
+ whether to load image data
342
+ """
343
+ if not 0 <= index < self.count():
344
+ return
345
+
346
+ item = self.item(index)
347
+ image = item.data(Qt.ItemDataRole.UserRole) # type: QImage
348
+
349
+ if image is None:
350
+ return QImage()
351
+
352
+ imagePath = item.data(Qt.ItemDataRole.DisplayRole)
353
+ if image.isNull() and imagePath and load:
354
+ image.load(imagePath)
355
+
356
+ return image
357
+
358
+ def resizeEvent(self, e):
359
+ w, h = self.width(), self.height()
360
+ bw, bh = self.preButton.width(), self.preButton.height()
361
+
362
+ if self.isHorizontal():
363
+ self.preButton.move(2, int(h / 2 - bh / 2))
364
+ self.nextButton.move(w - bw - 2, int(h / 2 - bh / 2))
365
+ else:
366
+ self.preButton.move(int(w / 2 - bw / 2), 2)
367
+ self.nextButton.move(int(w / 2 - bw / 2), h - bh - 2)
368
+
369
+ def enterEvent(self, e):
370
+ super().enterEvent(e)
371
+ self.isHover = True
372
+
373
+ if self.currentIndex() > 0:
374
+ self.preButton.fadeIn()
375
+
376
+ if self.currentIndex() < self.count() - 1:
377
+ self.nextButton.fadeIn()
378
+
379
+ def leaveEvent(self, e):
380
+ super().leaveEvent(e)
381
+ self.isHover = False
382
+ self.preButton.fadeOut()
383
+ self.nextButton.fadeOut()
384
+
385
+ def showEvent(self, e):
386
+ self.scrollBar.duration = 0
387
+ self.scrollToIndex(self.currentIndex())
388
+ self.scrollBar.duration = 500
389
+
390
+ def wheelEvent(self, e: QWheelEvent):
391
+ e.setAccepted(True)
392
+ if self.scrollBar.ani.state() == QPropertyAnimation.Running:
393
+ return
394
+
395
+ if e.angleDelta().y() < 0:
396
+ self.scrollNext()
397
+ else:
398
+ self.scrollPrevious()
399
+
400
+ def getAspectRatioMode(self):
401
+ return self._aspectRatioMode
402
+
403
+ def setAspectRatioMode(self, mode: Qt.AspectRatioMode):
404
+ if mode == self.aspectRatioMode:
405
+ return
406
+
407
+ self._aspectRatioMode = mode
408
+
409
+ for i in range(self.count()):
410
+ self._adjustItemSize(self.item(i))
411
+
412
+ self.viewport().update()
413
+
414
+ itemSize = Property(QSize, getItemSize, setItemSize)
415
+ borderRadius = Property(int, getBorderRadius, setBorderRadius)
416
+ aspectRatioMode = Property(Qt.AspectRatioMode, getAspectRatioMode, setAspectRatioMode)
417
+
418
+
419
+ class HorizontalFlipView(FlipView):
420
+ """ Horizontal flip view """
421
+
422
+ def __init__(self, parent=None):
423
+ super().__init__(Qt.Horizontal, parent)
424
+
425
+
426
+ class VerticalFlipView(FlipView):
427
+ """ Vertical flip view """
428
+
429
+ def __init__(self, parent=None):
430
+ super().__init__(Qt.Vertical, parent)