Skip to content

Commit b8fc7ee

Browse files
authored
'uix': new widget MDLoadingIndicator (#1825)
1 parent 56c2f03 commit b8fc7ee

File tree

7 files changed

+335
-0
lines changed

7 files changed

+335
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ temp
6868
/kivymd/tools/release/*.zip
6969
/kivymd/tools/release/temp
7070
.idea/
71+
docs/sources

examples/loadingindicator.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from kivy.clock import Clock
2+
from kivy.lang import Builder
3+
4+
from examples.common_app import CommonApp
5+
from kivymd.app import MDApp
6+
7+
KV = """
8+
MDScreen:
9+
md_bg_color:app.theme_cls.surfaceColor
10+
BoxLayout:
11+
orientation:"vertical"
12+
BoxLayout:
13+
size_hint_y:None
14+
height:dp(50)
15+
MDIconButton:
16+
size_hint:None, None
17+
size:[dp(50)] * 2
18+
on_release: app.open_menu(self)
19+
icon: "dots-vertical"
20+
Widget:
21+
22+
Widget:
23+
AnchorLayout:
24+
MDLoadingIndicator:
25+
id:indicator
26+
shape_size: dp(100)
27+
Widget:
28+
"""
29+
30+
31+
class ExampleApp(MDApp, CommonApp):
32+
33+
def build(self):
34+
return Builder.load_string(KV)
35+
36+
def on_start(self):
37+
self.root.ids.indicator.start()
38+
print(self.root.ids.indicator.get_shape_names())
39+
# stop animation
40+
# Clock.schedule_once(self.root.ids.indicator.stop, 5)
41+
42+
ExampleApp().run()

kivymd/factory_registers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
register("MDLabel", module="kivymd.uix.label")
7474
register("MDIcon", module="kivymd.uix.label")
7575
register("MDBadge", module="kivymd.uix.badge")
76+
register("MDLoadingIndicator", module="kivymd.uix.loadingindicator")
7677
register("MDList", module="kivymd.uix.list")
7778
register("MDListItem", module="kivymd.uix.list")
7879
register("MDListItemHeadlineText", module="kivymd.uix.list")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .loadingindicator import MDLoadingIndicator
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<MDLoadingIndicator>:
2+
active_indicator_color: app.theme_cls.primaryColor
3+
container_color: app.theme_cls.secondaryContainerColor
4+
MaterialShape:
5+
id: material_shape
6+
shape: root.shape
7+
fill_color: root.active_indicator_color
8+
size_hint: None, None
9+
size: [root.shape_size] * 2
10+
padding: 0.2085 * root.shape_size
11+
damping: 0.45
12+
canvas.before:
13+
Color:
14+
rgba: root.container_color
15+
Ellipse:
16+
size: self.size
17+
pos: self.pos
18+
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
"""
2+
Components/LoadingIndicator
3+
===========================
4+
5+
.. seealso::
6+
7+
`Material Design spec, Loading indicator <https://m3.material.io/components/loading-indicator/overview>`_
8+
`Shapes preview <https://github.com/T-Dynamos/materialshapes-python/tree/c48e367bd9e7ef3db95ea68bd2e3f8fd1fc976cb?tab=readme-ov-file#preview>`_
9+
10+
.. rubric:: Loading indicators display the status of a process using continuous or sequential shape animations.
11+
12+
- Used to represent indeterminate operations (no fixed end).
13+
- Each shape is based on Material 3 geometry guidelines.
14+
- Animations can cycle between multiple predefined shapes.
15+
16+
17+
Usage
18+
-----
19+
20+
.. tabs::
21+
22+
.. tab:: Declarative Python style with KV
23+
24+
.. code-block:: python
25+
26+
from kivy.clock import Clock
27+
from kivy.lang import Builder
28+
from kivymd.app import MDApp
29+
30+
KV = '''
31+
MDScreen:
32+
md_bg_color: app.theme_cls.surfaceColor
33+
MDLoadingIndicator:
34+
id: indicator
35+
shape_size: dp(100)
36+
'''
37+
38+
39+
class ExampleApp(MDApp):
40+
41+
def build(self):
42+
return Builder.load_string(KV)
43+
44+
def on_start(self):
45+
# start the morphing animation
46+
self.root.ids.indicator.start()
47+
48+
# print available shape names
49+
print(self.root.ids.indicator.get_shape_names())
50+
51+
# optionally stop animation after 5 seconds
52+
# Clock.schedule_once(self.root.ids.indicator.stop, 5)
53+
54+
55+
ExampleApp().run()
56+
57+
.. tab:: Declarative Python style
58+
59+
.. code-block:: python
60+
61+
from kivymd.app import MDApp
62+
from kivymd.uix.screen import MDScreen
63+
from kivymd.uix.loadingindicator import MDLoadingIndicator
64+
65+
class ExampleApp(MDApp):
66+
def build(self):
67+
return MDScreen(
68+
MDLoadingIndicator(
69+
id="indicator",
70+
shape_size="100dp",
71+
),
72+
md_bg_color=self.theme_cls.surfaceColor,
73+
)
74+
75+
def on_start(self):
76+
# start the morphing animation
77+
self.root.children[0].start()
78+
79+
# print available shape names
80+
print(self.root.children[0].get_shape_names())
81+
82+
# optionally stop animation after 5 seconds
83+
# Clock.schedule_once(self.root.ids.indicator.stop, 5)
84+
85+
ExampleApp().run()
86+
87+
88+
.. image:: https://github.com/user-attachments/assets/1a24f741-a710-4271-993b-5a335a284787
89+
:align: center
90+
"""
91+
92+
93+
import os
94+
95+
from kivy.animation import Animation
96+
from kivy.clock import Clock
97+
from kivy.lang import Builder
98+
from kivy.metrics import dp
99+
from kivy.properties import (
100+
ColorProperty,
101+
ListProperty,
102+
NumericProperty,
103+
StringProperty,
104+
)
105+
from kivy.uix.anchorlayout import AnchorLayout
106+
107+
from materialshapes.kivy_widget import MaterialShape
108+
from kivymd import uix_path
109+
from kivymd.uix.behaviors import DeclarativeBehavior
110+
from kivymd.uix.behaviors import RotateBehavior
111+
112+
with open(
113+
os.path.join(uix_path, "loadingindicator", "loadingindicator.kv"),
114+
encoding="utf-8",
115+
) as kv_file:
116+
Builder.load_string(kv_file.read())
117+
118+
119+
class MDLoadingIndicator(DeclarativeBehavior, AnchorLayout, RotateBehavior):
120+
"""
121+
Implementation of a morphing and rotating loading indicator.
122+
123+
For more information, see the
124+
:class:`~kivy.uix.anchorlayout.AnchorLayout` and
125+
:class:`~kivymd.uix.behaviors.RotateBehavior`
126+
classes documentation.
127+
"""
128+
129+
shape = StringProperty("cookie12Sided")
130+
"""
131+
Current shape name displayed by the loading indicator.
132+
133+
The shape corresponds to one of the predefined shapes available
134+
in the :class:`~materialshapes.kivy_widget.MaterialShape` library.
135+
136+
You can view all available shape names using
137+
:meth:`get_shape_names`.
138+
139+
:attr:`shape` is a :class:`~kivy.properties.StringProperty`
140+
and defaults to `'cookie12Sided'`.
141+
"""
142+
143+
shape_sequence = ListProperty(
144+
[
145+
"cookie12Sided",
146+
"pentagon",
147+
"pill",
148+
"verySunny",
149+
"cookie4Sided",
150+
"oval",
151+
"flower",
152+
"softBoom",
153+
]
154+
)
155+
"""
156+
Sequence of shape names through which the indicator cycles.
157+
158+
Each shape in the list is morphed into the next one over time,
159+
looping continuously while the indicator is active.
160+
161+
:attr:`shape_sequence` is a :class:`~kivy.properties.ListProperty`
162+
and defaults to::
163+
164+
[
165+
"cookie12Sided",
166+
"pentagon",
167+
"pill",
168+
"verySunny",
169+
"cookie4Sided",
170+
"oval",
171+
"flower",
172+
"softBoom",
173+
]
174+
"""
175+
176+
shape_size = NumericProperty(dp(48))
177+
"""
178+
Size of the loading indicator.
179+
180+
:attr:`shape_size` is a :class:`~kivy.properties.NumericProperty`
181+
and defaults to `dp(48)`.
182+
"""
183+
184+
duration = NumericProperty(0.65)
185+
"""
186+
Duration of one morph-and-rotate cycle in seconds.
187+
188+
This value controls the overall speed of the loading indicator.
189+
190+
:attr:`duration` is a :class:`~kivy.properties.NumericProperty`
191+
and defaults to `0.65`.
192+
"""
193+
194+
active_indicator_color = ColorProperty([0,0,0,0])
195+
"""
196+
Color of the active (foreground) loading shape.
197+
198+
:attr:`active_indicator_color` is a :class:`~kivy.properties.ColorProperty`
199+
and defaults to `None`.
200+
"""
201+
202+
container_color = ColorProperty([0,0,0,0])
203+
"""
204+
Background container color of the indicator.
205+
206+
:attr:`container_color` is a :class:`~kivy.properties.ColorProperty`
207+
and defaults to `None`.
208+
"""
209+
210+
shape_index = NumericProperty(0)
211+
"""
212+
Current index in the :attr:`shape_sequence`.
213+
214+
:attr:`shape_index` is a :class:`~kivy.properties.NumericProperty`
215+
and defaults to `0`.
216+
"""
217+
218+
_intrvl = None
219+
220+
def start(self, *args):
221+
"""
222+
Start the loading animation.
223+
224+
Initiates the shape morphing and rotation cycle.
225+
The indicator continuously transitions between the shapes defined in
226+
:attr:`shape_sequence` while rotating at regular intervals.
227+
228+
If already active, the animation sequence is reset.
229+
"""
230+
self._run_cycle()
231+
self.stop()
232+
self._intrvl = Clock.schedule_interval(self._run_cycle, self.duration)
233+
234+
def stop(self, *args):
235+
"""
236+
Stop the loading animation.
237+
"""
238+
if self._intrvl is not None:
239+
self._intrvl.cancel()
240+
self._intrvl = None
241+
242+
def _run_cycle(self, *args):
243+
"""
244+
Internal method for executing one morph-and-rotate cycle.
245+
246+
Each cycle performs two actions:
247+
248+
1. Morphs the current shape into the next in :attr:`shape_sequence`.
249+
2. Rotates the entire indicator by 90 degrees using an 'out_cubic'
250+
easing function.
251+
252+
This method is called automatically at regular intervals
253+
determined by :attr:`duration`.
254+
"""
255+
shape = self.shape_sequence[self.shape_index % len(self.shape_sequence)]
256+
self.ids.material_shape.morph_to(shape, d=self.duration * 0.9)
257+
258+
Animation(
259+
rotate_value_angle=(self.shape_index + 1) * 90,
260+
duration=self.duration * 0.8,
261+
t="out_cubic",
262+
).start(self)
263+
264+
self.shape_index += 1
265+
266+
267+
def get_shape_names(self):
268+
"""
269+
Return all available material shape names.
270+
"""
271+
return list(self.ids.material_shape.material_shapes.all.keys())

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def glob_paths(pattern):
132132
"kivy>=2.3.0",
133133
"pillow",
134134
"materialyoucolor>=2.0.7",
135+
"materialshapes>=0.3",
135136
"asynckivy>=0.6,<0.7",
136137
],
137138
setup_requires=[],

0 commit comments

Comments
 (0)