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,307 @@
1
+ # coding:utf-8
2
+ from PySide6.QtCore import QSize, Qt, Signal, QPoint, QRectF, QPropertyAnimation, Property, QEasingCurve
3
+ from PySide6.QtGui import QColor, QMouseEvent, QPainter, QPainterPath
4
+ from PySide6.QtWidgets import QProxyStyle, QSlider, QStyle, QStyleOptionSlider, QWidget
5
+
6
+ from ...common.style_sheet import FluentStyleSheet, themeColor, isDarkTheme
7
+ from ...common.color import autoFallbackThemeColor
8
+ from ...common.overload import singledispatchmethod
9
+
10
+
11
+
12
+ class SliderHandle(QWidget):
13
+ """ Slider handle """
14
+
15
+ pressed = Signal()
16
+ released = Signal()
17
+
18
+ def __init__(self, parent: QSlider):
19
+ super().__init__(parent=parent)
20
+ self.setFixedSize(22, 22)
21
+ self._radius = 5
22
+ self.lightHandleColor = QColor()
23
+ self.darkHandleColor = QColor()
24
+ self.radiusAni = QPropertyAnimation(self, b'radius', self)
25
+ self.radiusAni.setDuration(100)
26
+
27
+ @Property(int)
28
+ def radius(self):
29
+ return self._radius
30
+
31
+ @radius.setter
32
+ def radius(self, r):
33
+ self._radius = r
34
+ self.update()
35
+
36
+ def setHandleColor(self, light, dark):
37
+ self.lightHandleColor = QColor(light)
38
+ self.darkHandleColor = QColor(dark)
39
+ self.update()
40
+
41
+ def enterEvent(self, e):
42
+ self._startAni(6)
43
+
44
+ def leaveEvent(self, e):
45
+ self._startAni(5)
46
+
47
+ def mousePressEvent(self, e):
48
+ self._startAni(4)
49
+ self.pressed.emit()
50
+
51
+ def mouseReleaseEvent(self, e):
52
+ self._startAni(6)
53
+ self.released.emit()
54
+
55
+ def _startAni(self, radius):
56
+ self.radiusAni.stop()
57
+ self.radiusAni.setStartValue(self.radius)
58
+ self.radiusAni.setEndValue(radius)
59
+ self.radiusAni.start()
60
+
61
+ def paintEvent(self, e):
62
+ painter = QPainter(self)
63
+ painter.setRenderHints(QPainter.RenderHint.Antialiasing)
64
+ painter.setPen(Qt.PenStyle.NoPen)
65
+
66
+ # draw outer circle
67
+ isDark = isDarkTheme()
68
+ painter.setPen(QColor(0, 0, 0, 90 if isDark else 25))
69
+ painter.setBrush(QColor(69, 69, 69) if isDark else Qt.GlobalColor.white)
70
+ painter.drawEllipse(self.rect().adjusted(1, 1, -1, -1))
71
+
72
+ # draw innert circle
73
+ painter.setBrush(autoFallbackThemeColor(self.lightHandleColor, self.darkHandleColor))
74
+ painter.drawEllipse(QPoint(11, 11), self.radius, self.radius)
75
+
76
+
77
+
78
+ class Slider(QSlider):
79
+ """ A slider can be clicked
80
+
81
+ Constructors
82
+ ------------
83
+ * Slider(`parent`: QWidget = None)
84
+ * Slider(`orient`: Qt.Orientation, `parent`: QWidget = None)
85
+ """
86
+
87
+ clicked = Signal(int)
88
+
89
+ @singledispatchmethod
90
+ def __init__(self, parent: QWidget = None):
91
+ super().__init__(parent)
92
+ self._postInit()
93
+
94
+ @__init__.register
95
+ def _(self, orientation: Qt.Orientation, parent: QWidget = None):
96
+ super().__init__(orientation, parent=parent)
97
+ self._postInit()
98
+
99
+ def _postInit(self):
100
+ self.handle = SliderHandle(self)
101
+ self._pressedPos = QPoint()
102
+ self.lightGrooveColor = QColor()
103
+ self.darkGrooveColor = QColor()
104
+ self.setOrientation(self.orientation())
105
+
106
+ self.handle.pressed.connect(self.sliderPressed)
107
+ self.handle.released.connect(self.sliderReleased)
108
+ self.valueChanged.connect(self._adjustHandlePos)
109
+
110
+ def setThemeColor(self, light, dark):
111
+ self.lightGrooveColor = QColor(light)
112
+ self.darkGrooveColor = QColor(dark)
113
+ self.handle.setHandleColor(light, dark)
114
+ self.update()
115
+
116
+ def setOrientation(self, orientation: Qt.Orientation) -> None:
117
+ super().setOrientation(orientation)
118
+ if orientation == Qt.Orientation.Horizontal:
119
+ self.setMinimumHeight(22)
120
+ else:
121
+ self.setMinimumWidth(22)
122
+
123
+ def mousePressEvent(self, e: QMouseEvent):
124
+ self._pressedPos = e.pos()
125
+ self.setValue(self._posToValue(e.pos()))
126
+ self.clicked.emit(self.value())
127
+
128
+ def mouseMoveEvent(self, e: QMouseEvent):
129
+ self.setValue(self._posToValue(e.pos()))
130
+ self._pressedPos = e.pos()
131
+ self.sliderMoved.emit(self.value())
132
+
133
+ @property
134
+ def grooveLength(self):
135
+ l = self.width() if self.orientation() == Qt.Orientation.Horizontal else self.height()
136
+ return l - self.handle.width()
137
+
138
+ def _adjustHandlePos(self):
139
+ total = max(self.maximum() - self.minimum(), 1)
140
+ delta = int((self.value() - self.minimum()) / total * self.grooveLength)
141
+
142
+ if self.orientation() == Qt.Orientation.Vertical:
143
+ self.handle.move(0, delta)
144
+ else:
145
+ self.handle.move(delta, 0)
146
+
147
+ def _posToValue(self, pos: QPoint):
148
+ pd = self.handle.width() / 2
149
+ gs = max(self.grooveLength, 1)
150
+ v = pos.x() if self.orientation() == Qt.Orientation.Horizontal else pos.y()
151
+ return int((v - pd) / gs * (self.maximum() - self.minimum()) + self.minimum())
152
+
153
+ def paintEvent(self, e):
154
+ painter = QPainter(self)
155
+ painter.setRenderHints(QPainter.RenderHint.Antialiasing)
156
+ painter.setPen(Qt.PenStyle.NoPen)
157
+ painter.setBrush(QColor(255, 255, 255, 115) if isDarkTheme() else QColor(0, 0, 0, 100))
158
+
159
+ if self.orientation() == Qt.Orientation.Horizontal:
160
+ self._drawHorizonGroove(painter)
161
+ else:
162
+ self._drawVerticalGroove(painter)
163
+
164
+ def _drawHorizonGroove(self, painter: QPainter):
165
+ w, r = self.width(), self.handle.width() / 2
166
+ painter.drawRoundedRect(QRectF(r, r-2, w-r*2, 4), 2, 2)
167
+
168
+ if self.maximum() - self.minimum() == 0:
169
+ return
170
+
171
+ painter.setBrush(autoFallbackThemeColor(self.lightGrooveColor, self.darkGrooveColor))
172
+ aw = (self.value() - self.minimum()) / (self.maximum() - self.minimum()) * (w - r*2)
173
+ painter.drawRoundedRect(QRectF(r, r-2, aw, 4), 2, 2)
174
+
175
+ def _drawVerticalGroove(self, painter: QPainter):
176
+ h, r = self.height(), self.handle.width() / 2
177
+ painter.drawRoundedRect(QRectF(r-2, r, 4, h-2*r), 2, 2)
178
+
179
+ if self.maximum() - self.minimum() == 0:
180
+ return
181
+
182
+ painter.setBrush(autoFallbackThemeColor(self.lightGrooveColor, self.darkGrooveColor))
183
+ ah = (self.value() - self.minimum()) / (self.maximum() - self.minimum()) * (h - r*2)
184
+ painter.drawRoundedRect(QRectF(r-2, r, 4, ah), 2, 2)
185
+
186
+ def resizeEvent(self, e):
187
+ self._adjustHandlePos()
188
+
189
+
190
+ class ClickableSlider(QSlider):
191
+ """ A slider can be clicked """
192
+
193
+ clicked = Signal(int)
194
+
195
+ def mousePressEvent(self, e: QMouseEvent):
196
+ super().mousePressEvent(e)
197
+
198
+ if self.orientation() == Qt.Horizontal:
199
+ value = int(e.pos().x() / self.width() * self.maximum())
200
+ else:
201
+ value = int((self.height()-e.pos().y()) /
202
+ self.height() * self.maximum())
203
+
204
+ self.setValue(value)
205
+ self.clicked.emit(self.value())
206
+
207
+
208
+
209
+ class HollowHandleStyle(QProxyStyle):
210
+ """ Hollow handle style """
211
+
212
+ def __init__(self, config: dict = None):
213
+ """
214
+ Parameters
215
+ ----------
216
+ config: dict
217
+ style config
218
+ """
219
+ super().__init__()
220
+ self.config = {
221
+ "groove.height": 3,
222
+ "sub-page.color": QColor(255, 255, 255),
223
+ "add-page.color": QColor(255, 255, 255, 64),
224
+ "handle.color": QColor(255, 255, 255),
225
+ "handle.ring-width": 4,
226
+ "handle.hollow-radius": 6,
227
+ "handle.margin": 4
228
+ }
229
+ config = config if config else {}
230
+ self.config.update(config)
231
+
232
+ # get handle size
233
+ w = self.config["handle.margin"]+self.config["handle.ring-width"] + \
234
+ self.config["handle.hollow-radius"]
235
+ self.config["handle.size"] = QSize(2*w, 2*w)
236
+
237
+ def subControlRect(self, cc: QStyle.ComplexControl, opt: QStyleOptionSlider, sc: QStyle.SubControl, widget: QSlider):
238
+ """ get the rectangular area occupied by the sub control """
239
+ if cc != self.ComplexControl.CC_Slider or widget.orientation() != Qt.Horizontal \
240
+ or sc == self.SubControl.SC_SliderTickmarks:
241
+ return super().subControlRect(cc, opt, sc, widget)
242
+
243
+ rect = widget.rect()
244
+
245
+ if sc == self.SubControl.SC_SliderGroove:
246
+ h = self.config["groove.height"]
247
+ grooveRect = QRectF(0, (rect.height()-h)//2, rect.width(), h)
248
+ return grooveRect.toRect()
249
+
250
+ elif sc == self.SubControl.SC_SliderHandle:
251
+ size = self.config["handle.size"]
252
+ x = self.sliderPositionFromValue(
253
+ widget.minimum(), widget.maximum(), widget.value(), rect.width())
254
+
255
+ # solve the situation that the handle runs out of slider
256
+ x *= (rect.width()-size.width())/rect.width()
257
+ sliderRect = QRectF(x, 0, size.width(), size.height())
258
+ return sliderRect.toRect()
259
+
260
+ def drawComplexControl(self, cc: QStyle.ComplexControl, opt: QStyleOptionSlider, painter: QPainter, widget: QSlider):
261
+ """ draw sub control """
262
+ if cc != self.ComplexControl.CC_Slider or widget.orientation() != Qt.Horizontal:
263
+ return super().drawComplexControl(cc, opt, painter, widget)
264
+
265
+ grooveRect = self.subControlRect(cc, opt, self.SubControl.SC_SliderGroove, widget)
266
+ handleRect = self.subControlRect(cc, opt, self.SubControl.SC_SliderHandle, widget)
267
+ painter.setRenderHints(QPainter.Antialiasing)
268
+ painter.setPen(Qt.NoPen)
269
+
270
+ # paint groove
271
+ painter.save()
272
+ painter.translate(grooveRect.topLeft())
273
+
274
+ # paint the crossed part
275
+ w = handleRect.x()-grooveRect.x()
276
+ h = self.config['groove.height']
277
+ painter.setBrush(self.config["sub-page.color"])
278
+ painter.drawRect(0, 0, w, h)
279
+
280
+ # paint the uncrossed part
281
+ x = w+self.config['handle.size'].width()
282
+ painter.setBrush(self.config["add-page.color"])
283
+ painter.drawRect(x, 0, grooveRect.width()-w, h)
284
+ painter.restore()
285
+
286
+ # paint handle
287
+ ringWidth = self.config["handle.ring-width"]
288
+ hollowRadius = self.config["handle.hollow-radius"]
289
+ radius = ringWidth + hollowRadius
290
+
291
+ path = QPainterPath()
292
+ path.moveTo(0, 0)
293
+ center = handleRect.center() + QPoint(1, 1)
294
+ path.addEllipse(center, radius, radius)
295
+ path.addEllipse(center, hollowRadius, hollowRadius)
296
+
297
+ handleColor = self.config["handle.color"] # type:QColor
298
+ handleColor.setAlpha(255 if opt.activeSubControls !=
299
+ self.SubControl.SC_SliderHandle else 153)
300
+ painter.setBrush(handleColor)
301
+ painter.drawPath(path)
302
+
303
+ # press handle
304
+ if widget.isSliderDown():
305
+ handleColor.setAlpha(255)
306
+ painter.setBrush(handleColor)
307
+ painter.drawEllipse(handleRect)
@@ -0,0 +1,306 @@
1
+ # coding:utf-8
2
+ from enum import Enum
3
+
4
+ from PySide6.QtCore import Qt, QSize, QRectF, QPoint
5
+ from PySide6.QtGui import QPainter, QPainterPath, QColor
6
+ from PySide6.QtWidgets import (QSpinBox, QDoubleSpinBox, QToolButton, QHBoxLayout,
7
+ QDateEdit, QDateTimeEdit, QTimeEdit, QVBoxLayout, QApplication)
8
+
9
+ from ...common.style_sheet import FluentStyleSheet, themeColor, isDarkTheme
10
+ from ...common.icon import FluentIconBase, Theme, getIconColor
11
+ from ...common.font import setFont
12
+ from ...common.color import FluentSystemColor, autoFallbackThemeColor
13
+ from .button import TransparentToolButton
14
+ from .line_edit import LineEditMenu
15
+ from .flyout import Flyout, FlyoutViewBase, FlyoutAnimationType
16
+
17
+
18
+ class SpinIcon(FluentIconBase, Enum):
19
+ """ Spin icon """
20
+
21
+ UP = "Up"
22
+ DOWN = "Down"
23
+
24
+ def path(self, theme=Theme.AUTO):
25
+ return f':/fastuiwidgets/images/spin_box/{self.value}_{getIconColor(theme)}.svg'
26
+
27
+
28
+
29
+ class SpinButton(QToolButton):
30
+
31
+ def __init__(self, icon: SpinIcon, parent=None):
32
+ super().__init__(parent=parent)
33
+ self.isPressed = False
34
+ self._icon = icon
35
+ self.setFixedSize(31, 23)
36
+ self.setIconSize(QSize(10, 10))
37
+ FluentStyleSheet.SPIN_BOX.apply(self)
38
+
39
+ def mousePressEvent(self, e):
40
+ self.isPressed = True
41
+ super().mousePressEvent(e)
42
+
43
+ def mouseReleaseEvent(self, e):
44
+ self.isPressed = False
45
+ super().mouseReleaseEvent(e)
46
+
47
+ def paintEvent(self, e):
48
+ super().paintEvent(e)
49
+ painter = QPainter(self)
50
+ painter.setRenderHints(QPainter.Antialiasing |
51
+ QPainter.SmoothPixmapTransform)
52
+
53
+ if not self.isEnabled():
54
+ painter.setOpacity(0.36)
55
+ elif self.isPressed:
56
+ painter.setOpacity(0.7)
57
+
58
+ self._icon.render(painter, QRectF(10, 6.5, 11, 11))
59
+
60
+
61
+ class CompactSpinButton(QToolButton):
62
+ """ Compact spin button """
63
+
64
+ def __init__(self, parent=None):
65
+ super().__init__(parent=parent)
66
+ self.setFixedSize(26, 33)
67
+ self.setCursor(Qt.IBeamCursor)
68
+
69
+ def paintEvent(self, e):
70
+ painter = QPainter(self)
71
+ painter.setRenderHints(QPainter.Antialiasing)
72
+
73
+ x = (self.width() - 10) / 2
74
+ s = 9
75
+
76
+ SpinIcon.UP.render(painter, QRectF(x, self.height() / 2 - s + 1, s, s))
77
+ SpinIcon.DOWN.render(painter, QRectF(x, self.height() / 2 , s, s))
78
+
79
+
80
+ class SpinFlyoutView(FlyoutViewBase):
81
+ """ Spin flyout view """
82
+
83
+ def __init__(self, parent=None):
84
+ super().__init__(parent)
85
+ self.upButton = TransparentToolButton(SpinIcon.UP, self)
86
+ self.downButton = TransparentToolButton(SpinIcon.DOWN, self)
87
+ self.vBoxLayout = QVBoxLayout(self)
88
+
89
+ self.upButton.setFixedSize(36, 36)
90
+ self.downButton.setFixedSize(36, 36)
91
+ self.upButton.setIconSize(QSize(13, 13))
92
+ self.downButton.setIconSize(QSize(13, 13))
93
+
94
+ self.vBoxLayout.setContentsMargins(6, 6, 6, 6)
95
+ self.vBoxLayout.setSpacing(0)
96
+ self.vBoxLayout.addWidget(self.upButton)
97
+ self.vBoxLayout.addWidget(self.downButton)
98
+
99
+ def paintEvent(self, e):
100
+ painter = QPainter(self)
101
+ painter.setRenderHints(QPainter.Antialiasing)
102
+
103
+ painter.setBrush(
104
+ QColor(46, 46, 46) if isDarkTheme() else QColor(249, 249, 249))
105
+ painter.setPen(
106
+ QColor(0, 0, 0, 51) if isDarkTheme() else QColor(0, 0, 0, 15))
107
+
108
+ rect = self.rect().adjusted(1, 1, -1, -1)
109
+ painter.drawRoundedRect(rect, 8, 8)
110
+
111
+
112
+ class SpinBoxBase:
113
+ """ Spin box ui """
114
+
115
+ def __init__(self, parent=None):
116
+ super().__init__(parent=parent)
117
+ self._isError = False
118
+ self.lightFocusedBorderColor = QColor()
119
+ self.darkFocusedBorderColor = QColor()
120
+
121
+ self.hBoxLayout = QHBoxLayout(self)
122
+
123
+ self.setProperty('transparent', True)
124
+ FluentStyleSheet.SPIN_BOX.apply(self)
125
+ self.setButtonSymbols(QSpinBox.NoButtons)
126
+ self.setFixedHeight(33)
127
+ setFont(self)
128
+
129
+ self.setAttribute(Qt.WA_MacShowFocusRect, False)
130
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
131
+ self.customContextMenuRequested.connect(self._showContextMenu)
132
+
133
+ def isError(self):
134
+ return self._isError
135
+
136
+ def setError(self, isError: bool):
137
+ """ set the error status """
138
+ if isError == self.isError():
139
+ return
140
+
141
+ self._isError = isError
142
+ self.update()
143
+
144
+ def setReadOnly(self, isReadOnly: bool):
145
+ super().setReadOnly(isReadOnly)
146
+ self.setSymbolVisible(not isReadOnly)
147
+
148
+ def setSymbolVisible(self, isVisible: bool):
149
+ """ set whether the spin symbol is visible """
150
+ self.setProperty("symbolVisible", isVisible)
151
+ self.setStyle(QApplication.style())
152
+
153
+ def setCustomFocusedBorderColor(self, light, dark):
154
+ """ set the border color in focused status
155
+
156
+ Parameters
157
+ ----------
158
+ light, dark: str | QColor | Qt.GlobalColor
159
+ border color in light/dark theme mode
160
+ """
161
+ self.lightFocusedBorderColor = QColor(light)
162
+ self.darkFocusedBorderColor = QColor(dark)
163
+ self.update()
164
+
165
+ def focusedBorderColor(self):
166
+ if self.isError():
167
+ return FluentSystemColor.CRITICAL_FOREGROUND.color()
168
+
169
+ return autoFallbackThemeColor(self.lightFocusedBorderColor, self.darkFocusedBorderColor)
170
+
171
+ def _showContextMenu(self, pos):
172
+ menu = LineEditMenu(self.lineEdit())
173
+ menu.exec_(self.mapToGlobal(pos))
174
+
175
+ def _drawBorderBottom(self):
176
+ if not self.hasFocus():
177
+ return
178
+
179
+ painter = QPainter(self)
180
+ painter.setRenderHints(QPainter.Antialiasing)
181
+ painter.setPen(Qt.NoPen)
182
+
183
+ path = QPainterPath()
184
+ w, h = self.width(), self.height()
185
+ path.addRoundedRect(QRectF(0, h-10, w, 10), 5, 5)
186
+
187
+ rectPath = QPainterPath()
188
+ rectPath.addRect(0, h-10, w, 8)
189
+ path = path.subtracted(rectPath)
190
+
191
+ painter.fillPath(path, self.focusedBorderColor())
192
+
193
+ def paintEvent(self, e):
194
+ super().paintEvent(e)
195
+ self._drawBorderBottom()
196
+
197
+
198
+ class InlineSpinBoxBase(SpinBoxBase):
199
+ """ Inline spin box base """
200
+
201
+ def __init__(self, parent=None):
202
+ super().__init__(parent)
203
+ self.upButton = SpinButton(SpinIcon.UP, self)
204
+ self.downButton = SpinButton(SpinIcon.DOWN, self)
205
+
206
+ self.hBoxLayout.setContentsMargins(0, 4, 4, 4)
207
+ self.hBoxLayout.setSpacing(5)
208
+ self.hBoxLayout.addWidget(self.upButton, 0, Qt.AlignRight)
209
+ self.hBoxLayout.addWidget(self.downButton, 0, Qt.AlignRight)
210
+ self.hBoxLayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
211
+
212
+ self.upButton.clicked.connect(self.stepUp)
213
+ self.downButton.clicked.connect(self.stepDown)
214
+
215
+ def setSymbolVisible(self, isVisible: bool):
216
+ super().setSymbolVisible(isVisible)
217
+ self.upButton.setVisible(isVisible)
218
+ self.downButton.setVisible(isVisible)
219
+
220
+ def setAccelerated(self, on: bool):
221
+ super().setAccelerated(on)
222
+ self.upButton.setAutoRepeat(on)
223
+ self.downButton.setAutoRepeat(on)
224
+
225
+
226
+ class CompactSpinBoxBase(SpinBoxBase):
227
+ """ Compact spin box base """
228
+
229
+ def __init__(self, parent=None):
230
+ super().__init__(parent)
231
+ self.compactSpinButton = CompactSpinButton(self)
232
+ self.spinFlyoutView = SpinFlyoutView(self)
233
+ self.spinFlyout = Flyout(self.spinFlyoutView, self, False)
234
+
235
+ self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
236
+ self.hBoxLayout.addWidget(self.compactSpinButton, 0, Qt.AlignRight)
237
+ self.hBoxLayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
238
+
239
+ self.compactSpinButton.clicked.connect(self._showFlyout)
240
+ self.spinFlyoutView.upButton.clicked.connect(self.stepUp)
241
+ self.spinFlyoutView.downButton.clicked.connect(self.stepDown)
242
+
243
+ self.spinFlyout.hide()
244
+
245
+ def setAccelerated(self, on: bool):
246
+ super().setAccelerated(on)
247
+ self.spinFlyoutView.upButton.setAutoRepeat(on)
248
+ self.spinFlyoutView.downButton.setAutoRepeat(on)
249
+
250
+ def focusInEvent(self, e):
251
+ super().focusInEvent(e)
252
+ self._showFlyout()
253
+
254
+ def setSymbolVisible(self, isVisible: bool):
255
+ super().setSymbolVisible(isVisible)
256
+ self.compactSpinButton.setVisible(isVisible)
257
+
258
+ def _showFlyout(self):
259
+ if self.spinFlyout.isVisible() or self.isReadOnly():
260
+ return
261
+
262
+ y = int(self.compactSpinButton.height() / 2 - 46)
263
+ pos = self.compactSpinButton.mapToGlobal(QPoint(-12, y))
264
+
265
+ self.spinFlyout.exec(pos, FlyoutAnimationType.FADE_IN)
266
+
267
+
268
+ class SpinBox(InlineSpinBoxBase, QSpinBox):
269
+ """ Spin box """
270
+
271
+
272
+ class CompactSpinBox(CompactSpinBoxBase, QSpinBox):
273
+ """ Compact spin box """
274
+
275
+
276
+ class DoubleSpinBox(InlineSpinBoxBase, QDoubleSpinBox):
277
+ """ Double spin box """
278
+
279
+
280
+ class CompactDoubleSpinBox(CompactSpinBoxBase, QDoubleSpinBox):
281
+ """ Compact double spin box """
282
+
283
+
284
+ class TimeEdit(InlineSpinBoxBase, QTimeEdit):
285
+ """ Time edit """
286
+
287
+
288
+ class CompactTimeEdit(CompactSpinBoxBase, QTimeEdit):
289
+ """ Compact time edit """
290
+
291
+
292
+ class DateTimeEdit(InlineSpinBoxBase, QDateTimeEdit):
293
+ """ Date time edit """
294
+
295
+
296
+ class CompactDateTimeEdit(CompactSpinBoxBase, QDateTimeEdit):
297
+ """ Compact date time edit """
298
+
299
+
300
+ class DateEdit(InlineSpinBoxBase, QDateEdit):
301
+ """ Date edit """
302
+
303
+
304
+ class CompactDateEdit(CompactSpinBoxBase, QDateEdit):
305
+ """ Compact date edit """
306
+