"""
CircularLayout
==============

CircularLayout is a special layout that places widgets around a circle.

size_hint
---------

size_hint_x is used as an angle-quota hint (widget with higher
size_hint_x will be farther from each other, and vice versa), while
size_hint_y is used as a widget size hint (widgets with a higher size
hint will be bigger).size_hint_x cannot be None.

Widgets are all squares, unless you set size_hint_y to None (in that
case you'll be able to specify your own size), and their size is the
difference between the outer and the inner circle's radii. To make the
widgets bigger you can just decrease inner_radius_hint.
"""

from math import sin, cos, pi, radians

from kivy.uix.layout import Layout
from kivy.properties import (
    NumericProperty,
    ReferenceListProperty,
    OptionProperty,
    BoundedNumericProperty,
    VariableListProperty,
    AliasProperty,
)

__all__ = "CircularLayout"

try:
    xrange(1, 2)
except NameError:

    def xrange(first, second, third=None):
        if third:
            return range(first, second, third)
        else:
            return range(first, second)


class CircularLayout(Layout):
    """
    Circular layout class. See module documentation for more information.
    """

    padding = VariableListProperty([0, 0, 0, 0])
    """Padding between the layout box and it's children: [padding_left,
    padding_top, padding_right, padding_bottom].

    padding also accepts a two argument form [padding_horizontal,
    padding_vertical] and a one argument form [padding].

    .. version changed:: 1.7.0
        Replaced NumericProperty with VariableListProperty.

    :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and
    defaults to [0, 0, 0, 0].
    """

    start_angle = NumericProperty(0)
    """Angle (in degrees) at which the first widget will be placed.
    Start counting angles from the X axis, going counterclockwise.

    :attr:`start_angle` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0 (start from the right).
    """

    circle_quota = BoundedNumericProperty(360, min=0, max=360)
    """Size (in degrees) of the part of the circumference that will actually
    be used to place widgets.

    :attr:`circle_quota` is a :class:`~kivy.properties.BoundedNumericProperty`
    and defaults to 360 (all the circumference).
    """

    direction = OptionProperty("ccw", options=("cw", "ccw"))
    """Direction of widgets in the circle.

    :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'ccw'. Can be 'ccw' (counterclockwise) or 'cw' (clockwise).
    """

    outer_radius_hint = NumericProperty(1)
    """Sets the size of the outer circle. A number greater than 1 will make the
    widgets larger than the actual widget, a number smaller than 1 will leave
    a gap.

    :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 1.
    """

    inner_radius_hint = NumericProperty(0.6)
    """Sets the size of the inner circle. A number greater than
    :attr:`outer_radius_hint` will cause glitches. The closest it is to
    :attr:`outer_radius_hint`, the smallest will be the widget in the layout.

    :attr:`outer_radius_hint` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 1.
    """

    radius_hint = ReferenceListProperty(inner_radius_hint, outer_radius_hint)
    """Combined :attr:`outer_radius_hint` and :attr:`inner_radius_hint`
    in a list for convenience. See their documentation for more details.

    :attr:`radius_hint` is a :class:`~kivy.properties.ReferenceListProperty`.
    """

    def _get_delta_radii(self):
        radius = (
            min(
                self.width - self.padding[0] - self.padding[2],
                self.height - self.padding[1] - self.padding[3],
            )
            / 2.0
        )
        outer_r = radius * self.outer_radius_hint
        inner_r = radius * self.inner_radius_hint
        return outer_r - inner_r

    delta_radii = AliasProperty(
        _get_delta_radii, None, bind=("radius_hint", "padding", "size")
    )

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.bind(
            start_angle=self._trigger_layout,
            parent=self._trigger_layout,
            # padding=self._trigger_layout,
            children=self._trigger_layout,
            size=self._trigger_layout,
            radius_hint=self._trigger_layout,
            pos=self._trigger_layout,
        )

    def do_layout(self, *largs):
        # optimize layout by preventing looking at the same attribute in a loop
        len_children = len(self.children)
        if len_children == 0:
            return
        selfcx = self.center_x
        selfcy = self.center_y
        direction = self.direction
        cquota = radians(self.circle_quota)
        start_angle_r = radians(self.start_angle)
        padding_left = self.padding[0]
        padding_top = self.padding[1]
        padding_right = self.padding[2]
        padding_bottom = self.padding[3]
        padding_x = padding_left + padding_right
        padding_y = padding_top + padding_bottom

        radius = min(self.width - padding_x, self.height - padding_y) / 2.0
        outer_r = radius * self.outer_radius_hint
        inner_r = radius * self.inner_radius_hint
        middle_r = radius * sum(self.radius_hint) / 2.0
        delta_r = outer_r - inner_r

        stretch_weight_angle = 0.0
        for w in self.children:
            sha = w.size_hint_x
            if sha is None:
                raise ValueError(
                    "size_hint_x cannot be None in a CircularLayout"
                )
            else:
                stretch_weight_angle += sha

        sign = +1.0
        angle_offset = start_angle_r
        if direction == "cw":
            angle_offset = 2 * pi - start_angle_r
            sign = -1.0

        for c in reversed(self.children):
            sha = c.size_hint_x
            shs = c.size_hint_y

            angle_quota = cquota / stretch_weight_angle * sha
            angle = angle_offset + (sign * angle_quota / 2)
            angle_offset += sign * angle_quota

            # kived: looking it up, yes. x = cos(angle) * radius + centerx;
            # y = sin(angle) * radius + centery
            ccx = cos(angle) * middle_r + selfcx + padding_left - padding_right
            ccy = sin(angle) * middle_r + selfcy + padding_bottom - padding_top

            c.center_x = ccx
            c.center_y = ccy
            if shs:
                s = delta_r * shs
                c.width = s
                c.height = s


if __name__ == "__main__":
    from kivymd.app import MDApp
    from kivy.uix.button import Button

    class CircLayoutApp(MDApp):
        def build(self):
            cly = CircularLayout(
                direction="cw",
                start_angle=-75,
                inner_radius_hint=0.7,
                padding="20dp",
            )

            for i in xrange(1, 13):
                cly.add_widget(Button(text=str(i), font_size="30dp"))

            return cly

    CircLayoutApp().run()
