""" 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( """ 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()