globalforest/kivymd/uix/spinner.py

261 lines
7.2 KiB
Python
Raw Normal View History

2020-10-14 00:19:43 -04:00
"""
Components/Spinner
==================
.. rubric:: Circular progress indicator in Google's Material Design.
Usage
-----
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
Screen:
MDSpinner:
size_hint: None, None
size: dp(46), dp(46)
pos_hint: {'center_x': .5, 'center_y': .5}
active: True if check.active else False
MDCheckbox:
id: check
size_hint: None, None
size: dp(48), dp(48)
pos_hint: {'center_x': .5, 'center_y': .4}
active: True
'''
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner.gif
:align: center
Spinner palette
---------------
.. code-block:: kv
MDSpinner:
# The number of color values can be any.
palette:
[0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1], \
[0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1], \
[0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1], \
[0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1],
.. code-block:: python
MDSpinner(
size_hint=(None, None),
size=(dp(46), dp(46)),
pos_hint={'center_x': .5, 'center_y': .5},
active=True,
palette=[
[0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1],
[0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1],
[0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1],
[0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1],
]
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner-palette.gif
:align: center
"""
__all__ = ("MDSpinner",)
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import BooleanProperty, ListProperty, NumericProperty
from kivy.uix.widget import Widget
from kivymd.theming import ThemableBehavior
Builder.load_string(
"""
<MDSpinner>
canvas.before:
PushMatrix
Rotate:
angle: self._rotation_angle
origin: self.center
canvas:
Color:
rgba: self.color
a: self._alpha
SmoothLine:
circle: self.center_x, self.center_y, self.width / 2,\
self._angle_start, self._angle_end
cap: 'square'
width: dp(2.25)
canvas.after:
PopMatrix
"""
)
class MDSpinner(ThemableBehavior, Widget):
""":class:`MDSpinner` is an implementation of the circular progress
indicator in `Google's Material Design`.
It can be used either as an indeterminate indicator that loops while
the user waits for something to happen, or as a determinate indicator.
Set :attr:`determinate` to **True** to activate determinate mode, and
:attr:`determinate_time` to set the duration of the animation.
"""
determinate = BooleanProperty(False)
"""
Determinate value.
:attr:`determinate` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
determinate_time = NumericProperty(2)
"""
Determinate time value.
:attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty`
and defaults to `2`.
"""
active = BooleanProperty(True)
"""Use :attr:`active` to start or stop the spinner.
:attr:`active` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
color = ListProperty([0, 0, 0, 0])
"""
Spinner color.
:attr:`color` is a :class:`~kivy.properties.ListProperty`
and defaults to ``self.theme_cls.primary_color``.
"""
palette = ListProperty()
"""
A set of colors. Changes with each completed spinner cycle.
:attr:`palette` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
_alpha = NumericProperty(0)
_rotation_angle = NumericProperty(360)
_angle_start = NumericProperty(0)
_angle_end = NumericProperty(0)
_palette = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.color = self.theme_cls.primary_color
self._alpha_anim_in = Animation(_alpha=1, duration=0.8, t="out_quad")
self._alpha_anim_out = Animation(_alpha=0, duration=0.3, t="out_quad")
self._alpha_anim_out.bind(on_complete=self._reset)
self.theme_cls.bind(primary_color=self._update_color)
if self.determinate:
self._start_determinate()
else:
self._start_loop()
def _update_color(self, *args):
self.color = self.theme_cls.primary_color
def _start_determinate(self, *args):
self._alpha_anim_in.start(self)
_rot_anim = Animation(
_rotation_angle=0,
duration=self.determinate_time * 0.7,
t="out_quad",
)
_rot_anim.start(self)
_angle_start_anim = Animation(
_angle_end=360, duration=self.determinate_time, t="in_out_quad"
)
_angle_start_anim.bind(
on_complete=lambda *x: self._alpha_anim_out.start(self)
)
_angle_start_anim.start(self)
def _start_loop(self, *args):
if self._alpha == 0:
_rot_anim = Animation(_rotation_angle=0, duration=2, t="linear")
_rot_anim.start(self)
self._alpha = 1
self._alpha_anim_in.start(self)
_angle_start_anim = Animation(
_angle_end=self._angle_end + 270, duration=0.6, t="in_out_cubic"
)
_angle_start_anim.bind(on_complete=self._anim_back)
_angle_start_anim.start(self)
def _anim_back(self, *args):
_angle_back_anim = Animation(
_angle_start=self._angle_end - 8, duration=0.6, t="in_out_cubic"
)
_angle_back_anim.bind(on_complete=self._start_loop)
_angle_back_anim.start(self)
def on__rotation_angle(self, *args):
if self._rotation_angle == 0:
self._rotation_angle = 360
if not self.determinate:
_rot_anim = Animation(_rotation_angle=0, duration=2)
_rot_anim.start(self)
elif self._rotation_angle == 360:
if self._palette:
try:
Animation(color=next(self._palette), duration=2).start(self)
except StopIteration:
self._palette = iter(self.palette)
Animation(color=next(self._palette), duration=2).start(self)
def _reset(self, *args):
Animation.cancel_all(
self,
"_angle_start",
"_rotation_angle",
"_angle_end",
"_alpha",
"color",
)
self._angle_start = 0
self._angle_end = 0
self._rotation_angle = 360
self._alpha = 0
self.active = False
def on_palette(self, instance, value):
self._palette = iter(value)
def on_active(self, *args):
if not self.active:
self._reset()
else:
if self.determinate:
self._start_determinate()
else:
self._start_loop()