Skip to content

Commit 8328160

Browse files
committed
new expressive MDExCircularProgressIndicator and MDExLinearProgressIndicator
1 parent d668d8b commit 8328160

File tree

10 files changed

+1118
-116
lines changed

10 files changed

+1118
-116
lines changed

examples/exprogressindicator.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
from kivy.animation import Animation
2+
from kivy.clock import Clock
3+
from kivy.lang import Builder
4+
from kivy.properties import BooleanProperty, StringProperty
5+
6+
from examples.common_app import CommonApp
7+
from kivymd.app import MDApp
8+
from kivymd.uix.boxlayout import MDBoxLayout
9+
10+
KV = """
11+
<SelectedItem>
12+
adaptive_size: True
13+
spacing: "12dp"
14+
15+
MDCheckbox:
16+
group: root.group
17+
on_active: root.dispatch("on_active", self.active)
18+
active: root.active
19+
20+
MDLabel:
21+
text: root.text
22+
theme_line_height: "Custom"
23+
adaptive_size: True
24+
pos_hint: {'center_y': .5}
25+
line_height: 1
26+
27+
28+
MDScreen:
29+
md_bg_color: self.theme_cls.backgroundColor
30+
31+
MDIconButton:
32+
on_release: app.open_menu(self)
33+
pos_hint: {"top": .98}
34+
x: dp(12)
35+
icon: "menu"
36+
37+
BoxLayout:
38+
spacing: "24dp"
39+
40+
41+
BoxLayout:
42+
spacing:dp(10)
43+
orientation:"vertical"
44+
padding:[dp(50), dp(80), 0, dp(20)]
45+
46+
BoxLayout:
47+
orientation:"vertical"
48+
MDLabel:
49+
text: "Progress (0-100)"
50+
adaptive_height: True
51+
MDSlider:
52+
min:0
53+
max:100
54+
step: 1
55+
value:50
56+
on_value:
57+
linear_indicator_horizontal.value = self.value
58+
linear_indicator_vertical.value = self.value
59+
circular_indicator.value = self.value
60+
MDSliderHandle:
61+
MDSliderValueLabel:
62+
63+
64+
BoxLayout:
65+
orientation:"vertical"
66+
MDLabel:
67+
text: "Amplitude (0 - 30) dp"
68+
adaptive_height: True
69+
MDSlider:
70+
min:dp(0)
71+
max:dp(30)
72+
step: 1
73+
value:dp(3)
74+
on_value:
75+
linear_indicator_horizontal.amplitude = self.value
76+
linear_indicator_vertical.amplitude = self.value
77+
circular_indicator.amplitude = self.value
78+
MDSliderHandle:
79+
80+
BoxLayout:
81+
orientation:"vertical"
82+
MDLabel:
83+
text: "Wavelenght (0 - 100) dp"
84+
adaptive_height: True
85+
MDSlider:
86+
min:dp(0)
87+
max:dp(100)
88+
step: 2
89+
value:dp(10)
90+
on_value:
91+
linear_indicator_horizontal.wave_length = self.value
92+
linear_indicator_vertical.wave_length = self.value
93+
circular_indicator.wave_length = self.value
94+
MDSliderHandle:
95+
96+
BoxLayout:
97+
orientation:"vertical"
98+
MDLabel:
99+
text: "Wave speed (-50 - 50) dp/s"
100+
adaptive_height: True
101+
MDSlider:
102+
min:dp(-50)
103+
max:dp(50)
104+
step: 2
105+
value:dp(20)
106+
on_value:
107+
linear_indicator_horizontal.wave_speed = self.value
108+
linear_indicator_vertical.wave_speed = self.value
109+
circular_indicator.wave_speed = self.value
110+
MDSliderHandle:
111+
112+
BoxLayout:
113+
orientation:"vertical"
114+
MDLabel:
115+
text: "Thickness (4 - 20) dp"
116+
adaptive_height: True
117+
118+
MDSlider:
119+
min:dp(4)
120+
max:dp(20)
121+
step: 1
122+
value:dp(4)
123+
on_value:
124+
linear_indicator_horizontal.thickness = self.value
125+
linear_indicator_vertical.thickness = self.value
126+
circular_indicator.thickness = self.value
127+
MDSliderHandle:
128+
MDSliderValueLabel:
129+
130+
BoxLayout:
131+
orientation:"vertical"
132+
MDLabel:
133+
text: "Circular indicator size (20 - 200)dp"
134+
adaptive_height: True
135+
136+
MDSlider:
137+
min:dp(20)
138+
max:dp(240)
139+
step: 2
140+
value:box.size[0]
141+
on_value:
142+
box.size = [self.value]*2
143+
MDSliderHandle:
144+
MDSliderValueLabel:
145+
146+
BoxLayout:
147+
spacing: "24dp"
148+
orientation:"vertical"
149+
padding:dp(20)
150+
151+
AnchorLayout:
152+
BoxLayout:
153+
id:box
154+
size_hint: None, None
155+
size: [dp(50)]*2
156+
157+
MDExCircularProgressIndicator:
158+
id: circular_indicator
159+
active: False
160+
161+
MDExLinearProgressIndicator:
162+
id: linear_indicator_horizontal
163+
164+
MDExLinearProgressIndicator:
165+
id: linear_indicator_vertical
166+
orientation: "vertical"
167+
size_hint_y: None
168+
height: self.width
169+
170+
BoxLayout:
171+
spacing: "12dp"
172+
size_hint_y:None
173+
height:dp(80)
174+
175+
Widget:
176+
MDLabel:
177+
text:"Determinate"
178+
adaptive_size:True
179+
Switch:
180+
on_active:
181+
linear_indicator_horizontal.determinate = self.active
182+
linear_indicator_vertical.determinate = self.active
183+
Widget:
184+
185+
MDLabel:
186+
id:fps
187+
padding:[dp(10), 0]
188+
halign:"center"
189+
text:"FPS:"
190+
adaptive_size:True
191+
"""
192+
193+
194+
class SelectedItem(MDBoxLayout):
195+
active = BooleanProperty(False)
196+
text = StringProperty()
197+
group = StringProperty("type")
198+
199+
def __init__(self, *args, **kwargs):
200+
super().__init__(*args, **kwargs)
201+
self.register_event_type("on_active")
202+
203+
def on_active(self, *args): ...
204+
205+
206+
class Example(MDApp, CommonApp):
207+
208+
def build(self):
209+
self.theme_cls.primary_palette = "Olive"
210+
self.theme_cls.theme_style = "Dark"
211+
return Builder.load_string(KV)
212+
213+
def on_start(self):
214+
Clock.schedule_interval(self.set_fps, 0.5)
215+
216+
def set_fps(self, dt):
217+
self.root.ids.fps.text = f"FPS: {Clock.get_rfps()}"
218+
219+
def disabled_widgets(self): ...
220+
221+
222+
Example().run()

examples/md_transitions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def on_start(self):
5353
"easing_accelerated",
5454
"easing_decelerated",
5555
"easing_standard",
56-
"in_out_cubic",
56+
"easing_emphasized",
5757
]: # Add more here for comparison
5858
print(transition)
5959
widget = AnimBox()

kivymd/animation.py

Lines changed: 16 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -94,120 +94,10 @@ def run_animation(self, dt):
9494
:align: center
9595
"""
9696

97-
import math
98-
import sys
99-
10097
import kivy.animation
10198

102-
float_epsilon = 8.3446500e-7
103-
104-
if sys.version_info < (3, 11):
105-
cbrt = lambda number: (abs(number) ** (1 / 3)) * (-1 if number < 0 else 1)
106-
else:
107-
cbrt = math.cbrt
108-
109-
110-
class CubicBezier:
111-
"""Ported from Android source code"""
112-
113-
p0 = 0
114-
p1 = 0
115-
p2 = 0
116-
p3 = 0
117-
118-
def __init__(self, *args):
119-
self.p0, self.p1, self.p2, self.p3 = args
120-
121-
def evaluate_cubic(self, p1, p2, t):
122-
a = 1.0 / 3.0 + (p1 - p2)
123-
b = p2 - 2.0 * p1
124-
c = p1
125-
return 3.0 * ((a * t + b) * t + c) * t
126-
127-
def clamp_range(self, r):
128-
if r < 0.0:
129-
if -float_epsilon <= r < 0.0:
130-
return 0.0
131-
else:
132-
return math.nan
133-
elif r > 1.0:
134-
if 1.0 <= r <= 1.0 + float_epsilon:
135-
return 1.0
136-
else:
137-
return math.nan
138-
else:
139-
return r
140-
141-
def close_to(self, x, y):
142-
return abs(x - y) < float_epsilon
143-
144-
def find_first_cubic_root(self, p0, p1, p2, p3):
145-
a = 3.0 * (p0 - 2.0 * p1 + p2)
146-
b = 3.0 * (p1 - p0)
147-
c = p0
148-
d = -p0 + 3.0 * (p1 - p2) + p3
149-
if self.close_to(d, 0.0):
150-
if self.close_to(a, 0.0):
151-
if self.close_to(b, 0.0):
152-
return math.nan
153-
return self.clamp_range(-c / b)
154-
else:
155-
q = math.sqrt(b * b - 4.0 * a * c)
156-
a2 = 2.0 * a
157-
root = self.clamp_range((q - b) / a2)
158-
if not math.isnan(root):
159-
return root
160-
return self.clamp_range((-b - q) / a2)
161-
a /= d
162-
b /= d
163-
c /= d
164-
o3 = (3.0 * b - a * a) / 9.0
165-
q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
166-
discriminant = q2 * q2 + o3 * o3 * o3
167-
a3 = a / 3.0
168-
169-
if discriminant < 0.0:
170-
mp33 = -(o3 * o3 * o3)
171-
r = math.sqrt(mp33)
172-
t = -q2 / r
173-
cos_phi = max(-1.0, min(t, 1.0))
174-
phi = math.acos(cos_phi)
175-
t1 = 2.0 * cbrt(r)
176-
root = self.clamp_range(t1 * math.cos(phi / 3.0) - a3)
177-
if not math.isnan(root):
178-
return root
179-
root = self.clamp_range(
180-
t1 * math.cos((phi + 2.0 * math.pi) / 3.0) - a3
181-
)
182-
if not math.isnan(root):
183-
return root
184-
return self.clamp_range(
185-
t1 * math.cos((phi + 4.0 * math.pi) / 3.0) - a3
186-
)
187-
188-
elif self.close_to(discriminant, 0.0):
189-
u1 = -cbrt(q2)
190-
root = self.clamp_range(2.0 * u1 - a3)
191-
if not math.isnan(root):
192-
return root
193-
return self.clamp_range(-u1 - a3)
194-
195-
sd = math.sqrt(discriminant)
196-
u1 = cbrt(-q2 + sd)
197-
v1 = cbrt(q2 + sd)
198-
return self.clamp_range(u1 - v1 - a3)
199-
200-
def t(self, value: float):
201-
return self.evaluate_cubic(
202-
self.p1,
203-
self.p3,
204-
self.find_first_cubic_root(
205-
-value,
206-
self.p0 - value,
207-
self.p2 - value,
208-
1.0 - value,
209-
),
210-
)
99+
from kivymd.utils.cubic_bezier import CubicBezier
100+
import time
211101

212102

213103
class MDAnimationTransition(kivy.animation.AnimationTransition):
@@ -218,10 +108,21 @@ class MDAnimationTransition(kivy.animation.AnimationTransition):
218108
easing_accelerated = CubicBezier(0.4, 0.0, 1.0, 1.0).t
219109
easing_linear = CubicBezier(0.0, 0.0, 1.0, 1.0).t
220110

111+
# equivalent to
112+
# path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)
113+
def easing_emphasized(t: float):
114+
if t < 0.4:
115+
# Normalize t: maps [0, 0.4] to [0, 1]
116+
t_norm = t / 0.4
117+
# First segment: (0.05, 0) to (0.1333, 0.06)
118+
return CubicBezier(0.05, 0, 0.133333, 0.06).t(t_norm) * 0.4
119+
else:
120+
# Normalize t: maps [0.4, 1.0] to [0, 1]
121+
t_norm = (t - 0.4) / 0.6
122+
# Second segment: (0.2083, 0.82) to (0.25, 1)
123+
# We start at 0.4 (the end of the last segment) and move toward 1.0
124+
return 0.4 + CubicBezier(0.208333, 0.82, 0.25, 1).t(t_norm) * 0.6
221125

222-
# TODO: add `easing_emphasized` here
223-
# it's defination is
224-
# path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)
225126

226127
# Monkey patch kivy's animation module
227128
kivy.animation.AnimationTransition = MDAnimationTransition

kivymd/factory_registers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@
106106
register("MDSlider", module="kivymd.uix.slider")
107107
register("MDCircularProgressIndicator", module="kivymd.uix.progressindicator")
108108
register("MDLinearProgressIndicator", module="kivymd.uix.progressindicator")
109+
register(
110+
"MDExCircularProgressIndicator", module="kivymd.uix.exprogressindicator"
111+
)
112+
register("MDExLinearProgressIndicator", module="kivymd.uix.exprogressindicator")
109113
register("MDTabsPrimary", module="kivymd.uix.tab")
110114
register("MDTabsSecondary", module="kivymd.uix.tab")
111115
register("MDTabsItem", module="kivymd.uix.tab")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .exprogressindicator import (
2+
MDExCircularProgressIndicator,
3+
MDExLinearProgressIndicator,
4+
)

0 commit comments

Comments
 (0)