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,12 @@
|
|
1
|
+
from .config import *
|
2
|
+
from .font import setFont, getFont
|
3
|
+
from .auto_wrap import TextWrap
|
4
|
+
from .icon import Action, Icon, getIconColor, drawSvgIcon, FluentIcon, drawIcon, FluentIconBase, writeSvg, FluentFontIconBase
|
5
|
+
from .style_sheet import (setStyleSheet, getStyleSheet, setTheme, ThemeColor, themeColor,
|
6
|
+
setThemeColor, applyThemeColor, FluentStyleSheet, StyleSheetBase,
|
7
|
+
StyleSheetFile, StyleSheetCompose, CustomStyleSheet, toggleTheme, setCustomStyleSheet)
|
8
|
+
from .smooth_scroll import SmoothScroll, SmoothMode
|
9
|
+
from .translator import FluentTranslator
|
10
|
+
from .router import qrouter, Router
|
11
|
+
from .color import FluentThemeColor, FluentSystemColor
|
12
|
+
from .theme_listener import SystemThemeListener
|
@@ -0,0 +1,530 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
from enum import Enum
|
3
|
+
from PySide6.QtCore import QEasingCurve, QEvent, QObject, QPropertyAnimation, Property, Signal, QPoint, QPointF
|
4
|
+
from PySide6.QtGui import QMouseEvent, QEnterEvent, QColor
|
5
|
+
from PySide6.QtWidgets import QWidget, QLineEdit, QGraphicsDropShadowEffect
|
6
|
+
|
7
|
+
from .config import qconfig
|
8
|
+
|
9
|
+
|
10
|
+
class AnimationBase(QObject):
|
11
|
+
""" Animation base class """
|
12
|
+
|
13
|
+
def __init__(self, parent: QWidget):
|
14
|
+
super().__init__(parent=parent)
|
15
|
+
parent.installEventFilter(self)
|
16
|
+
|
17
|
+
def _onHover(self, e: QEnterEvent):
|
18
|
+
pass
|
19
|
+
|
20
|
+
def _onLeave(self, e: QEvent):
|
21
|
+
pass
|
22
|
+
|
23
|
+
def _onPress(self, e: QMouseEvent):
|
24
|
+
pass
|
25
|
+
|
26
|
+
def _onRelease(self, e: QMouseEvent):
|
27
|
+
pass
|
28
|
+
|
29
|
+
def eventFilter(self, obj, e: QEvent):
|
30
|
+
if obj is self.parent():
|
31
|
+
if e.type() == QEvent.MouseButtonPress:
|
32
|
+
self._onPress(e)
|
33
|
+
elif e.type() == QEvent.MouseButtonRelease:
|
34
|
+
self._onRelease(e)
|
35
|
+
elif e.type() == QEvent.Enter:
|
36
|
+
self._onHover(e)
|
37
|
+
elif e.type() == QEvent.Leave:
|
38
|
+
self._onLeave(e)
|
39
|
+
|
40
|
+
return super().eventFilter(obj, e)
|
41
|
+
|
42
|
+
|
43
|
+
class TranslateYAnimation(AnimationBase):
|
44
|
+
|
45
|
+
valueChanged = Signal(float)
|
46
|
+
|
47
|
+
def __init__(self, parent: QWidget, offset=2):
|
48
|
+
super().__init__(parent)
|
49
|
+
self._y = 0
|
50
|
+
self.maxOffset = offset
|
51
|
+
self.ani = QPropertyAnimation(self, b'y', self)
|
52
|
+
|
53
|
+
def getY(self):
|
54
|
+
return self._y
|
55
|
+
|
56
|
+
def setY(self, y):
|
57
|
+
self._y = y
|
58
|
+
self.parent().update()
|
59
|
+
self.valueChanged.emit(y)
|
60
|
+
|
61
|
+
def _onPress(self, e):
|
62
|
+
""" arrow down """
|
63
|
+
self.ani.setEndValue(self.maxOffset)
|
64
|
+
self.ani.setEasingCurve(QEasingCurve.OutQuad)
|
65
|
+
self.ani.setDuration(150)
|
66
|
+
self.ani.start()
|
67
|
+
|
68
|
+
def _onRelease(self, e):
|
69
|
+
""" arrow up """
|
70
|
+
self.ani.setEndValue(0)
|
71
|
+
self.ani.setDuration(500)
|
72
|
+
self.ani.setEasingCurve(QEasingCurve.OutElastic)
|
73
|
+
self.ani.start()
|
74
|
+
|
75
|
+
y = Property(float, getY, setY)
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
class BackgroundAnimationWidget:
|
80
|
+
""" Background animation widget """
|
81
|
+
|
82
|
+
def __init__(self, *args, **kwargs) -> None:
|
83
|
+
super().__init__(*args, **kwargs)
|
84
|
+
self.isHover = False
|
85
|
+
self.isPressed = False
|
86
|
+
self.bgColorObject = BackgroundColorObject(self)
|
87
|
+
self.backgroundColorAni = QPropertyAnimation(
|
88
|
+
self.bgColorObject, b'backgroundColor', self)
|
89
|
+
self.backgroundColorAni.setDuration(120)
|
90
|
+
self.installEventFilter(self)
|
91
|
+
|
92
|
+
qconfig.themeChanged.connect(self._updateBackgroundColor)
|
93
|
+
|
94
|
+
def eventFilter(self, obj, e):
|
95
|
+
if obj is self:
|
96
|
+
if e.type() == QEvent.Type.EnabledChange:
|
97
|
+
if self.isEnabled():
|
98
|
+
self.setBackgroundColor(self._normalBackgroundColor())
|
99
|
+
else:
|
100
|
+
self.setBackgroundColor(self._disabledBackgroundColor())
|
101
|
+
|
102
|
+
return super().eventFilter(obj, e)
|
103
|
+
|
104
|
+
def mousePressEvent(self, e):
|
105
|
+
self.isPressed = True
|
106
|
+
self._updateBackgroundColor()
|
107
|
+
super().mousePressEvent(e)
|
108
|
+
|
109
|
+
def mouseReleaseEvent(self, e):
|
110
|
+
self.isPressed = False
|
111
|
+
self._updateBackgroundColor()
|
112
|
+
super().mouseReleaseEvent(e)
|
113
|
+
|
114
|
+
def enterEvent(self, e):
|
115
|
+
self.isHover = True
|
116
|
+
self._updateBackgroundColor()
|
117
|
+
|
118
|
+
def leaveEvent(self, e):
|
119
|
+
self.isHover = False
|
120
|
+
self._updateBackgroundColor()
|
121
|
+
|
122
|
+
def focusInEvent(self, e):
|
123
|
+
super().focusInEvent(e)
|
124
|
+
self._updateBackgroundColor()
|
125
|
+
|
126
|
+
def _normalBackgroundColor(self):
|
127
|
+
return QColor(0, 0, 0, 0)
|
128
|
+
|
129
|
+
def _hoverBackgroundColor(self):
|
130
|
+
return self._normalBackgroundColor()
|
131
|
+
|
132
|
+
def _pressedBackgroundColor(self):
|
133
|
+
return self._normalBackgroundColor()
|
134
|
+
|
135
|
+
def _focusInBackgroundColor(self):
|
136
|
+
return self._normalBackgroundColor()
|
137
|
+
|
138
|
+
def _disabledBackgroundColor(self):
|
139
|
+
return self._normalBackgroundColor()
|
140
|
+
|
141
|
+
def _updateBackgroundColor(self):
|
142
|
+
if not self.isEnabled():
|
143
|
+
color = self._disabledBackgroundColor()
|
144
|
+
elif isinstance(self, QLineEdit) and self.hasFocus():
|
145
|
+
color = self._focusInBackgroundColor()
|
146
|
+
elif self.isPressed:
|
147
|
+
color = self._pressedBackgroundColor()
|
148
|
+
elif self.isHover:
|
149
|
+
color = self._hoverBackgroundColor()
|
150
|
+
else:
|
151
|
+
color = self._normalBackgroundColor()
|
152
|
+
|
153
|
+
self.backgroundColorAni.stop()
|
154
|
+
self.backgroundColorAni.setEndValue(color)
|
155
|
+
self.backgroundColorAni.start()
|
156
|
+
|
157
|
+
def getBackgroundColor(self):
|
158
|
+
return self.bgColorObject.backgroundColor
|
159
|
+
|
160
|
+
def setBackgroundColor(self, color: QColor):
|
161
|
+
self.bgColorObject.backgroundColor = color
|
162
|
+
|
163
|
+
@property
|
164
|
+
def backgroundColor(self):
|
165
|
+
return self.getBackgroundColor()
|
166
|
+
|
167
|
+
|
168
|
+
class BackgroundColorObject(QObject):
|
169
|
+
""" Background color object """
|
170
|
+
|
171
|
+
def __init__(self, parent: BackgroundAnimationWidget):
|
172
|
+
super().__init__(parent)
|
173
|
+
self._backgroundColor = parent._normalBackgroundColor()
|
174
|
+
|
175
|
+
@Property(QColor)
|
176
|
+
def backgroundColor(self):
|
177
|
+
return self._backgroundColor
|
178
|
+
|
179
|
+
@backgroundColor.setter
|
180
|
+
def backgroundColor(self, color: QColor):
|
181
|
+
self._backgroundColor = color
|
182
|
+
self.parent().update()
|
183
|
+
|
184
|
+
class DropShadowAnimation(QPropertyAnimation):
|
185
|
+
""" Drop shadow animation """
|
186
|
+
|
187
|
+
def __init__(self, parent: QWidget, normalColor=QColor(0, 0, 0, 0), hoverColor=QColor(0, 0, 0, 75)):
|
188
|
+
super().__init__(parent=parent)
|
189
|
+
self.normalColor = normalColor
|
190
|
+
self.hoverColor = hoverColor
|
191
|
+
self.offset = QPoint(0, 0)
|
192
|
+
self.blurRadius = 38
|
193
|
+
self.isHover = False
|
194
|
+
|
195
|
+
self.shadowEffect = QGraphicsDropShadowEffect(self)
|
196
|
+
self.shadowEffect.setColor(self.normalColor)
|
197
|
+
|
198
|
+
parent.installEventFilter(self)
|
199
|
+
|
200
|
+
def setBlurRadius(self, radius: int):
|
201
|
+
self.blurRadius = radius
|
202
|
+
|
203
|
+
def setOffset(self, dx: int, dy: int):
|
204
|
+
self.offset = QPoint(dx, dy)
|
205
|
+
|
206
|
+
def setNormalColor(self, color: QColor):
|
207
|
+
self.normalColor = color
|
208
|
+
|
209
|
+
def setHoverColor(self, color: QColor):
|
210
|
+
self.hoverColor = color
|
211
|
+
|
212
|
+
def setColor(self, color):
|
213
|
+
pass
|
214
|
+
|
215
|
+
def _createShadowEffect(self):
|
216
|
+
self.shadowEffect = QGraphicsDropShadowEffect(self)
|
217
|
+
self.shadowEffect.setOffset(self.offset)
|
218
|
+
self.shadowEffect.setBlurRadius(self.blurRadius)
|
219
|
+
self.shadowEffect.setColor(self.normalColor)
|
220
|
+
|
221
|
+
self.setTargetObject(self.shadowEffect)
|
222
|
+
self.setStartValue(self.shadowEffect.color())
|
223
|
+
self.setPropertyName(b'color')
|
224
|
+
self.setDuration(150)
|
225
|
+
|
226
|
+
return self.shadowEffect
|
227
|
+
|
228
|
+
def eventFilter(self, obj, e):
|
229
|
+
if obj is self.parent() and self.parent().isEnabled():
|
230
|
+
if e.type() in [QEvent.Type.Enter]:
|
231
|
+
self.isHover = True
|
232
|
+
|
233
|
+
if self.state() != QPropertyAnimation.State.Running:
|
234
|
+
self.parent().setGraphicsEffect(self._createShadowEffect())
|
235
|
+
|
236
|
+
self.setEndValue(self.hoverColor)
|
237
|
+
self.start()
|
238
|
+
elif e.type() in [QEvent.Type.Leave, QEvent.Type.MouseButtonPress]:
|
239
|
+
self.isHover = False
|
240
|
+
if self.parent().graphicsEffect():
|
241
|
+
self.finished.connect(self._onAniFinished)
|
242
|
+
self.setEndValue(self.normalColor)
|
243
|
+
self.start()
|
244
|
+
|
245
|
+
return super().eventFilter(obj, e)
|
246
|
+
|
247
|
+
def _onAniFinished(self):
|
248
|
+
self.finished.disconnect()
|
249
|
+
self.shadowEffect = None
|
250
|
+
self.parent().setGraphicsEffect(None)
|
251
|
+
|
252
|
+
|
253
|
+
class FluentAnimationSpeed(Enum):
|
254
|
+
""" Fluent animation speed """
|
255
|
+
FAST = 0
|
256
|
+
MEDIUM = 1
|
257
|
+
SLOW = 2
|
258
|
+
|
259
|
+
|
260
|
+
class FluentAnimationType(Enum):
|
261
|
+
""" Fluent animation type """
|
262
|
+
FAST_INVOKE = 0
|
263
|
+
STRONG_INVOKE = 1
|
264
|
+
FAST_DISMISS = 2
|
265
|
+
SOFT_DISMISS = 3
|
266
|
+
POINT_TO_POINT = 4
|
267
|
+
FADE_IN_OUT = 5
|
268
|
+
|
269
|
+
|
270
|
+
class FluentAnimationProperty(Enum):
|
271
|
+
""" Fluent animation property """
|
272
|
+
POSITION = "position"
|
273
|
+
SCALE = "scale"
|
274
|
+
ANGLE = "angle"
|
275
|
+
OPACITY = "opacity"
|
276
|
+
|
277
|
+
|
278
|
+
|
279
|
+
class FluentAnimationProperObject(QObject):
|
280
|
+
""" Fluent animation property object """
|
281
|
+
|
282
|
+
objects = {}
|
283
|
+
|
284
|
+
def __init__(self, parent=None):
|
285
|
+
super().__init__(parent=parent)
|
286
|
+
|
287
|
+
def getValue(self):
|
288
|
+
return 0
|
289
|
+
|
290
|
+
def setValue(self):
|
291
|
+
pass
|
292
|
+
|
293
|
+
@classmethod
|
294
|
+
def register(cls, name):
|
295
|
+
""" register menu animation manager
|
296
|
+
|
297
|
+
Parameters
|
298
|
+
----------
|
299
|
+
name: Any
|
300
|
+
the name of manager, it should be unique
|
301
|
+
"""
|
302
|
+
def wrapper(Manager):
|
303
|
+
if name not in cls.objects:
|
304
|
+
cls.objects[name] = Manager
|
305
|
+
|
306
|
+
return Manager
|
307
|
+
|
308
|
+
return wrapper
|
309
|
+
|
310
|
+
@classmethod
|
311
|
+
def create(cls, propertyType: FluentAnimationProperty, parent=None):
|
312
|
+
if propertyType not in cls.objects:
|
313
|
+
raise ValueError(f"`{propertyType}` has not been registered")
|
314
|
+
|
315
|
+
return cls.objects[propertyType](parent)
|
316
|
+
|
317
|
+
|
318
|
+
@FluentAnimationProperObject.register(FluentAnimationProperty.POSITION)
|
319
|
+
class PositionObject(FluentAnimationProperObject):
|
320
|
+
""" Position object """
|
321
|
+
|
322
|
+
def __init__(self, parent=None):
|
323
|
+
super().__init__(parent)
|
324
|
+
self._position = QPoint()
|
325
|
+
|
326
|
+
def getValue(self):
|
327
|
+
return self._position
|
328
|
+
|
329
|
+
def setValue(self, pos: QPoint):
|
330
|
+
self._position = pos
|
331
|
+
self.parent().update()
|
332
|
+
|
333
|
+
position = Property(QPoint, getValue, setValue)
|
334
|
+
|
335
|
+
|
336
|
+
@FluentAnimationProperObject.register(FluentAnimationProperty.SCALE)
|
337
|
+
class ScaleObject(FluentAnimationProperObject):
|
338
|
+
""" Scale object """
|
339
|
+
|
340
|
+
def __init__(self, parent=None):
|
341
|
+
super().__init__(parent)
|
342
|
+
self._scale = 1
|
343
|
+
|
344
|
+
def getValue(self):
|
345
|
+
return self._scale
|
346
|
+
|
347
|
+
def setValue(self, scale: float):
|
348
|
+
self._scale = scale
|
349
|
+
self.parent().update()
|
350
|
+
|
351
|
+
scale = Property(float, getValue, setValue)
|
352
|
+
|
353
|
+
|
354
|
+
@FluentAnimationProperObject.register(FluentAnimationProperty.ANGLE)
|
355
|
+
class AngleObject(FluentAnimationProperObject):
|
356
|
+
""" Angle object """
|
357
|
+
|
358
|
+
def __init__(self, parent=None):
|
359
|
+
super().__init__(parent)
|
360
|
+
self._angle = 0
|
361
|
+
|
362
|
+
def getValue(self):
|
363
|
+
return self._angle
|
364
|
+
|
365
|
+
def setValue(self, angle: float):
|
366
|
+
self._angle = angle
|
367
|
+
self.parent().update()
|
368
|
+
|
369
|
+
angle = Property(float, getValue, setValue)
|
370
|
+
|
371
|
+
|
372
|
+
@FluentAnimationProperObject.register(FluentAnimationProperty.OPACITY)
|
373
|
+
class OpacityObject(FluentAnimationProperObject):
|
374
|
+
""" Opacity object """
|
375
|
+
|
376
|
+
def __init__(self, parent=None):
|
377
|
+
super().__init__(parent)
|
378
|
+
self._opacity = 0
|
379
|
+
|
380
|
+
def getValue(self):
|
381
|
+
return self._opacity
|
382
|
+
|
383
|
+
def setValue(self, opacity: float):
|
384
|
+
self._opacity = opacity
|
385
|
+
self.parent().update()
|
386
|
+
|
387
|
+
opacity = Property(float, getValue, setValue)
|
388
|
+
|
389
|
+
|
390
|
+
class FluentAnimation(QPropertyAnimation):
|
391
|
+
""" Fluent animation base """
|
392
|
+
|
393
|
+
animations = {}
|
394
|
+
|
395
|
+
def __init__(self, parent=None):
|
396
|
+
super().__init__(parent=parent)
|
397
|
+
self.setSpeed(FluentAnimationSpeed.FAST)
|
398
|
+
self.setEasingCurve(self.curve())
|
399
|
+
|
400
|
+
@classmethod
|
401
|
+
def createBezierCurve(cls, x1, y1, x2, y2):
|
402
|
+
curve = QEasingCurve(QEasingCurve.BezierSpline)
|
403
|
+
curve.addCubicBezierSegment(QPointF(x1, y1), QPointF(x2, y2), QPointF(1, 1))
|
404
|
+
return curve
|
405
|
+
|
406
|
+
@classmethod
|
407
|
+
def curve(cls):
|
408
|
+
return cls.createBezierCurve(0, 0, 1, 1)
|
409
|
+
|
410
|
+
def setSpeed(self, speed: FluentAnimationSpeed):
|
411
|
+
""" set the speed of animation """
|
412
|
+
self.setDuration(self.speedToDuration(speed))
|
413
|
+
|
414
|
+
def speedToDuration(self, speed: FluentAnimationSpeed):
|
415
|
+
return 100
|
416
|
+
|
417
|
+
def startAnimation(self, endValue, startValue=None):
|
418
|
+
self.stop()
|
419
|
+
|
420
|
+
if startValue is None:
|
421
|
+
self.setStartValue(self.value())
|
422
|
+
else:
|
423
|
+
self.setStartValue(startValue)
|
424
|
+
|
425
|
+
self.setEndValue(endValue)
|
426
|
+
self.start()
|
427
|
+
|
428
|
+
def value(self):
|
429
|
+
return self.targetObject().getValue()
|
430
|
+
|
431
|
+
def setValue(self, value):
|
432
|
+
self.targetObject().setValue(value)
|
433
|
+
|
434
|
+
@classmethod
|
435
|
+
def register(cls, name):
|
436
|
+
""" register menu animation manager
|
437
|
+
|
438
|
+
Parameters
|
439
|
+
----------
|
440
|
+
name: Any
|
441
|
+
the name of manager, it should be unique
|
442
|
+
"""
|
443
|
+
def wrapper(Manager):
|
444
|
+
if name not in cls.animations:
|
445
|
+
cls.animations[name] = Manager
|
446
|
+
|
447
|
+
return Manager
|
448
|
+
|
449
|
+
return wrapper
|
450
|
+
|
451
|
+
@classmethod
|
452
|
+
def create(cls, aniType: FluentAnimationType, propertyType: FluentAnimationProperty,
|
453
|
+
speed=FluentAnimationSpeed.FAST, value=None, parent=None) -> "FluentAnimation":
|
454
|
+
if aniType not in cls.animations:
|
455
|
+
raise ValueError(f"`{aniType}` has not been registered.")
|
456
|
+
|
457
|
+
obj = FluentAnimationProperObject.create(propertyType, parent)
|
458
|
+
ani = cls.animations[aniType](parent)
|
459
|
+
|
460
|
+
ani.setSpeed(speed)
|
461
|
+
ani.setTargetObject(obj)
|
462
|
+
ani.setPropertyName(propertyType.value.encode())
|
463
|
+
|
464
|
+
if value is not None:
|
465
|
+
ani.setValue(value)
|
466
|
+
|
467
|
+
return ani
|
468
|
+
|
469
|
+
|
470
|
+
@FluentAnimation.register(FluentAnimationType.FAST_INVOKE)
|
471
|
+
class FastInvokeAnimation(FluentAnimation):
|
472
|
+
""" Fast invoke animation """
|
473
|
+
|
474
|
+
@classmethod
|
475
|
+
def curve(cls):
|
476
|
+
return cls.createBezierCurve(0, 0, 0, 1)
|
477
|
+
|
478
|
+
def speedToDuration(self, speed: FluentAnimationSpeed):
|
479
|
+
if speed == FluentAnimationSpeed.FAST:
|
480
|
+
return 187
|
481
|
+
if speed == FluentAnimationSpeed.MEDIUM:
|
482
|
+
return 333
|
483
|
+
|
484
|
+
return 500
|
485
|
+
|
486
|
+
|
487
|
+
@FluentAnimation.register(FluentAnimationType.STRONG_INVOKE)
|
488
|
+
class StrongInvokeAnimation(FluentAnimation):
|
489
|
+
""" Strong invoke animation """
|
490
|
+
|
491
|
+
@classmethod
|
492
|
+
def curve(cls):
|
493
|
+
return cls.createBezierCurve(0.13, 1.62, 0, 0.92)
|
494
|
+
|
495
|
+
def speedToDuration(self, speed: FluentAnimationSpeed):
|
496
|
+
return 667
|
497
|
+
|
498
|
+
|
499
|
+
@FluentAnimation.register(FluentAnimationType.FAST_DISMISS)
|
500
|
+
class FastDismissAnimation(FastInvokeAnimation):
|
501
|
+
""" Fast dismiss animation """
|
502
|
+
|
503
|
+
|
504
|
+
@FluentAnimation.register(FluentAnimationType.SOFT_DISMISS)
|
505
|
+
class SoftDismissAnimation(FluentAnimation):
|
506
|
+
""" Soft dismiss animation """
|
507
|
+
|
508
|
+
@classmethod
|
509
|
+
def curve(cls):
|
510
|
+
return cls.createBezierCurve(1, 0, 1, 1)
|
511
|
+
|
512
|
+
def speedToDuration(self, speed: FluentAnimationSpeed):
|
513
|
+
return 167
|
514
|
+
|
515
|
+
|
516
|
+
@FluentAnimation.register(FluentAnimationType.POINT_TO_POINT)
|
517
|
+
class PointToPointAnimation(FastDismissAnimation):
|
518
|
+
""" Point to point animation """
|
519
|
+
|
520
|
+
@classmethod
|
521
|
+
def curve(cls):
|
522
|
+
return cls.createBezierCurve(0.55, 0.55, 0, 1)
|
523
|
+
|
524
|
+
|
525
|
+
@FluentAnimation.register(FluentAnimationType.FADE_IN_OUT)
|
526
|
+
class FadeInOutAnimation(FluentAnimation):
|
527
|
+
""" Fade in/out animation """
|
528
|
+
|
529
|
+
def speedToDuration(self, speed: FluentAnimationSpeed):
|
530
|
+
return 83
|
@@ -0,0 +1,164 @@
|
|
1
|
+
from enum import Enum, auto
|
2
|
+
from functools import lru_cache
|
3
|
+
from re import sub
|
4
|
+
from typing import List, Optional, Tuple
|
5
|
+
from unicodedata import east_asian_width
|
6
|
+
|
7
|
+
|
8
|
+
class CharType(Enum):
|
9
|
+
SPACE = auto()
|
10
|
+
ASIAN = auto()
|
11
|
+
LATIN = auto()
|
12
|
+
|
13
|
+
|
14
|
+
class TextWrap:
|
15
|
+
"""Text wrap"""
|
16
|
+
|
17
|
+
EAST_ASAIN_WIDTH_TABLE = {
|
18
|
+
"F": 2,
|
19
|
+
"H": 1,
|
20
|
+
"W": 2,
|
21
|
+
"A": 1,
|
22
|
+
"N": 1,
|
23
|
+
"Na": 1,
|
24
|
+
}
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
@lru_cache(maxsize=128)
|
28
|
+
def get_width(cls, char: str) -> int:
|
29
|
+
"""Returns the width of the char"""
|
30
|
+
return cls.EAST_ASAIN_WIDTH_TABLE.get(east_asian_width(char), 1)
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
@lru_cache(maxsize=32)
|
34
|
+
def get_text_width(cls, text: str) -> int:
|
35
|
+
"""Returns the width of the text"""
|
36
|
+
return sum(cls.get_width(char) for char in text)
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
@lru_cache(maxsize=128)
|
40
|
+
def get_char_type(cls, char: str) -> CharType:
|
41
|
+
"""Returns the type of the char"""
|
42
|
+
|
43
|
+
if char.isspace():
|
44
|
+
return CharType.SPACE
|
45
|
+
|
46
|
+
if cls.get_width(char) == 1:
|
47
|
+
return CharType.LATIN
|
48
|
+
|
49
|
+
return CharType.ASIAN
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def process_text_whitespace(cls, text: str) -> str:
|
53
|
+
"""Process whitespace and leading and trailing spaces in strings"""
|
54
|
+
return sub(pattern=r"\s+", repl=" ", string=text).strip()
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
@lru_cache(maxsize=32)
|
58
|
+
def split_long_token(cls, token: str, width: int) -> List[str]:
|
59
|
+
"""Split long token into smaller chunks."""
|
60
|
+
return [token[i : i + width] for i in range(0, len(token), width)]
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def tokenizer(cls, text: str):
|
64
|
+
"""tokenize line"""
|
65
|
+
|
66
|
+
buffer = ""
|
67
|
+
last_char_type: Optional[CharType] = None
|
68
|
+
|
69
|
+
for char in text:
|
70
|
+
char_type = cls.get_char_type(char)
|
71
|
+
|
72
|
+
if buffer and (char_type != last_char_type or char_type != CharType.LATIN):
|
73
|
+
yield buffer
|
74
|
+
buffer = ""
|
75
|
+
|
76
|
+
buffer += char
|
77
|
+
last_char_type = char_type
|
78
|
+
|
79
|
+
yield buffer
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def wrap(cls, text: str, width: int, once: bool = True) -> Tuple[str, bool]:
|
83
|
+
"""Wrap according to string length
|
84
|
+
|
85
|
+
Parameters
|
86
|
+
----------
|
87
|
+
text: str
|
88
|
+
the text to be wrapped
|
89
|
+
|
90
|
+
width: int
|
91
|
+
the maximum length of a single line, the length of Chinese characters is 2
|
92
|
+
|
93
|
+
once: bool
|
94
|
+
whether to wrap only once
|
95
|
+
|
96
|
+
Returns
|
97
|
+
-------
|
98
|
+
wrap_text: str
|
99
|
+
text after auto word wrap process
|
100
|
+
|
101
|
+
is_wrapped: bool
|
102
|
+
whether a line break occurs in the text
|
103
|
+
"""
|
104
|
+
|
105
|
+
width = int(width)
|
106
|
+
lines = text.splitlines()
|
107
|
+
is_wrapped = False
|
108
|
+
wrapped_lines = []
|
109
|
+
|
110
|
+
for line in lines:
|
111
|
+
line = cls.process_text_whitespace(line)
|
112
|
+
|
113
|
+
if cls.get_text_width(line) > width:
|
114
|
+
wrapped_line, is_wrapped = cls._wrap_line(line, width, once)
|
115
|
+
wrapped_lines.append(wrapped_line)
|
116
|
+
|
117
|
+
if once:
|
118
|
+
wrapped_lines.append(text[len(wrapped_line) :].rstrip())
|
119
|
+
return "".join(wrapped_lines), is_wrapped
|
120
|
+
|
121
|
+
else:
|
122
|
+
wrapped_lines.append(line)
|
123
|
+
|
124
|
+
return "\n".join(wrapped_lines), is_wrapped
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def _wrap_line(cls, text: str, width: int, once: bool = True) -> Tuple[str, bool]:
|
128
|
+
line_buffer = ""
|
129
|
+
wrapped_lines = []
|
130
|
+
current_width = 0
|
131
|
+
|
132
|
+
for token in cls.tokenizer(text):
|
133
|
+
token_width = cls.get_text_width(token)
|
134
|
+
|
135
|
+
if token == " " and current_width == 0:
|
136
|
+
continue
|
137
|
+
|
138
|
+
if current_width + token_width <= width:
|
139
|
+
line_buffer += token
|
140
|
+
current_width += token_width
|
141
|
+
|
142
|
+
if current_width == width:
|
143
|
+
wrapped_lines.append(line_buffer.rstrip())
|
144
|
+
line_buffer = ""
|
145
|
+
current_width = 0
|
146
|
+
else:
|
147
|
+
if current_width != 0:
|
148
|
+
wrapped_lines.append(line_buffer.rstrip())
|
149
|
+
|
150
|
+
chunks = cls.split_long_token(token, width)
|
151
|
+
|
152
|
+
for chunk in chunks[:-1]:
|
153
|
+
wrapped_lines.append(chunk.rstrip())
|
154
|
+
|
155
|
+
line_buffer = chunks[-1]
|
156
|
+
current_width = cls.get_text_width(chunks[-1])
|
157
|
+
|
158
|
+
if current_width != 0:
|
159
|
+
wrapped_lines.append(line_buffer.rstrip())
|
160
|
+
|
161
|
+
if once:
|
162
|
+
return "\n".join([wrapped_lines[0], " ".join(wrapped_lines[1:])]), True
|
163
|
+
|
164
|
+
return "\n".join(wrapped_lines), True
|