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,211 @@
1
+ # coding:utf-8
2
+ from typing import List
3
+
4
+ from PySide6.QtCore import (QAbstractAnimation, QEasingCurve, QPoint, QPropertyAnimation,
5
+ Signal)
6
+ from PySide6.QtWidgets import QGraphicsOpacityEffect, QStackedWidget, QWidget
7
+
8
+
9
+ class OpacityAniStackedWidget(QStackedWidget):
10
+ """ Stacked widget with fade in and fade out animation """
11
+
12
+ def __init__(self, parent=None):
13
+ super().__init__(parent=parent)
14
+ self.__nextIndex = 0
15
+ self.__effects = [] # type:List[QPropertyAnimation]
16
+ self.__anis = [] # type:List[QPropertyAnimation]
17
+
18
+ def addWidget(self, w: QWidget):
19
+ super().addWidget(w)
20
+
21
+ effect = QGraphicsOpacityEffect(self)
22
+ effect.setOpacity(1)
23
+ ani = QPropertyAnimation(effect, b'opacity', self)
24
+ ani.setDuration(220)
25
+ ani.finished.connect(self.__onAniFinished)
26
+ self.__anis.append(ani)
27
+ self.__effects.append(effect)
28
+ w.setGraphicsEffect(effect)
29
+
30
+ def setCurrentIndex(self, index: int):
31
+ index_ = self.currentIndex()
32
+ if index == index_:
33
+ return
34
+
35
+ if index > index_:
36
+ ani = self.__anis[index]
37
+ ani.setStartValue(0)
38
+ ani.setEndValue(1)
39
+ super().setCurrentIndex(index)
40
+ else:
41
+ ani = self.__anis[index_]
42
+ ani.setStartValue(1)
43
+ ani.setEndValue(0)
44
+
45
+ self.widget(index_).show()
46
+ self.__nextIndex = index
47
+ ani.start()
48
+
49
+ def setCurrentWidget(self, w: QWidget):
50
+ self.setCurrentIndex(self.indexOf(w))
51
+
52
+ def __onAniFinished(self):
53
+ super().setCurrentIndex(self.__nextIndex)
54
+
55
+
56
+ class PopUpAniInfo:
57
+ """ Pop up ani info """
58
+
59
+ def __init__(self, widget: QWidget, deltaX: int, deltaY, ani: QPropertyAnimation):
60
+ self.widget = widget
61
+ self.deltaX = deltaX
62
+ self.deltaY = deltaY
63
+ self.ani = ani
64
+
65
+
66
+ class PopUpAniStackedWidget(QStackedWidget):
67
+ """ Stacked widget with pop up animation """
68
+
69
+ aniFinished = Signal()
70
+ aniStart = Signal()
71
+
72
+ def __init__(self, parent=None):
73
+ super().__init__(parent)
74
+ self.aniInfos = [] # type: List[PopUpAniInfo]
75
+ self.isAnimationEnabled = True
76
+ self._nextIndex = None
77
+ self._ani = None
78
+
79
+ def addWidget(self, widget, deltaX=0, deltaY=76):
80
+ """ add widget to window
81
+
82
+ Parameters
83
+ -----------
84
+ widget:
85
+ widget to be added
86
+
87
+ deltaX: int
88
+ the x-axis offset from the beginning to the end of animation
89
+
90
+ deltaY: int
91
+ the y-axis offset from the beginning to the end of animation
92
+ """
93
+ super().addWidget(widget)
94
+
95
+ self.aniInfos.append(PopUpAniInfo(
96
+ widget=widget,
97
+ deltaX=deltaX,
98
+ deltaY=deltaY,
99
+ ani=QPropertyAnimation(widget, b'pos'),
100
+ ))
101
+
102
+ def removeWidget(self, widget: QWidget):
103
+ index = self.indexOf(widget)
104
+ if index == -1:
105
+ return
106
+
107
+ self.aniInfos.pop(index)
108
+ super().removeWidget(widget)
109
+
110
+ def setAnimationEnabled(self, isEnabled: bool):
111
+ """set whether the pop animation is enabled"""
112
+ self.isAnimationEnabled = isEnabled
113
+
114
+ def setCurrentIndex(self, index: int, needPopOut: bool = False, showNextWidgetDirectly: bool = True,
115
+ duration: int = 250, easingCurve=QEasingCurve.OutQuad):
116
+ """ set current window to display
117
+
118
+ Parameters
119
+ ----------
120
+ index: int
121
+ the index of widget to display
122
+
123
+ isNeedPopOut: bool
124
+ need pop up animation or not
125
+
126
+ showNextWidgetDirectly: bool
127
+ whether to show next widget directly when animation started
128
+
129
+ duration: int
130
+ animation duration
131
+
132
+ easingCurve: QEasingCurve
133
+ the interpolation mode of animation
134
+ """
135
+ if index < 0 or index >= self.count():
136
+ raise Exception(f'The index `{index}` is illegal')
137
+
138
+ if index == self.currentIndex():
139
+ return
140
+
141
+ if not self.isAnimationEnabled:
142
+ return super().setCurrentIndex(index)
143
+
144
+ if self._ani and self._ani.state() == QAbstractAnimation.Running:
145
+ self._ani.stop()
146
+ self.__onAniFinished()
147
+
148
+ # get the index of widget to be displayed
149
+ self._nextIndex = index
150
+
151
+ # get animation
152
+ nextAniInfo = self.aniInfos[index]
153
+ currentAniInfo = self.aniInfos[self.currentIndex()]
154
+
155
+ currentWidget = self.currentWidget()
156
+ nextWidget = nextAniInfo.widget
157
+ ani = currentAniInfo.ani if needPopOut else nextAniInfo.ani
158
+ self._ani = ani
159
+
160
+ if needPopOut:
161
+ deltaX, deltaY = currentAniInfo.deltaX, currentAniInfo.deltaY
162
+ pos = currentWidget.pos() + QPoint(deltaX, deltaY)
163
+ self.__setAnimation(ani, currentWidget.pos(), pos, duration, easingCurve)
164
+ nextWidget.setVisible(showNextWidgetDirectly)
165
+ else:
166
+ deltaX, deltaY = nextAniInfo.deltaX, nextAniInfo.deltaY
167
+ pos = nextWidget.pos() + QPoint(deltaX, deltaY)
168
+ self.__setAnimation(ani, pos, QPoint(nextWidget.x(), 0), duration, easingCurve)
169
+ super().setCurrentIndex(index)
170
+
171
+ # start animation
172
+ ani.finished.connect(self.__onAniFinished)
173
+ ani.start()
174
+ self.aniStart.emit()
175
+
176
+ def setCurrentWidget(self, widget, needPopOut: bool = False, showNextWidgetDirectly: bool = True,
177
+ duration: int = 250, easingCurve=QEasingCurve.OutQuad):
178
+ """ set currect widget
179
+
180
+ Parameters
181
+ ----------
182
+ widget:
183
+ the widget to be displayed
184
+
185
+ isNeedPopOut: bool
186
+ need pop up animation or not
187
+
188
+ showNextWidgetDirectly: bool
189
+ whether to show next widget directly when animation started
190
+
191
+ duration: int
192
+ animation duration
193
+
194
+ easingCurve: QEasingCurve
195
+ the interpolation mode of animation
196
+ """
197
+ self.setCurrentIndex(
198
+ self.indexOf(widget), needPopOut, showNextWidgetDirectly, duration, easingCurve)
199
+
200
+ def __setAnimation(self, ani, startValue, endValue, duration, easingCurve=QEasingCurve.Linear):
201
+ """ set the config of animation """
202
+ ani.setEasingCurve(easingCurve)
203
+ ani.setStartValue(startValue)
204
+ ani.setEndValue(endValue)
205
+ ani.setDuration(duration)
206
+
207
+ def __onAniFinished(self):
208
+ """ animation finished slot """
209
+ self._ani.finished.disconnect()
210
+ super().setCurrentIndex(self._nextIndex)
211
+ self.aniFinished.emit()
@@ -0,0 +1,188 @@
1
+ # coding:utf-8
2
+ from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt, QTimer, Signal, QSize, QPoint, QRectF
3
+ from PySide6.QtGui import QPainter
4
+ from PySide6.QtWidgets import QLabel, QWidget, QToolButton, QGraphicsOpacityEffect
5
+ from PySide6.QtSvgWidgets import QSvgWidget
6
+
7
+ from ...common import FluentStyleSheet, isDarkTheme, Theme
8
+ from ...common.icon import FluentIcon as FIF
9
+
10
+
11
+ class StateCloseButton(QToolButton):
12
+
13
+ def __init__(self, parent=None):
14
+ super().__init__(parent)
15
+ self.setFixedSize(12, 12)
16
+ self.isPressed = False
17
+ self.isEnter = False
18
+
19
+ def enterEvent(self, e):
20
+ self.isEnter = True
21
+ self.update()
22
+
23
+ def leaveEvent(self, e):
24
+ self.isEnter = False
25
+ self.isPressed = False
26
+ self.update()
27
+
28
+ def mousePressEvent(self, e):
29
+ self.isPressed = True
30
+ self.update()
31
+ super().mousePressEvent(e)
32
+
33
+ def mouseReleaseEvent(self, e):
34
+ self.isPressed = False
35
+ self.update()
36
+ super().mouseReleaseEvent(e)
37
+
38
+ def paintEvent(self, e):
39
+ painter = QPainter(self)
40
+ painter.setRenderHints(QPainter.Antialiasing)
41
+ if self.isPressed:
42
+ painter.setOpacity(0.6)
43
+ elif self.isEnter:
44
+ painter.setOpacity(0.8)
45
+
46
+ theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
47
+ FIF.CLOSE.render(painter, self.rect(), theme)
48
+
49
+
50
+ class StateToolTip(QWidget):
51
+ """ State tooltip """
52
+
53
+ closedSignal = Signal()
54
+
55
+ def __init__(self, title, content, parent=None):
56
+ """
57
+ Parameters
58
+ ----------
59
+ title: str
60
+ title of tooltip
61
+
62
+ content: str
63
+ content of tooltip
64
+
65
+ parant:
66
+ parent window
67
+ """
68
+ super().__init__(parent)
69
+ self.title = title
70
+ self.content = content
71
+
72
+ self.titleLabel = QLabel(self.title, self)
73
+ self.contentLabel = QLabel(self.content, self)
74
+ self.rotateTimer = QTimer(self)
75
+
76
+ self.opacityEffect = QGraphicsOpacityEffect(self)
77
+ self.animation = QPropertyAnimation(self.opacityEffect, b"opacity")
78
+ self.closeButton = StateCloseButton(self)
79
+
80
+ self.isDone = False
81
+ self.rotateAngle = 0
82
+ self.deltaAngle = 20
83
+
84
+ self.__initWidget()
85
+
86
+ def __initWidget(self):
87
+ """ initialize widgets """
88
+ self.setAttribute(Qt.WA_StyledBackground)
89
+ self.setGraphicsEffect(self.opacityEffect)
90
+ self.opacityEffect.setOpacity(1)
91
+ self.rotateTimer.setInterval(50)
92
+ self.contentLabel.setMinimumWidth(200)
93
+
94
+ # connect signal to slot
95
+ self.closeButton.clicked.connect(self.__onCloseButtonClicked)
96
+ self.rotateTimer.timeout.connect(self.__rotateTimerFlowSlot)
97
+
98
+ self.__setQss()
99
+ self.__initLayout()
100
+
101
+ self.rotateTimer.start()
102
+
103
+ def __initLayout(self):
104
+ """ initialize layout """
105
+ self.setFixedSize(max(self.titleLabel.width(),
106
+ self.contentLabel.width()) + 56, 51)
107
+ self.titleLabel.move(32, 9)
108
+ self.contentLabel.move(12, 27)
109
+ self.closeButton.move(self.width() - 24, 19)
110
+
111
+ def __setQss(self):
112
+ """ set style sheet """
113
+ self.titleLabel.setObjectName("titleLabel")
114
+ self.contentLabel.setObjectName("contentLabel")
115
+
116
+ FluentStyleSheet.STATE_TOOL_TIP.apply(self)
117
+
118
+ self.titleLabel.adjustSize()
119
+ self.contentLabel.adjustSize()
120
+
121
+ def setTitle(self, title: str):
122
+ """ set the title of tooltip """
123
+ self.title = title
124
+ self.titleLabel.setText(title)
125
+ self.titleLabel.adjustSize()
126
+
127
+ def setContent(self, content: str):
128
+ """ set the content of tooltip """
129
+ self.content = content
130
+ self.contentLabel.setText(content)
131
+
132
+ # adjustSize() will mask spinner get stuck
133
+ self.contentLabel.adjustSize()
134
+
135
+ def setState(self, isDone=False):
136
+ """ set the state of tooltip """
137
+ self.isDone = isDone
138
+ self.update()
139
+ if isDone:
140
+ QTimer.singleShot(1000, self.__fadeOut)
141
+
142
+ def __onCloseButtonClicked(self):
143
+ """ close button clicked slot """
144
+ self.closedSignal.emit()
145
+ self.hide()
146
+
147
+ def __fadeOut(self):
148
+ """ fade out """
149
+ self.rotateTimer.stop()
150
+ self.animation.setDuration(200)
151
+ self.animation.setStartValue(1)
152
+ self.animation.setEndValue(0)
153
+ self.animation.finished.connect(self.deleteLater)
154
+ self.animation.start()
155
+
156
+ def __rotateTimerFlowSlot(self):
157
+ """ rotate timer time out slot """
158
+ self.rotateAngle = (self.rotateAngle + self.deltaAngle) % 360
159
+ self.update()
160
+
161
+ def getSuitablePos(self):
162
+ """ get suitable position in main window """
163
+ for i in range(10):
164
+ dy = i*(self.height() + 16)
165
+ pos = QPoint(self.parent().width() - self.width() - 24, 50+dy)
166
+ widget = self.parent().childAt(pos + QPoint(2, 2))
167
+ if isinstance(widget, StateToolTip):
168
+ pos += QPoint(0, self.height() + 16)
169
+ else:
170
+ break
171
+
172
+ return pos
173
+
174
+ def paintEvent(self, e):
175
+ """ paint state tooltip """
176
+ super().paintEvent(e)
177
+ painter = QPainter(self)
178
+ painter.setRenderHints(QPainter.Antialiasing)
179
+ painter.setPen(Qt.NoPen)
180
+ theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
181
+
182
+ if not self.isDone:
183
+ painter.translate(19, 18)
184
+ painter.rotate(self.rotateAngle)
185
+ FIF.SYNC.render(painter, QRectF(-8, -8, 16, 16), theme)
186
+ else:
187
+ FIF.COMPLETED.render(painter, QRectF(11, 10, 16, 16), theme)
188
+
@@ -0,0 +1,312 @@
1
+ # coding: utf-8
2
+ from enum import Enum
3
+
4
+ from PySide6.QtCore import Qt, QTimer, Property, Signal, QEvent, QPoint, QPropertyAnimation, QEasingCurve
5
+ from PySide6.QtGui import QColor, QPainter, QHoverEvent
6
+ from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QToolButton, QWidget
7
+
8
+ from ...common.style_sheet import FluentStyleSheet, themeColor, ThemeColor, isDarkTheme, setCustomStyleSheet
9
+ from ...common.overload import singledispatchmethod
10
+ from ...common.color import fallbackThemeColor, validColor
11
+ from .button import ToolButton
12
+
13
+
14
+ class Indicator(ToolButton):
15
+ """ Indicator of switch button """
16
+
17
+ checkedChanged = Signal(bool)
18
+
19
+ def __init__(self, parent):
20
+ super().__init__(parent=parent)
21
+ self.setCheckable(True)
22
+ self.setFixedSize(42, 22)
23
+ self.lightCheckedColor = QColor()
24
+ self.darkCheckedColor = QColor()
25
+
26
+ self._sliderX = 5
27
+ self.slideAni = QPropertyAnimation(self, b'sliderX', self)
28
+ self.slideAni.setDuration(120)
29
+
30
+ self.toggled.connect(self._toggleSlider)
31
+
32
+ def mouseReleaseEvent(self, e):
33
+ """ toggle checked state when mouse release"""
34
+ super().mouseReleaseEvent(e)
35
+ self.checkedChanged.emit(self.isChecked())
36
+
37
+ def _toggleSlider(self):
38
+ self.slideAni.setEndValue(25 if self.isChecked() else 5)
39
+ self.slideAni.start()
40
+
41
+ def toggle(self):
42
+ self.setChecked(not self.isChecked())
43
+
44
+ def setDown(self, isDown: bool):
45
+ self.isPressed = isDown
46
+ super().setDown(isDown)
47
+
48
+ def setHover(self, isHover: bool):
49
+ self.isHover = isHover
50
+ self.update()
51
+
52
+ def setCheckedColor(self, light, dark):
53
+ self.lightCheckedColor = QColor(light)
54
+ self.darkCheckedColor = QColor(dark)
55
+ self.update()
56
+
57
+ def paintEvent(self, e):
58
+ """ paint indicator """
59
+ painter = QPainter(self)
60
+ painter.setRenderHints(QPainter.Antialiasing)
61
+ self._drawBackground(painter)
62
+ self._drawCircle(painter)
63
+
64
+ def _drawBackground(self, painter: QPainter):
65
+ r = self.height() / 2
66
+ painter.setPen(self._borderColor())
67
+ painter.setBrush(self._backgroundColor())
68
+ painter.drawRoundedRect(self.rect().adjusted(1, 1, -1, -1), r, r)
69
+
70
+ def _drawCircle(self, painter: QPainter):
71
+ painter.setPen(Qt.NoPen)
72
+ painter.setBrush(self._sliderColor())
73
+ painter.drawEllipse(int(self.sliderX), 5, 12, 12)
74
+
75
+ def _backgroundColor(self):
76
+ isDark = isDarkTheme()
77
+
78
+ if self.isChecked():
79
+ color = self.darkCheckedColor if isDark else self.lightCheckedColor
80
+ if not self.isEnabled():
81
+ return QColor(255, 255, 255, 41) if isDark else QColor(0, 0, 0, 56)
82
+ if self.isPressed:
83
+ return validColor(color, ThemeColor.LIGHT_2.color())
84
+ elif self.isHover:
85
+ return validColor(color, ThemeColor.LIGHT_1.color())
86
+
87
+ return fallbackThemeColor(color)
88
+ else:
89
+ if not self.isEnabled():
90
+ return QColor(0, 0, 0, 0)
91
+ if self.isPressed:
92
+ return QColor(255, 255, 255, 18) if isDark else QColor(0, 0, 0, 23)
93
+ elif self.isHover:
94
+ return QColor(255, 255, 255, 10) if isDark else QColor(0, 0, 0, 15)
95
+
96
+ return QColor(0, 0, 0, 0)
97
+
98
+ def _borderColor(self):
99
+ isDark = isDarkTheme()
100
+
101
+ if self.isChecked():
102
+ return self._backgroundColor() if self.isEnabled() else QColor(0, 0, 0, 0)
103
+ else:
104
+ if self.isEnabled():
105
+ return QColor(255, 255, 255, 153) if isDark else QColor(0, 0, 0, 133)
106
+
107
+ return QColor(255, 255, 255, 41) if isDark else QColor(0, 0, 0, 56)
108
+
109
+ def _sliderColor(self):
110
+ isDark = isDarkTheme()
111
+
112
+ if self.isChecked():
113
+ if self.isEnabled():
114
+ return QColor(Qt.black if isDark else Qt.white)
115
+
116
+ return QColor(255, 255, 255, 77) if isDark else QColor(255, 255, 255)
117
+ else:
118
+ if self.isEnabled():
119
+ return QColor(255, 255, 255, 201) if isDark else QColor(0, 0, 0, 156)
120
+
121
+ return QColor(255, 255, 255, 96) if isDark else QColor(0, 0, 0, 91)
122
+
123
+ def getSliderX(self):
124
+ return self._sliderX
125
+
126
+ def setSliderX(self, x):
127
+ self._sliderX = max(x, 5)
128
+ self.update()
129
+
130
+ sliderX = Property(float, getSliderX, setSliderX)
131
+
132
+
133
+ class IndicatorPosition(Enum):
134
+ """ Indicator position """
135
+ LEFT = 0
136
+ RIGHT = 1
137
+
138
+
139
+ class SwitchButton(QWidget):
140
+ """ Switch button class
141
+
142
+ Constructors
143
+ ------------
144
+ * SwitchButton(`parent`: QWidget = None)
145
+ * SwitchButton(`text`: str = "Off", `parent`: QWidget = None, `indicatorPos`=IndicatorPosition.LEFT)
146
+ """
147
+
148
+ checkedChanged = Signal(bool)
149
+
150
+ @singledispatchmethod
151
+ def __init__(self, parent: QWidget = None, indicatorPos=IndicatorPosition.LEFT):
152
+ """
153
+ Parameters
154
+ ----------
155
+ parent: QWidget
156
+ parent widget
157
+
158
+ indicatorPosition: IndicatorPosition
159
+ the position of indicator
160
+ """
161
+ super().__init__(parent=parent)
162
+ self._text = self.tr('Off')
163
+ self._offText = self.tr('Off')
164
+ self._onText = self.tr('On')
165
+ self.__spacing = 12
166
+ self.lightTextColor = QColor(0, 0, 0)
167
+ self.darkTextColor = QColor(255, 255, 255)
168
+
169
+ self.indicatorPos = indicatorPos
170
+ self.hBox = QHBoxLayout(self)
171
+ self.indicator = Indicator(self)
172
+ self.label = QLabel(self._text, self)
173
+
174
+ self.__initWidget()
175
+
176
+ @__init__.register
177
+ def _(self, text: str = 'Off', parent: QWidget = None, indicatorPos=IndicatorPosition.LEFT):
178
+ """
179
+ Parameters
180
+ ----------
181
+ text: str
182
+ the text of switch button
183
+
184
+ parent: QWidget
185
+ parent widget
186
+
187
+ indicatorPosition: IndicatorPosition
188
+ the position of indicator
189
+ """
190
+ self.__init__(parent, indicatorPos)
191
+ self._offText = text
192
+ self.setText(text)
193
+
194
+ def __initWidget(self):
195
+ """ initialize widgets """
196
+ self.setAttribute(Qt.WA_StyledBackground)
197
+ self.installEventFilter(self)
198
+ self.setFixedHeight(22)
199
+
200
+ # set layout
201
+ self.hBox.setSpacing(self.__spacing)
202
+ self.hBox.setContentsMargins(2, 0, 0, 0)
203
+
204
+ if self.indicatorPos == IndicatorPosition.LEFT:
205
+ self.hBox.addWidget(self.indicator)
206
+ self.hBox.addWidget(self.label)
207
+ self.hBox.setAlignment(Qt.AlignLeft)
208
+ else:
209
+ self.hBox.addWidget(self.label, 0, Qt.AlignRight)
210
+ self.hBox.addWidget(self.indicator, 0, Qt.AlignRight)
211
+ self.hBox.setAlignment(Qt.AlignRight)
212
+
213
+ # set default style sheet
214
+ FluentStyleSheet.SWITCH_BUTTON.apply(self)
215
+ FluentStyleSheet.SWITCH_BUTTON.apply(self.label)
216
+
217
+ # connect signal to slot
218
+ self.indicator.toggled.connect(self._updateText)
219
+ self.indicator.toggled.connect(self.checkedChanged)
220
+
221
+ def eventFilter(self, obj, e: QEvent):
222
+ if obj is self and self.isEnabled():
223
+ if e.type() == QEvent.MouseButtonPress:
224
+ self.indicator.setDown(True)
225
+ elif e.type() == QEvent.MouseButtonRelease:
226
+ self.indicator.setDown(False)
227
+ self.indicator.toggle()
228
+ elif e.type() == QEvent.Enter:
229
+ self.indicator.setHover(True)
230
+ elif e.type() == QEvent.Leave:
231
+ self.indicator.setHover(False)
232
+
233
+ return super().eventFilter(obj, e)
234
+
235
+ def isChecked(self):
236
+ return self.indicator.isChecked()
237
+
238
+ def setChecked(self, isChecked):
239
+ """ set checked state """
240
+ self._updateText()
241
+ self.indicator.setChecked(isChecked)
242
+
243
+ def setTextColor(self, light, dark):
244
+ """ set the color of text
245
+
246
+ Parameters
247
+ ----------
248
+ light, dark: str | QColor | Qt.GlobalColor
249
+ text color in light/dark theme mode
250
+ """
251
+ self.lightTextColor = QColor(light)
252
+ self.darkTextColor = QColor(dark)
253
+
254
+ setCustomStyleSheet(
255
+ self.label,
256
+ f"SwitchButton>QLabel{{color:{self.lightTextColor.name(QColor.NameFormat.HexArgb)}}}",
257
+ f"SwitchButton>QLabel{{color:{self.darkTextColor.name(QColor.NameFormat.HexArgb)}}}"
258
+ )
259
+
260
+ def setCheckedIndicatorColor(self, light, dark):
261
+ """ set the color of indicator in checked status
262
+
263
+ Parameters
264
+ ----------
265
+ light, dark: str | QColor | Qt.GlobalColor
266
+ border color in light/dark theme mode
267
+ """
268
+ self.indicator.setCheckedColor(light, dark)
269
+
270
+ def toggleChecked(self):
271
+ """ toggle checked state """
272
+ self.indicator.setChecked(not self.indicator.isChecked())
273
+
274
+ def _updateText(self):
275
+ self.setText(self.onText if self.isChecked() else self.offText)
276
+ self.adjustSize()
277
+
278
+ def getText(self):
279
+ return self._text
280
+
281
+ def setText(self, text):
282
+ self._text = text
283
+ self.label.setText(text)
284
+ self.adjustSize()
285
+
286
+ def getSpacing(self):
287
+ return self.__spacing
288
+
289
+ def setSpacing(self, spacing):
290
+ self.__spacing = spacing
291
+ self.hBox.setSpacing(spacing)
292
+ self.update()
293
+
294
+ def getOnText(self):
295
+ return self._onText
296
+
297
+ def setOnText(self, text):
298
+ self._onText = text
299
+ self._updateText()
300
+
301
+ def getOffText(self):
302
+ return self._offText
303
+
304
+ def setOffText(self, text):
305
+ self._offText = text
306
+ self._updateText()
307
+
308
+ spacing = Property(int, getSpacing, setSpacing)
309
+ checked = Property(bool, isChecked, setChecked)
310
+ text = Property(str, getText, setText)
311
+ onText = Property(str, getOnText, setOnText)
312
+ offText = Property(str, getOffText, setOffText)