-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcircularlayout.py
208 lines (150 loc) · 5.58 KB
/
circularlayout.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""
Components/CircularLayout
=========================
CircularLayout is a special layout that places widgets around a circle.
MDCircularLayout
----------------
.. rubric:: Usage
.. code-block::
from kivy.lang.builder import Builder
from kivy.uix.label import Label
from kivymd.app import MDApp
kv = '''
MDScreen:
MDCircularLayout:
id: container
pos_hint: {"center_x": .5, "center_y": .5}
row_spacing: min(self.size) * 0.1
'''
class Main(MDApp):
def build(self):
return Builder.load_string(kv)
def on_start(self):
for x in range(1, 49):
self.root.ids.container.add_widget(
Label(text=f"{x}", color=[0, 0, 0, 1])
)
Main().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout.png
:align: center
"""
__all__ = ("MDCircularLayout",)
from math import atan2, cos, degrees, radians, sin
from kivy.properties import BooleanProperty, NumericProperty
from kivymd.uix.floatlayout import MDFloatLayout
class MDCircularLayout(MDFloatLayout):
degree_spacing = NumericProperty(30)
"""
The space between children in degree.
:attr:`degree_spacing` is an :class:`~kivy.properties.NumericProperty`
and defaults to `30`.
"""
circular_radius = NumericProperty(None, allownone=True)
"""
Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified.
:attr:`circular_radius` is an :class:`~kivy.properties.NumericProperty`
and defaults to `None`.
"""
start_from = NumericProperty(60)
"""
The positon of first child in degree.
:attr:`start_from` is an :class:`~kivy.properties.NumericProperty`
and defaults to `60`.
"""
max_degree = NumericProperty(360)
"""
Maximum range in degree allowed for each row of widgets before jumping to the next row.
:attr:`max_degree` is an :class:`~kivy.properties.NumericProperty`
and defaults to `360`.
"""
circular_padding = NumericProperty("25dp")
"""
Padding between outer widgets and the edge of the biggest circle.
:attr:`circular_padding` is an :class:`~kivy.properties.NumericProperty`
and defaults to `25dp`.
"""
row_spacing = NumericProperty("50dp")
"""
Space between each row of widget.
:attr:`row_spacing` is an :class:`~kivy.properties.NumericProperty`
and defaults to `50dp`.
"""
clockwise = BooleanProperty(True)
"""
Direction of widgets in circular direction.
:attr:`clockwise` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(
row_spacing=self._update_layout,
)
def get_angle(self, pos: tuple) -> float:
"""Returns the angle of given pos."""
center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
(dx, dy) = (center[0] - pos[0], center[1] - pos[1])
angle = degrees(atan2(float(dy), float(dx)))
angle += 180
return angle
def remove_widget(self, widget, **kwargs):
super().remove_widget(widget, **kwargs)
self._update_layout()
def do_layout(self, *largs, **kwargs):
self._update_layout()
return super().do_layout(*largs, **kwargs)
def _max_per_row(self):
return int(self.max_degree / self.degree_spacing)
def _update_layout(self, *args):
for index, child in enumerate(reversed(self.children)):
pos = self._point_on_circle(
self._calculate_radius(index),
self._calculate_degree(index),
)
child.center = pos
def _calculate_radius(self, index):
"""Calculates the radius for given index."""
idx = int(index / self._max_per_row())
if not self.circular_radius:
init_radius = (
min([self.width / 2, self.height / 2]) - self.circular_padding
)
else:
init_radius = self.circular_radius
if idx != 0:
space = self.row_spacing * idx
init_radius -= space
return init_radius
def _calculate_degree(self, index):
"""Calculates the angle for given index."""
if self.clockwise:
degree = self.start_from - index * self.degree_spacing
else:
degree = self.start_from + index * self.degree_spacing
return degree
def _point_on_circle(self, radius, degree):
angle = radians(degree)
center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
x = center[0] + (radius * cos(angle))
y = center[1] + (radius * sin(angle))
return [x, y]
if __name__ == "__main__":
from kivy.lang.builder import Builder
from kivy.uix.label import Label
from kivymd.app import MDApp
kv = """
MDScreen:
MDCircularLayout:
id: container
pos_hint: {"center_x": .5, "center_y": .5}
row_spacing: min(self.size) * 0.1
"""
class Main(MDApp):
def build(self):
return Builder.load_string(kv)
def on_start(self):
for x in range(1, 49):
self.root.ids.container.add_widget(
Label(text=f"{x}", color=[0, 0, 0, 1])
)
Main().run()