Python-FastUI-Widgets 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastuiwidgets/__init__.py +12 -0
- fastuiwidgets/_rc/__init__.py +0 -0
- fastuiwidgets/_rc/resource.py +98835 -0
- fastuiwidgets/common/__init__.py +12 -0
- fastuiwidgets/common/animation.py +530 -0
- fastuiwidgets/common/auto_wrap.py +164 -0
- fastuiwidgets/common/color.py +95 -0
- fastuiwidgets/common/config.py +423 -0
- fastuiwidgets/common/exception_handler.py +31 -0
- fastuiwidgets/common/font.py +38 -0
- fastuiwidgets/common/icon.py +703 -0
- fastuiwidgets/common/image_utils.py +198 -0
- fastuiwidgets/common/overload.py +47 -0
- fastuiwidgets/common/router.py +133 -0
- fastuiwidgets/common/screen.py +25 -0
- fastuiwidgets/common/smooth_scroll.py +141 -0
- fastuiwidgets/common/style_sheet.py +512 -0
- fastuiwidgets/common/theme_listener.py +27 -0
- fastuiwidgets/common/translator.py +14 -0
- fastuiwidgets/components/__init__.py +6 -0
- fastuiwidgets/components/date_time/__init__.py +4 -0
- fastuiwidgets/components/date_time/calendar_picker.py +121 -0
- fastuiwidgets/components/date_time/calendar_view.py +671 -0
- fastuiwidgets/components/date_time/date_picker.py +245 -0
- fastuiwidgets/components/date_time/fast_calendar_view.py +487 -0
- fastuiwidgets/components/date_time/picker_base.py +632 -0
- fastuiwidgets/components/date_time/time_picker.py +223 -0
- fastuiwidgets/components/dialog_box/__init__.py +6 -0
- fastuiwidgets/components/dialog_box/color_dialog.py +414 -0
- fastuiwidgets/components/dialog_box/dialog.py +167 -0
- fastuiwidgets/components/dialog_box/folder_list_dialog.py +307 -0
- fastuiwidgets/components/dialog_box/mask_dialog_base.py +120 -0
- fastuiwidgets/components/dialog_box/message_box_base.py +92 -0
- fastuiwidgets/components/dialog_box/message_dialog.py +65 -0
- fastuiwidgets/components/layout/__init__.py +3 -0
- fastuiwidgets/components/layout/expand_layout.py +96 -0
- fastuiwidgets/components/layout/flow_layout.py +236 -0
- fastuiwidgets/components/layout/v_box_layout.py +41 -0
- fastuiwidgets/components/material/__init__.py +6 -0
- fastuiwidgets/components/material/acrylic_combo_box.py +96 -0
- fastuiwidgets/components/material/acrylic_flyout.py +105 -0
- fastuiwidgets/components/material/acrylic_line_edit.py +27 -0
- fastuiwidgets/components/material/acrylic_menu.py +204 -0
- fastuiwidgets/components/material/acrylic_tool_tip.py +39 -0
- fastuiwidgets/components/material/acrylic_widget.py +42 -0
- fastuiwidgets/components/navigation/__init__.py +9 -0
- fastuiwidgets/components/navigation/breadcrumb.py +350 -0
- fastuiwidgets/components/navigation/navigation_bar.py +416 -0
- fastuiwidgets/components/navigation/navigation_interface.py +268 -0
- fastuiwidgets/components/navigation/navigation_panel.py +657 -0
- fastuiwidgets/components/navigation/navigation_widget.py +686 -0
- fastuiwidgets/components/navigation/pivot.py +272 -0
- fastuiwidgets/components/navigation/segmented_widget.py +174 -0
- fastuiwidgets/components/settings/__init__.py +8 -0
- fastuiwidgets/components/settings/custom_color_setting_card.py +139 -0
- fastuiwidgets/components/settings/expand_setting_card.py +390 -0
- fastuiwidgets/components/settings/folder_list_setting_card.py +134 -0
- fastuiwidgets/components/settings/options_setting_card.py +86 -0
- fastuiwidgets/components/settings/setting_card.py +449 -0
- fastuiwidgets/components/settings/setting_card_group.py +48 -0
- fastuiwidgets/components/widgets/__init__.py +41 -0
- fastuiwidgets/components/widgets/acrylic_label.py +261 -0
- fastuiwidgets/components/widgets/button.py +1059 -0
- fastuiwidgets/components/widgets/card_widget.py +369 -0
- fastuiwidgets/components/widgets/check_box.py +203 -0
- fastuiwidgets/components/widgets/combo_box.py +556 -0
- fastuiwidgets/components/widgets/command_bar.py +636 -0
- fastuiwidgets/components/widgets/cycle_list_widget.py +251 -0
- fastuiwidgets/components/widgets/flip_view.py +430 -0
- fastuiwidgets/components/widgets/flyout.py +521 -0
- fastuiwidgets/components/widgets/frameless_window.py +49 -0
- fastuiwidgets/components/widgets/icon_widget.py +53 -0
- fastuiwidgets/components/widgets/info_badge.py +483 -0
- fastuiwidgets/components/widgets/info_bar.py +596 -0
- fastuiwidgets/components/widgets/label.py +553 -0
- fastuiwidgets/components/widgets/line_edit.py +551 -0
- fastuiwidgets/components/widgets/list_view.py +158 -0
- fastuiwidgets/components/widgets/menu.py +1318 -0
- fastuiwidgets/components/widgets/pips_pager.py +331 -0
- fastuiwidgets/components/widgets/progress_bar.py +311 -0
- fastuiwidgets/components/widgets/progress_ring.py +212 -0
- fastuiwidgets/components/widgets/scroll_area.py +125 -0
- fastuiwidgets/components/widgets/scroll_bar.py +673 -0
- fastuiwidgets/components/widgets/separator.py +43 -0
- fastuiwidgets/components/widgets/slider.py +307 -0
- fastuiwidgets/components/widgets/spin_box.py +306 -0
- fastuiwidgets/components/widgets/stacked_widget.py +211 -0
- fastuiwidgets/components/widgets/state_tool_tip.py +188 -0
- fastuiwidgets/components/widgets/switch_button.py +312 -0
- fastuiwidgets/components/widgets/tab_view.py +804 -0
- fastuiwidgets/components/widgets/table_view.py +360 -0
- fastuiwidgets/components/widgets/teaching_tip.py +657 -0
- fastuiwidgets/components/widgets/tool_tip.py +460 -0
- fastuiwidgets/components/widgets/tree_view.py +216 -0
- fastuiwidgets/multimedia/__init__.py +3 -0
- fastuiwidgets/multimedia/media_play_bar.py +319 -0
- fastuiwidgets/multimedia/media_player.py +124 -0
- fastuiwidgets/multimedia/video_widget.py +93 -0
- fastuiwidgets/window/__init__.py +2 -0
- fastuiwidgets/window/fluent_window.py +413 -0
- fastuiwidgets/window/splash_screen.py +92 -0
- fastuiwidgets/window/stacked_widget.py +66 -0
- python_fastui_widgets-1.0.0.dist-info/METADATA +30 -0
- python_fastui_widgets-1.0.0.dist-info/RECORD +107 -0
- python_fastui_widgets-1.0.0.dist-info/WHEEL +5 -0
- python_fastui_widgets-1.0.0.dist-info/licenses/LICENSE +674 -0
- python_fastui_widgets-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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)
|