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,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