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,671 @@
1
+ # coding: utf-8
2
+ from math import ceil
3
+ from collections import defaultdict, Counter
4
+ from typing import Tuple, Type
5
+
6
+ from PySide6.QtCore import (Qt, QRectF, Signal, QSize, QModelIndex, QDate, QCalendar, QEasingCurve, QPropertyAnimation,
7
+ QParallelAnimationGroup, QPoint, QRect, QStringListModel)
8
+ from PySide6.QtGui import QPainter, QColor, QCursor
9
+ from PySide6.QtWidgets import (QApplication, QFrame, QPushButton, QHBoxLayout, QVBoxLayout, QListWidget,
10
+ QListWidgetItem, QStyledItemDelegate, QStyle, QStyleOptionViewItem,
11
+ QLabel, QWidget, QStackedWidget, QGraphicsDropShadowEffect, QListView)
12
+
13
+ from ...common.icon import FluentIcon as FIF
14
+ from ...common.style_sheet import isDarkTheme, FluentStyleSheet, themeColor, ThemeColor
15
+ from ...common.font import getFont
16
+ from ...common.screen import getCurrentScreenGeometry
17
+ from ..widgets.button import TransparentToolButton
18
+ from ..widgets.scroll_bar import SmoothScrollBar
19
+
20
+
21
+ class ScrollButton(TransparentToolButton):
22
+ """ Scroll button """
23
+
24
+ def _drawIcon(self, icon, painter: QPainter, rect: QRectF):
25
+ pass
26
+
27
+ def paintEvent(self, e):
28
+ super().paintEvent(e)
29
+ painter = QPainter(self)
30
+ painter.setRenderHints(QPainter.Antialiasing)
31
+
32
+ if not self.isPressed:
33
+ w, h = 10, 10
34
+ else:
35
+ w, h = 9, 9
36
+
37
+ x = (self.width() - w) / 2
38
+ y = (self.height() - h) / 2
39
+
40
+ if not isDarkTheme():
41
+ self._icon.render(painter, QRectF(x, y, w, h), fill="#5e5e5e")
42
+ else:
43
+ self._icon.render(painter, QRectF(x, y, w, h), fill="#9c9c9c")
44
+
45
+
46
+ class ScrollItemDelegate(QStyledItemDelegate):
47
+
48
+ def __init__(self, min, max):
49
+ super().__init__()
50
+ self.setRange(min, max)
51
+ self.font = getFont()
52
+ self.pressedIndex = QModelIndex()
53
+ self.currentIndex = QModelIndex()
54
+ self.selectedIndex = QModelIndex()
55
+
56
+ def setRange(self, min, max):
57
+ self.min = min
58
+ self.max = max
59
+
60
+ def setPressedIndex(self, index: QModelIndex):
61
+ self.pressedIndex = index
62
+
63
+ def setCurrentIndex(self, index: QModelIndex):
64
+ self.currentIndex = index
65
+
66
+ def setSelectedIndex(self, index: QModelIndex):
67
+ self.selectedIndex = index
68
+
69
+ def paint(self, painter, option, index):
70
+ painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
71
+ self._drawBackground(painter, option, index)
72
+ self._drawText(painter, option, index)
73
+
74
+ def _drawBackground(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
75
+ painter.save()
76
+
77
+ # outer ring
78
+ if index != self.selectedIndex:
79
+ painter.setPen(Qt.NoPen)
80
+ else:
81
+ painter.setPen(themeColor())
82
+
83
+ if index == self.currentIndex:
84
+ if index == self.pressedIndex:
85
+ painter.setBrush(ThemeColor.LIGHT_2.color())
86
+ elif option.state & QStyle.State_MouseOver:
87
+ painter.setBrush(ThemeColor.LIGHT_1.color())
88
+ else:
89
+ painter.setBrush(themeColor())
90
+ else:
91
+ c = 255 if isDarkTheme() else 0
92
+ if index == self.pressedIndex:
93
+ painter.setBrush(QColor(c, c, c, 7))
94
+ elif option.state & QStyle.State_MouseOver:
95
+ painter.setBrush(QColor(c, c, c, 9))
96
+ else:
97
+ painter.setBrush(Qt.transparent)
98
+
99
+ m = self._itemMargin()
100
+ painter.drawEllipse(option.rect.adjusted(m, m, -m, -m))
101
+ painter.restore()
102
+
103
+ def _drawText(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
104
+ painter.save()
105
+ painter.setFont(self.font)
106
+
107
+ if index == self.currentIndex:
108
+ c = 0 if isDarkTheme() else 255
109
+ painter.setPen(QColor(c, c, c))
110
+ else:
111
+ painter.setPen(Qt.white if isDarkTheme() else Qt.black)
112
+ if not (self.min <= index.data(Qt.UserRole) <= self.max or option.state & QStyle.State_MouseOver) or \
113
+ index == self.pressedIndex:
114
+ painter.setOpacity(0.6)
115
+
116
+ text = index.data(Qt.DisplayRole)
117
+ painter.drawText(option.rect, Qt.AlignCenter, text)
118
+ painter.restore()
119
+
120
+ def _itemMargin(self):
121
+ return 0
122
+
123
+
124
+ class YearScrollItemDelegate(ScrollItemDelegate):
125
+ """ Year scroll item delegate """
126
+
127
+ def _itemMargin(self):
128
+ return 8
129
+
130
+
131
+ class DayScrollItemDelegate(ScrollItemDelegate):
132
+ """ Day scroll item delegate """
133
+
134
+ def _itemMargin(self):
135
+ return 3
136
+
137
+
138
+ class ScrollViewBase(QListWidget):
139
+ """ Scroll view base class """
140
+
141
+ pageChanged = Signal(int)
142
+
143
+ def __init__(self, Delegate: Type[ScrollItemDelegate], parent=None):
144
+ super().__init__(parent)
145
+ self.cols = 4
146
+ self.pageRows = 3
147
+ self.currentPage = 0
148
+ self.vScrollBar = SmoothScrollBar(Qt.Vertical, self)
149
+
150
+ self.delegate = Delegate(0, 0)
151
+ self.currentDate = QDate.currentDate()
152
+ self.date = QDate.currentDate()
153
+
154
+ self.minYear = self.currentDate.year() - 100
155
+ self.maxYear = self.currentDate.year() + 100
156
+
157
+ self.setUniformItemSizes(True)
158
+ self._initItems()
159
+ self.__initWidget()
160
+
161
+ def __initWidget(self):
162
+ self.setSpacing(0)
163
+ self.setMovement(QListWidget.Static)
164
+ self.setGridSize(self.gridSize())
165
+ self.setViewportMargins(0, 0, 0, 0)
166
+ self.setItemDelegate(self.delegate)
167
+ self.setViewMode(QListWidget.IconMode)
168
+ self.setResizeMode(QListWidget.Adjust)
169
+
170
+ self.vScrollBar.ani.finished.connect(self._onFirstScrollFinished)
171
+ self.vScrollBar.setScrollAnimation(1)
172
+ self.setDate(self.date)
173
+
174
+ self.vScrollBar.setForceHidden(True)
175
+ self.setVerticalScrollMode(QListWidget.ScrollMode.ScrollPerPixel)
176
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
177
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
178
+
179
+ def _onFirstScrollFinished(self):
180
+ self.vScrollBar.setScrollAnimation(300, QEasingCurve.OutQuad)
181
+ self.vScrollBar.ani.finished.disconnect()
182
+
183
+ def scrollUp(self):
184
+ self.scrollToPage(self.currentPage - 1)
185
+
186
+ def scrollDown(self):
187
+ self.scrollToPage(self.currentPage + 1)
188
+
189
+ def scrollToPage(self, page: int):
190
+ if not 0 <= page <= ceil(self.model().rowCount() / (self.pageRows * self.cols)):
191
+ return
192
+
193
+ self.currentPage = page
194
+ y = self.gridSize().height() * self.pageRows * page
195
+ self.vScrollBar.setValue(y)
196
+ self.delegate.setRange(*self.currentPageRange())
197
+ self.pageChanged.emit(page)
198
+
199
+ def currentPageRange(self):
200
+ return 0, 0
201
+
202
+ def setDate(self, date: QDate):
203
+ self.scrollToDate(date)
204
+
205
+ def scrollToDate(self, date: QDate):
206
+ pass
207
+
208
+ def _setPressedIndex(self, index):
209
+ self.delegate.setPressedIndex(index)
210
+ self.viewport().update()
211
+
212
+ def _setSelectedIndex(self, index):
213
+ self.delegate.setSelectedIndex(index)
214
+ self.viewport().update()
215
+
216
+ def wheelEvent(self, e):
217
+ if self.vScrollBar.ani.state() == QPropertyAnimation.Running:
218
+ return
219
+
220
+ if e.angleDelta().y() < 0:
221
+ self.scrollDown()
222
+ else:
223
+ self.scrollUp()
224
+
225
+ def mousePressEvent(self, e):
226
+ super().mousePressEvent(e)
227
+ if e.button() == Qt.LeftButton and self.indexAt(e.pos()).row() >= 0:
228
+ self._setPressedIndex(self.currentIndex())
229
+
230
+ def mouseReleaseEvent(self, e):
231
+ super().mouseReleaseEvent(e)
232
+ self._setPressedIndex(QModelIndex())
233
+
234
+ def gridSize(self) -> QSize:
235
+ return QSize(76, 76)
236
+
237
+
238
+ class CalendarViewBase(QFrame):
239
+ """ Calendar view base class """
240
+
241
+ resetted = Signal()
242
+ titleClicked = Signal()
243
+ itemClicked = Signal(QDate)
244
+
245
+ def __init__(self, parent=None):
246
+ super().__init__(parent)
247
+
248
+ self.titleButton = QPushButton(self)
249
+ self.resetButton = ScrollButton(FIF.CANCEL, self)
250
+ self.upButton = ScrollButton(FIF.CARE_UP_SOLID, self)
251
+ self.downButton = ScrollButton(FIF.CARE_DOWN_SOLID, self)
252
+
253
+ self.scrollView = None # type: ScrollViewBase
254
+
255
+ self.hBoxLayout = QHBoxLayout()
256
+ self.vBoxLayout = QVBoxLayout(self)
257
+
258
+ self.__initWidget()
259
+
260
+ def __initWidget(self):
261
+ self.setFixedSize(314, 355)
262
+ self.upButton.setFixedSize(32, 34)
263
+ self.downButton.setFixedSize(32, 34)
264
+ self.resetButton.setFixedSize(32, 34)
265
+ self.titleButton.setFixedHeight(34)
266
+
267
+ self.hBoxLayout.setContentsMargins(9, 8, 9, 8)
268
+ self.hBoxLayout.setSpacing(7)
269
+ self.hBoxLayout.addWidget(self.titleButton, 1, Qt.AlignVCenter)
270
+ self.hBoxLayout.addWidget(self.resetButton, 0, Qt.AlignVCenter)
271
+ self.hBoxLayout.addWidget(self.upButton, 0, Qt.AlignVCenter)
272
+ self.hBoxLayout.addWidget(self.downButton, 0, Qt.AlignVCenter)
273
+ self.setResetEnabled(False)
274
+
275
+ self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
276
+ self.vBoxLayout.setSpacing(0)
277
+ self.vBoxLayout.addLayout(self.hBoxLayout)
278
+ self.vBoxLayout.setAlignment(Qt.AlignTop)
279
+
280
+ self.titleButton.setObjectName('titleButton')
281
+ FluentStyleSheet.CALENDAR_PICKER.apply(self)
282
+
283
+ self.titleButton.clicked.connect(self.titleClicked)
284
+ self.resetButton.clicked.connect(self.resetted)
285
+ self.upButton.clicked.connect(self._onScrollUp)
286
+ self.downButton.clicked.connect(self._onScrollDown)
287
+
288
+ def setScrollView(self, view: ScrollViewBase):
289
+ self.scrollView = view
290
+ self.scrollView.itemClicked.connect(lambda i: self.itemClicked.emit(i.data(Qt.UserRole)))
291
+ self.vBoxLayout.addWidget(view)
292
+ view.pageChanged.connect(self._updateTitle)
293
+ self._updateTitle()
294
+
295
+ def setResetEnabled(self, isEnabled: bool):
296
+ self.resetButton.setVisible(isEnabled)
297
+
298
+ def isResetEnabled(self):
299
+ return self.resetButton.isVisible()
300
+
301
+ def setDate(self, date: QDate):
302
+ self.scrollView.setDate(date)
303
+ self._updateTitle()
304
+
305
+ def setTitle(self, title: str):
306
+ self.titleButton.setText(title)
307
+
308
+ def currentPageDate(self) -> QDate:
309
+ raise NotImplementedError
310
+
311
+ def _onScrollUp(self):
312
+ self.scrollView.scrollUp()
313
+ self._updateTitle()
314
+
315
+ def _onScrollDown(self):
316
+ self.scrollView.scrollDown()
317
+ self._updateTitle()
318
+
319
+ def _updateTitle(self):
320
+ pass
321
+
322
+
323
+ class YearScrollView(ScrollViewBase):
324
+ """ Year scroll view """
325
+
326
+ def __init__(self, parent=None):
327
+ super().__init__(YearScrollItemDelegate, parent)
328
+
329
+ def _initItems(self):
330
+ years = range(self.minYear, self.maxYear+1)
331
+ self.addItems([str(i) for i in years])
332
+
333
+ for i, year in enumerate(years):
334
+ item = self.item(i)
335
+ item.setData(Qt.UserRole, QDate(year, 1, 1))
336
+ item.setSizeHint(self.sizeHint())
337
+ if year == self.currentDate.year():
338
+ self.delegate.setCurrentIndex(self.indexFromItem(item))
339
+
340
+ def scrollToDate(self, date: QDate):
341
+ page = (date.year() - self.minYear) // 12
342
+ self.scrollToPage(page)
343
+
344
+ def currentPageRange(self):
345
+ pageSize = self.pageRows * self.cols
346
+ left = self.currentPage * pageSize + self.minYear
347
+
348
+ years = defaultdict(int)
349
+ for i in range(left, left + 16):
350
+ y = i // 10 * 10
351
+ years[y] += 1
352
+
353
+ year = Counter(years).most_common()[0][0]
354
+ return QDate(year, 1, 1), QDate(year + 10, 1, 1)
355
+
356
+
357
+ class YearCalendarView(CalendarViewBase):
358
+ """ Year calendar view """
359
+
360
+ def __init__(self, parent=None):
361
+ super().__init__(parent)
362
+ self.setScrollView(YearScrollView(self))
363
+ self.titleButton.setEnabled(False)
364
+
365
+ def _updateTitle(self):
366
+ left, right = self.scrollView.currentPageRange()
367
+ self.setTitle(f'{left.year()} - {right.year()}')
368
+
369
+
370
+ class MonthScrollView(ScrollViewBase):
371
+ """ Month scroll view """
372
+
373
+ def __init__(self, parent=None):
374
+ super().__init__(YearScrollItemDelegate, parent)
375
+
376
+ def _initItems(self):
377
+ self.months = [
378
+ self.tr('Jan'), self.tr('Feb'), self.tr('Mar'), self.tr('Apr'),
379
+ self.tr('May'), self.tr('Jun'), self.tr('Jul'), self.tr('Aug'),
380
+ self.tr('Sep'), self.tr('Oct'), self.tr('Nov'), self.tr('Dec'),
381
+ ]
382
+ self.addItems(self.months * 201)
383
+
384
+ # add month items
385
+ for i in range(12 * 201):
386
+ year = i // 12 + self.minYear
387
+ m = i % 12 + 1
388
+ item = self.item(i)
389
+ item.setData(Qt.UserRole, QDate(year, m, 1))
390
+ item.setSizeHint(self.gridSize())
391
+
392
+ if year == self.currentDate.year() and m == self.currentDate.month():
393
+ self.delegate.setCurrentIndex(self.indexFromItem(item))
394
+
395
+ def scrollToDate(self, date: QDate):
396
+ page = date.year() - self.minYear
397
+ self.scrollToPage(page)
398
+
399
+ def currentPageRange(self):
400
+ year = self.minYear + self.currentPage
401
+ return QDate(year, 1, 1), QDate(year, 12, 31)
402
+
403
+
404
+ class MonthCalendarView(CalendarViewBase):
405
+ """ Month calendar view """
406
+
407
+ def __init__(self, parent=None):
408
+ super().__init__(parent)
409
+ self.setScrollView(MonthScrollView(self))
410
+
411
+ def _updateTitle(self):
412
+ date, _ = self.scrollView.currentPageRange()
413
+ self.setTitle(str(date.year()))
414
+
415
+ def currentPageDate(self) -> QDate:
416
+ date, _ = self.scrollView.currentPageRange()
417
+ item = self.scrollView.currentItem()
418
+ month = item.data(Qt.UserRole).month() if item else 1
419
+ return QDate(date.year(), month, 1)
420
+
421
+
422
+ class DayScrollView(ScrollViewBase):
423
+ """ Day scroll view """
424
+
425
+ def __init__(self, parent=None):
426
+ super().__init__(DayScrollItemDelegate, parent)
427
+ self.cols = 7
428
+ self.pageRows = 4
429
+ self.vBoxLayout = QHBoxLayout(self)
430
+
431
+ # add week day labels
432
+ self.weekDays = [
433
+ self.tr('Mo'), self.tr('Tu'), self.tr('We'),
434
+ self.tr('Th'), self.tr('Fr'), self.tr('Sa'), self.tr('Su')
435
+ ]
436
+ self.weekDayGroup = QWidget(self)
437
+ self.weekDayLayout = QHBoxLayout(self.weekDayGroup)
438
+ self.weekDayGroup.setObjectName('weekDayGroup')
439
+ for day in self.weekDays:
440
+ label = QLabel(day)
441
+ label.setObjectName('weekDayLabel')
442
+ self.weekDayLayout.addWidget(label, 1, Qt.AlignHCenter)
443
+
444
+ self.setViewportMargins(0, 38, 0, 0)
445
+ self.vBoxLayout.setAlignment(Qt.AlignTop)
446
+ self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
447
+ self.weekDayLayout.setSpacing(0)
448
+ self.weekDayLayout.setContentsMargins(3, 12, 3, 12)
449
+ self.vBoxLayout.addWidget(self.weekDayGroup)
450
+
451
+ def gridSize(self) -> QSize:
452
+ return QSize(44, 44)
453
+
454
+ def _initItems(self):
455
+ startDate = QDate(self.minYear, 1, 1)
456
+ endDate = QDate(self.maxYear, 12, 31)
457
+ currentDate = startDate
458
+
459
+ # add placeholder
460
+ bias = currentDate.dayOfWeek() - 1
461
+ for i in range(bias):
462
+ item = QListWidgetItem(self)
463
+ item.setFlags(Qt.NoItemFlags)
464
+ self.addItem(item)
465
+
466
+ # add day items
467
+ items, dates = [], []
468
+ while currentDate <= endDate:
469
+ items.append(str(currentDate.day()))
470
+ dates.append(QDate(currentDate))
471
+ currentDate = currentDate.addDays(1)
472
+
473
+ self.addItems(items)
474
+ for i in range(bias, self.count()):
475
+ item = self.item(i)
476
+ item.setData(Qt.UserRole, dates[i-bias])
477
+ item.setSizeHint(self.gridSize())
478
+
479
+ self.delegate.setCurrentIndex(self.model().index(self._dateToRow(self.currentDate)))
480
+
481
+ def setDate(self, date: QDate):
482
+ self.scrollToDate(date)
483
+ self.setCurrentIndex(self.model().index(self._dateToRow(date)))
484
+ self.delegate.setSelectedIndex(self.currentIndex())
485
+
486
+ def scrollToDate(self, date: QDate):
487
+ page = (date.year() - self.minYear) * 12 + date.month() - 1
488
+ self.scrollToPage(page)
489
+
490
+ def scrollToPage(self, page: int):
491
+ if not 0 <= page <= 201 * 12 - 1:
492
+ return
493
+
494
+ self.currentPage = page
495
+
496
+ index = self._dateToRow(self._pageToDate())
497
+ y = index // self.cols * self.gridSize().height()
498
+ self.vScrollBar.scrollTo(y)
499
+
500
+ self.delegate.setRange(*self.currentPageRange())
501
+ self.pageChanged.emit(page)
502
+
503
+ def currentPageRange(self):
504
+ date = self._pageToDate()
505
+ return date, date.addMonths(1).addDays(-1)
506
+
507
+ def _pageToDate(self):
508
+ year = self.currentPage // 12 + self.minYear
509
+ month = self.currentPage % 12 + 1
510
+ return QDate(year, month, 1)
511
+
512
+ def _dateToRow(self, date: QDate):
513
+ startDate = QDate(self.minYear, 1, 1)
514
+ days = startDate.daysTo(date)
515
+ return days + startDate.dayOfWeek() - 1
516
+
517
+ def mouseReleaseEvent(self, e):
518
+ super().mouseReleaseEvent(e)
519
+ self._setSelectedIndex(self.currentIndex())
520
+
521
+
522
+ class DayCalendarView(CalendarViewBase):
523
+ """ Day calendar view """
524
+
525
+ def __init__(self, parent=None):
526
+ super().__init__(parent)
527
+ self.setScrollView(DayScrollView(self))
528
+
529
+ def _updateTitle(self):
530
+ date = self.currentPageDate()
531
+ name = QCalendar().monthName(self.locale(), date.month(), date.year())
532
+ self.setTitle(f'{name} {date.year()}')
533
+
534
+ def currentPageDate(self) -> QDate:
535
+ date, _ = self.scrollView.currentPageRange()
536
+ return date
537
+
538
+ def scrollToDate(self, date: QDate):
539
+ self.scrollView.scrollToDate(date)
540
+ self._updateTitle()
541
+
542
+
543
+ class CalendarView(QWidget):
544
+ """ Calendar view """
545
+
546
+ resetted = Signal()
547
+ dateChanged = Signal(QDate)
548
+
549
+ def __init__(self, parent=None):
550
+ super().__init__(parent)
551
+ self.hBoxLayout = QHBoxLayout(self)
552
+ self.date = QDate()
553
+ self._isResetEnabled = False
554
+
555
+ self.stackedWidget = QStackedWidget(self)
556
+ self.yearView = YearCalendarView(self)
557
+ self.monthView = MonthCalendarView(self)
558
+ self.dayView = DayCalendarView(self)
559
+
560
+ self.opacityAni = QPropertyAnimation(self, b'windowOpacity', self)
561
+ self.slideAni = QPropertyAnimation(self, b'geometry', self)
562
+ self.aniGroup = QParallelAnimationGroup(self)
563
+
564
+ self.__initWidget()
565
+
566
+ def __initWidget(self):
567
+ self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint |
568
+ Qt.NoDropShadowWindowHint)
569
+ self.setAttribute(Qt.WA_TranslucentBackground)
570
+ self.setAttribute(Qt.WA_DeleteOnClose, True)
571
+
572
+ self.stackedWidget.addWidget(self.dayView)
573
+ self.stackedWidget.addWidget(self.monthView)
574
+ self.stackedWidget.addWidget(self.yearView)
575
+
576
+ self.hBoxLayout.setContentsMargins(12, 8, 12, 20)
577
+ self.hBoxLayout.addWidget(self.stackedWidget)
578
+ self.setShadowEffect()
579
+
580
+ self.dayView.setDate(QDate.currentDate())
581
+
582
+ self.aniGroup.addAnimation(self.opacityAni)
583
+ self.aniGroup.addAnimation(self.slideAni)
584
+
585
+ self.dayView.titleClicked.connect(self._onDayViewTitleClicked)
586
+ self.monthView.titleClicked.connect(self._onMonthTitleClicked)
587
+
588
+ self.monthView.itemClicked.connect(self._onMonthItemClicked)
589
+ self.yearView.itemClicked.connect(self._onYearItemClicked)
590
+ self.dayView.itemClicked.connect(self._onDayItemClicked)
591
+
592
+ self.monthView.resetted.connect(self._onResetted)
593
+ self.yearView.resetted.connect(self._onResetted)
594
+ self.dayView.resetted.connect(self._onResetted)
595
+
596
+ def setShadowEffect(self, blurRadius=30, offset=(0, 8), color=QColor(0, 0, 0, 30)):
597
+ """ add shadow to dialog """
598
+ self.shadowEffect = QGraphicsDropShadowEffect(self.stackedWidget)
599
+ self.shadowEffect.setBlurRadius(blurRadius)
600
+ self.shadowEffect.setOffset(*offset)
601
+ self.shadowEffect.setColor(color)
602
+ self.stackedWidget.setGraphicsEffect(None)
603
+ self.stackedWidget.setGraphicsEffect(self.shadowEffect)
604
+
605
+ def isRestEnabled(self):
606
+ return self._isResetEnabled
607
+
608
+ def setResetEnabled(self, isEnabled: bool):
609
+ """ set the visibility of reset button """
610
+ self._isResetEnabled = isEnabled
611
+ self.yearView.setResetEnabled(isEnabled)
612
+ self.monthView.setResetEnabled(isEnabled)
613
+ self.dayView.setResetEnabled(isEnabled)
614
+
615
+ def _onResetted(self):
616
+ self.resetted.emit()
617
+ self.close()
618
+
619
+ def _onDayViewTitleClicked(self):
620
+ self.stackedWidget.setCurrentWidget(self.monthView)
621
+ self.monthView.setDate(self.dayView.currentPageDate())
622
+
623
+ def _onMonthTitleClicked(self):
624
+ self.stackedWidget.setCurrentWidget(self.yearView)
625
+ self.yearView.setDate(self.monthView.currentPageDate())
626
+
627
+ def _onMonthItemClicked(self, date: QDate):
628
+ self.stackedWidget.setCurrentWidget(self.dayView)
629
+ self.dayView.scrollToDate(date)
630
+
631
+ def _onYearItemClicked(self, date: QDate):
632
+ self.stackedWidget.setCurrentWidget(self.monthView)
633
+ self.monthView.setDate(date)
634
+
635
+ def _onDayItemClicked(self, date: QDate):
636
+ self.close()
637
+ if date != self.date:
638
+ self.date = date
639
+ self.dateChanged.emit(date)
640
+
641
+ def setDate(self, date: QDate):
642
+ """ set the selected date """
643
+ self.dayView.setDate(date)
644
+ self.date = date
645
+
646
+ def exec(self, pos: QPoint, ani=True):
647
+ """ show calendar view """
648
+ if self.isVisible():
649
+ return
650
+
651
+ rect = getCurrentScreenGeometry()
652
+ w, h = self.sizeHint().width() + 5, self.sizeHint().height()
653
+ pos.setX(max(rect.left(), min(pos.x(), rect.right() - w)))
654
+ pos.setY(max(rect.top(), min(pos.y() - 4, rect.bottom() - h + 5)))
655
+ self.move(pos)
656
+
657
+ if not ani:
658
+ return self.show()
659
+
660
+ self.opacityAni.setStartValue(0)
661
+ self.opacityAni.setEndValue(1)
662
+ self.opacityAni.setDuration(150)
663
+ self.opacityAni.setEasingCurve(QEasingCurve.OutQuad)
664
+
665
+ self.slideAni.setStartValue(QRect(pos-QPoint(0, 8), self.sizeHint()))
666
+ self.slideAni.setEndValue(QRect(pos, self.sizeHint()))
667
+ self.slideAni.setDuration(150)
668
+ self.slideAni.setEasingCurve(QEasingCurve.OutQuad)
669
+ self.aniGroup.start()
670
+
671
+ self.show()