"""
Components/Button
=================

.. seealso::

    `Material Design spec, Buttons <https://material.io/components/buttons>`_

    `Material Design spec, Buttons: floating action button <https://material.io/components/buttons-floating-action-button>`_

.. rubric:: Buttons allow users to take actions, and make choices,
    with a single tap.

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/buttons.png
    :align: center

`KivyMD` provides the following button classes for use:

- MDIconButton_
- MDFloatingActionButton_
- MDFlatButton_
- MDRaisedButton_
- MDRectangleFlatButton_
- MDRectangleFlatIconButton_
- MDRoundFlatButton_
- MDRoundFlatIconButton_
- MDFillRoundFlatButton_
- MDFillRoundFlatIconButton_
- MDTextButton_
- MDFloatingActionButtonSpeedDial_

.. MDIconButton:
MDIconButton
------------

.. tabs::

    .. tab:: Declarative KV style

        .. code-block:: python

            from kivy.lang import Builder

            from kivymd.app import MDApp

            KV = '''
            MDScreen:

                MDIconButton:
                    icon: "language-python"
                    pos_hint: {"center_x": .5, "center_y": .5}
            '''


            class Example(MDApp):
                def build(self):
                    self.theme_cls.theme_style = "Dark"
                    self.theme_cls.primary_palette = "Orange"
                    return Builder.load_string(KV)


            Example().run()

    .. tab:: Declarative python style

        .. code-block:: python

            from kivymd.app import MDApp
            from kivymd.uix.button import MDIconButton
            from kivymd.uix.screen import MDScreen


            class Example(MDApp):
                def build(self):
                    self.theme_cls.theme_style = "Dark"
                    self.theme_cls.primary_palette = "Orange"
                    return (
                        MDScreen(
                            MDIconButton(
                                icon="language-python",
                                pos_hint={"center_x": 0.5, "center_y": 0.5},
                            )
                        )
                    )


            Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.png
    :align: center

The :class:`~MDIconButton.icon` parameter must have the name of the icon
from ``kivymd/icon_definitions.py`` file.

You can also use custom icons:

.. code-block:: kv

    MDIconButton:
        icon: "kivymd/images/logo/kivymd-icon-256.png"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.png
    :align: center

By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``.
Use :class:`~BaseButton.icon_size` attribute to resize the button:

.. code-block:: kv

    MDIconButton:
        icon: "android"
        icon_size: "64sp"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.png
    :align: center

By default, the color of :class:`~MDIconButton`
(depending on the style of the application) is black or white.
You can change the color of :class:`~MDIconButton` as the text color
of :class:`~kivymd.uix.label.MDLabel`, substituting ``theme_icon_color`` for
``theme_text_color`` and ``icon_color`` for ``text_color``.

.. code-block:: kv

    MDIconButton:
        icon: "android"
        theme_icon_color: "Custom"
        icon_color: app.theme_cls.primary_color

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-theme-text-color.png
    :align: center

.. MDFloatingActionButton:
MDFloatingActionButton
----------------------

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button.png
    :align: center

The above parameters for :class:`~MDIconButton` apply
to :class:`~MDFloatingActionButton`.

To change :class:`~MDFloatingActionButton` background, use the
``md_bg_color`` parameter:

.. code-block:: kv

    MDFloatingActionButton:
        icon: "android"
        md_bg_color: app.theme_cls.primary_color

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-md-bg-color.png
    :align: center

Material design style 3
-----------------------

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.button import MDFloatingActionButton

    KV = '''
    MDScreen:
        md_bg_color: "#f7f2fa"

        MDBoxLayout:
            id: box
            spacing: "56dp"
            adaptive_size: True
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


    class Example(MDApp):
        def build(self):
            self.theme_cls.theme_style = "Dark"
            self.theme_cls.primary_palette = "Orange"
            self.theme_cls.material_style = "M3"
            return Builder.load_string(KV)

        def on_start(self):
            data = {
                "standard": {"md_bg_color": "#fefbff", "text_color": "#6851a5"},
                "small": {"md_bg_color": "#e9dff7", "text_color": "#211c29"},
                "large": {"md_bg_color": "#f8d7e3", "text_color": "#311021"},
            }
            for type_button in data.keys():
                self.root.ids.box.add_widget(
                    MDFloatingActionButton(
                        icon="pencil",
                        type=type_button,
                        theme_icon_color="Custom",
                        md_bg_color=data[type_button]["md_bg_color"],
                        icon_color=data[type_button]["text_color"],
                    )
                )


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.png
    :align: center

.. MDFlatButton:
MDFlatButton
------------

To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter:

.. code-block:: kv

    MDFlatButton:
        text: "MDFlatButton"
        theme_text_color: "Custom"
        text_color: "orange"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png
    :align: center

Or use markup:

.. code-block:: kv

    MDFlatButton:
        text: "[color=#00ffcc]MDFlatButton[/color]"

To specify the font size and font name, use the parameters as in the usual
`Kivy` buttons:

.. code-block:: kv

    MDFlatButton:
        text: "MDFlatButton"
        font_size: "18sp"
        font_name: "path/to/font"

.. MDRaisedButton:
MDRaisedButton
--------------

This button is similar to the :class:`~MDFlatButton` button except that you
can set the background color for :class:`~MDRaisedButton`:

.. code-block:: kv

    MDRaisedButton:
        text: "MDRaisedButton"
        md_bg_color: "red"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.png
    :align: center

.. MDRectangleFlatButton:
MDRectangleFlatButton
---------------------

.. code-block:: kv

    MDRectangleFlatButton:
        text: "MDRectangleFlatButton"
        theme_text_color: "Custom"
        text_color: "white"
        line_color: "red"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png
    :align: center

.. MDRectangleFlatIconButton:
MDRectangleFlatIconButton
-------------------------

Button parameters :class:`~MDRectangleFlatIconButton` are the same as
button :class:`~MDRectangleFlatButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.

.. code-block:: kv

    MDRectangleFlatIconButton:
        icon: "android"
        text: "MDRectangleFlatIconButton"
        theme_text_color: "Custom"
        text_color: "white"
        line_color: "red"
        theme_icon_color: "Custom"
        icon_color: "orange"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png
    :align: center

Without border
--------------

.. code-block:: python

    from kivymd.app import MDApp
    from kivymd.uix.screen import MDScreen
    from kivymd.uix.button import MDRectangleFlatIconButton


    class Example(MDApp):
        def build(self):
            self.theme_cls.theme_style = "Dark"
            self.theme_cls.primary_palette = "Orange"
            return (
                MDScreen(
                    MDRectangleFlatIconButton(
                        text="MDRectangleFlatIconButton",
                        icon="language-python",
                        line_color=(0, 0, 0, 0),
                        pos_hint={"center_x": .5, "center_y": .5},
                    )
                )
            )


    Example().run()

.. code-block:: kv

    MDRectangleFlatIconButton:
        text: "MDRectangleFlatIconButton"
        icon: "language-python"
        line_color: 0, 0, 0, 0
        pos_hint: {"center_x": .5, "center_y": .5}

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-without-border.png
    :align: center

.. MDRoundFlatButton:
MDRoundFlatButton
-----------------

.. code-block:: kv

    MDRoundFlatButton:
        text: "MDRoundFlatButton"
        text_color: "white"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png
    :align: center

.. MDRoundFlatIconButton:
MDRoundFlatIconButton
---------------------

Button parameters :class:`~MDRoundFlatIconButton` are the same as
button :class:`~MDRoundFlatButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`:

.. code-block:: kv

    MDRoundFlatIconButton:
        text: "MDRoundFlatIconButton"
        icon: "android"
        text_color: "white"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png
    :align: center

.. MDFillRoundFlatButton:
MDFillRoundFlatButton
---------------------

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-button.png
    :align: center

Button parameters :class:`~MDFillRoundFlatButton` are the same as
button :class:`~MDRaisedButton`.

.. MDFillRoundFlatIconButton:
MDFillRoundFlatIconButton
-------------------------

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-icon-button.png
    :align: center

Button parameters :class:`~MDFillRoundFlatIconButton` are the same as
button :class:`~MDRaisedButton`, with the addition of the
``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.

.. note:: Notice that the width of the :class:`~MDFillRoundFlatIconButton`
    button matches the size of the button text.

.. MDTextButton:
MDTextButton
------------

.. code-block:: kv

    MDTextButton:
        text: "MDTextButton"
        custom_color: "white"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png
    :align: center

.. MDFloatingActionButtonSpeedDial:
MDFloatingActionButtonSpeedDial
-------------------------------

.. Note:: See the full list of arguments in the class
    :class:`~MDFloatingActionButtonSpeedDial`.

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp

    KV = '''
    MDScreen:

        MDFloatingActionButtonSpeedDial:
            data: app.data
            root_button_anim: True
    '''


    class Example(MDApp):
        data = {
            'Python': 'language-python',
            'PHP': 'language-php',
            'C++': 'language-cpp',
        }

        def build(self):
            self.theme_cls.theme_style = "Dark"
            self.theme_cls.primary_palette = "Orange"
            return Builder.load_string(KV)


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial.gif
    :align: center

Or without KV Language:

.. tabs::

    .. tab:: Imperative python style

        .. code-block:: python

            from kivymd.uix.screen import MDScreen
            from kivymd.app import MDApp
            from kivymd.uix.button import MDFloatingActionButtonSpeedDial


            class Example(MDApp):
                data = {
                    'Python': 'language-python',
                    'PHP': 'language-php',
                    'C++': 'language-cpp',
                }

                def build(self):
                    self.theme_cls.theme_style = "Dark"
                    self.theme_cls.primary_palette = "Orange"
                    screen = MDScreen()
                    speed_dial = MDFloatingActionButtonSpeedDial()
                    speed_dial.data = self.data
                    speed_dial.root_button_anim = True
                    screen.add_widget(speed_dial)
                    return screen


            Example().run()

    .. tab:: Declarative python style

        .. code-block:: python

            from kivymd.uix.screen import MDScreen
            from kivymd.app import MDApp
            from kivymd.uix.button import MDFloatingActionButtonSpeedDial


            class Example(MDApp):
                def build(self):
                    self.theme_cls.theme_style = "Dark"
                    self.theme_cls.primary_palette = "Orange"
                    return (
                        MDScreen(
                            MDFloatingActionButtonSpeedDial(
                                data={
                                    'Python': 'language-python',
                                    'PHP': 'language-php',
                                    'C++': 'language-cpp',
                                },
                                root_button_anim=True,
                            )
                        )
                    )


            Example().run()

You can use various types of animation of labels for buttons on the stack:

.. code-block:: kv

    MDFloatingActionButtonSpeedDial:
        hint_animation: True

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif
    :align: center

You can set your color values for background, text of buttons etc:

.. code-block:: kv

    MDFloatingActionButtonSpeedDial:
        hint_animation: True
        bg_hint_color: app.theme_cls.primary_dark

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png
    :align: center

Binds to individual buttons
---------------------------

.. tabs::

    .. tab:: Declarative KV style

        .. code-block:: python

            from kivy.lang import Builder
            from kivy.properties import DictProperty

            from kivymd.app import MDApp

            KV = '''
            MDScreen:

                MDFloatingActionButtonSpeedDial:
                    id: speed_dial
                    data: app.data
                    root_button_anim: True
                    hint_animation: True
            '''


            class Example(MDApp):
                data = DictProperty()

                def build(self):
                    self.theme_cls.theme_style = "Dark"
                    self.theme_cls.primary_palette = "Orange"
                    self.data = {
                        'Python': 'language-python',
                        'JS': [
                            'language-javascript',
                            "on_press", lambda x: print("pressed JS"),
                            "on_release", lambda x: print(
                                "stack_buttons",
                                self.root.ids.speed_dial.stack_buttons
                            )
                        ],
                        'PHP': [
                            'language-php',
                            "on_press", lambda x: print("pressed PHP"),
                            "on_release", self.callback
                        ],
                        'C++': [
                            'language-cpp',
                            "on_press", lambda x: print("pressed C++"),
                            "on_release", lambda x: self.callback()
                        ],
                    }
                    return Builder.load_string(KV)

                def callback(self, *args):
                    print(args)


            Example().run()

    .. tab:: Declarative python style

        .. code-block:: python

            from kivymd.app import MDApp
            from kivymd.uix.button import MDFloatingActionButtonSpeedDial
            from kivymd.uix.screen import MDScreen


            class Example(MDApp):
                def build(self):
                    self.theme_cls.theme_style = "Dark"
                    self.theme_cls.primary_palette = "Orange"
                    return (
                        MDScreen(
                            MDFloatingActionButtonSpeedDial(
                                id="speed_dial",
                                hint_animation=True,
                                root_button_anim=True,
                            )
                        )
                    )

                def on_start(self):
                    data = {
                        "Python": "language-python",
                        "JS": [
                            "language-javascript",
                            "on_press", lambda x: print("pressed JS"),
                            "on_release", lambda x: print(
                                "stack_buttons",
                                self.root.ids.speed_dial.stack_buttons
                            )
                        ],
                        "PHP": [
                            "language-php",
                            "on_press", lambda x: print("pressed PHP"),
                            "on_release", self.callback
                        ],
                        "C++": [
                            "language-cpp",
                            "on_press", lambda x: print("pressed C++"),
                            "on_release", lambda x: self.callback()
                        ],
                    }
                    self.root.ids.speed_dial.data = data

                def callback(self, *args):
                    print(args)


            Example().run()
"""

from __future__ import annotations

__all__ = (
    "BaseButton",
    "MDIconButton",
    "MDFloatingActionButton",
    "MDFlatButton",
    "MDRaisedButton",
    "MDRectangleFlatButton",
    "MDRectangleFlatIconButton",
    "MDRoundFlatButton",
    "MDRoundFlatIconButton",
    "MDFillRoundFlatButton",
    "MDFillRoundFlatIconButton",
    "MDTextButton",
    "MDFloatingActionButtonSpeedDial",
)

import os
from typing import Union

from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp, sp
from kivy.properties import (
    BooleanProperty,
    BoundedNumericProperty,
    ColorProperty,
    DictProperty,
    NumericProperty,
    ObjectProperty,
    OptionProperty,
    StringProperty,
    VariableListProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.weakproxy import WeakProxy

from kivymd import uix_path
from kivymd.color_definitions import text_colors
from kivymd.font_definitions import theme_font_styles
from kivymd.material_resources import (
    FLOATING_ACTION_BUTTON_M2_ELEVATION,
    FLOATING_ACTION_BUTTON_M2_OFFSET,
    FLOATING_ACTION_BUTTON_M3_ELEVATION,
    FLOATING_ACTION_BUTTON_M3_OFFSET,
    FLOATING_ACTION_BUTTON_M3_SOFTNESS,
    RAISED_BUTTON_OFFSET,
    RAISED_BUTTON_SOFTNESS,
)
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
    CommonElevationBehavior,
    DeclarativeBehavior,
    RectangularRippleBehavior,
    RotateBehavior,
)
from kivymd.uix.label import MDLabel
from kivymd.uix.tooltip import MDTooltip

with open(
    os.path.join(uix_path, "button", "button.kv"), encoding="utf-8"
) as kv_file:
    Builder.load_string(kv_file.read())


theme_text_color_options = (
    "Primary",
    "Secondary",
    "Hint",
    "Error",
    "Custom",
    "ContrastParentBackground",
)


class BaseButton(
    DeclarativeBehavior,
    RectangularRippleBehavior,
    ThemableBehavior,
    ButtonBehavior,
    AnchorLayout,
):
    """
    Base class for all buttons.

    For more information, see in the
    :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
    :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
    :class:`~kivymd.theming.ThemableBehavior` and
    :class:`~kivy.uix.behaviors.ButtonBehavior` and
    :class:`~kivy.uix.anchorlayout.AnchorLayout`
    classes documentation.
    """

    padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)])
    """
    Padding between the widget box and its children, in pixels:
    [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].

    .. versionadded:: 1.0.0

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

    halign = OptionProperty("center", options=("left", "center", "right"))
    """
    Horizontal anchor.

    .. versionadded:: 1.0.0

    :attr:`anchor_x` is an :class:`~kivy.properties.OptionProperty`
    and defaults to 'center'. It accepts values of 'left', 'center' or 'right'.
    """

    valign = OptionProperty("center", options=("top", "center", "bottom"))
    """
    Vertical anchor.

    .. versionadded:: 1.0.0

    :attr:`anchor_y` is an :class:`~kivy.properties.OptionProperty`
    and defaults to 'center'. It accepts values of 'top', 'center' or 'bottom'.
    """

    text = StringProperty("")
    """
    Button text.

    :attr:`text` is a :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    icon = StringProperty("")
    """
    Button icon.

    :attr:`icon` is a :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    font_style = OptionProperty("Body1", options=theme_font_styles)
    """
    Button text font style.

    Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`,
    `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`,
    `'Caption'`, `'Overline'`, `'Icon'`.

    :attr:`font_style` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'Body1'`.
    """

    theme_text_color = OptionProperty(None, options=theme_text_color_options)
    """
    Button text type. Available options are: (`"Primary"`, `"Secondary"`,
    `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).

    :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `None` (set by button class).
    """

    theme_icon_color = OptionProperty(None, options=theme_text_color_options)
    """
    Button icon type. Available options are: (`"Primary"`, `"Secondary"`,
    `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).

    .. versionadded:: 1.0.0

    :attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `None` (set by button subclass).
    """

    text_color = ColorProperty(None)
    """
    Button text color in (r, g, b, a) or string format.

    :attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    icon_color = ColorProperty(None)
    """
    Button icon color in (r, g, b, a) or string format.

    :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    font_name = StringProperty()
    """
    Button text font name.

    :attr:`font_name` is a :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    font_size = NumericProperty("14sp")
    """
    Button text font size.

    :attr:`font_size` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `14sp`.
    """

    icon_size = NumericProperty()
    """
    Icon font size.
    Use this parameter as the font size, that is, in sp units.

    .. versionadded:: 1.0.0

    :attr:`icon_size` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `None`.
    """

    line_width = NumericProperty(1)
    """
    Line width for button border.

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

    line_color = ColorProperty(None)
    """
    Line color in (r, g, b, a) or string format for button border.

    :attr:`line_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    line_color_disabled = ColorProperty(None)
    """
    Disabled line color in (r, g, b, a) or string format for button border.

    .. versionadded:: 1.0.0

    :attr:`line_color_disabled` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    md_bg_color = ColorProperty(None)
    """
    Button background color in (r, g, b, a) or string format.

    :attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    md_bg_color_disabled = ColorProperty(None)
    """
    The background color in (r, g, b, a) or string format of the button when
    the button is disabled.

    :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    disabled_color = ColorProperty(None)
    """
    The color of the text and icon when the button is disabled,
    in (r, g, b, a) or string format.

    .. versionadded:: 1.0.0

    :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    rounded_button = BooleanProperty(False)
    """
    Should the button have fully rounded corners (e.g. like M3 buttons)?

    .. versionadded:: 1.0.0

    :attr:`rounded_button` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    # Note - _radius must be > 0 to avoid rendering issues.
    _radius = BoundedNumericProperty(dp(4), min=0.0999, errorvalue=0.1)
    # Properties used for rendering.
    _disabled_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
    _md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
    _md_bg_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0])
    _line_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
    _line_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0])
    _theme_text_color = OptionProperty(None, options=theme_text_color_options)
    _theme_icon_color = OptionProperty(None, options=theme_text_color_options)
    _text_color = ColorProperty(None)
    _icon_color = ColorProperty(None)

    # Defaults which can be overridden in subclasses
    _min_width = NumericProperty(dp(64))
    _min_height = NumericProperty(dp(36))

    # Default colors - set to None to use primary theme colors
    _default_md_bg_color = [0.0, 0.0, 0.0, 0.0]
    _default_md_bg_color_disabled = [0.0, 0.0, 0.0, 0.0]
    _default_line_color = [0.0, 0.0, 0.0, 0.0]
    _default_line_color_disabled = [0.0, 0.0, 0.0, 0.0]
    _default_theme_text_color = StringProperty("Primary")
    _default_theme_icon_color = StringProperty("Primary")
    _default_text_color = ColorProperty(None)
    _default_icon_color = ColorProperty(None)

    _animation_fade_bg = ObjectProperty(None, allownone=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.theme_cls.bind(
            primary_palette=self.set_all_colors,
            theme_style=self.set_all_colors,
        )
        self.bind(
            md_bg_color=self.set_button_colors,
            md_bg_color_disabled=self.set_button_colors,
            line_color=self.set_button_colors,
            line_color_disabled=self.set_button_colors,
            theme_text_color=self.set_text_color,
            text_color=self.set_text_color,
            theme_icon_color=self.set_icon_color,
            icon_color=self.set_icon_color,
            disabled_color=self.set_disabled_color,
            rounded_button=self.set_radius,
            height=self.set_radius,
        )
        Clock.schedule_once(self.set_all_colors)
        Clock.schedule_once(self.set_radius)

    def set_disabled_color(self, *args):
        """
        Sets the color for the icon, text and line of the button when button
        is disabled.
        """

        if self.disabled:
            disabled_color = (
                self.disabled_color
                if self.disabled_color
                else self.theme_cls.disabled_hint_text_color
            )
            self._disabled_color = disabled_color
            # Button icon color.
            if "lbl_ic" in self.ids:
                self.ids.lbl_ic.disabled_color = disabled_color
            # Button text color.
            if "lbl_txt" in self.ids:
                self.ids.lbl_txt.disabled_color = disabled_color
        else:
            self._disabled_color = self._line_color

    def set_all_colors(self, *args) -> None:
        """Set all button colours."""

        self.set_button_colors()
        self.set_text_color()
        self.set_icon_color()

    def set_button_colors(self, *args) -> None:
        """Set all button colours (except text/icons)."""

        # Set main color
        _md_bg_color = (
            self.md_bg_color
            or self._default_md_bg_color
            or self.theme_cls.primary_color
        )

        # Set disabled color
        _md_bg_color_disabled = (
            self.md_bg_color_disabled
            or (
                [sum(self.md_bg_color[0:3]) / 3.0] * 3
                + [0.38 if self.theme_cls.theme_style == "Light" else 0.5]
                if self.md_bg_color
                else None
            )
            or self._default_md_bg_color_disabled
            or self.theme_cls.disabled_primary_color
        )

        # Set line color
        _line_color = (
            self.line_color
            or self._default_line_color
            or self.theme_cls.primary_color
        )

        # Set disabled line color
        _line_color_disabled = (
            self.line_color_disabled
            or (
                [sum(self.line_color[0:3]) / 3.0] * 3
                + [0.38 if self.theme_cls.theme_style == "Light" else 0.5]
                if self.line_color
                else None
            )
            or self._default_line_color_disabled
            or self.theme_cls.disabled_primary_color
        )

        if self.theme_cls.theme_style_switch_animation:
            Animation(
                _md_bg_color=_md_bg_color,
                _md_bg_color_disabled=_md_bg_color_disabled,
                _line_color=_line_color,
                _line_color_disabled=_line_color_disabled,
                d=self.theme_cls.theme_style_switch_animation_duration,
                t="linear",
            ).start(self)
        else:
            self._md_bg_color = _md_bg_color
            self._md_bg_color_disabled = _md_bg_color_disabled
        self._line_color = _line_color
        self._line_color_disabled = _line_color_disabled

    def set_text_color(self, *args) -> None:
        """
        Set _theme_text_color and _text_color based on defaults and options.
        """

        self._theme_text_color = (
            self.theme_text_color or self._default_theme_text_color
        )
        if self._default_text_color == "PrimaryHue":
            default_text_color = text_colors[self.theme_cls.primary_palette][
                self.theme_cls.primary_hue
            ]
        elif self._default_text_color == "Primary":
            default_text_color = self.theme_cls.primary_color
        else:
            default_text_color = self.theme_cls.text_color
        self._text_color = self.text_color or default_text_color

    def set_icon_color(self, *args) -> None:
        """
        Set _theme_icon_color and _icon_color based on defaults and options.
        """

        self._theme_icon_color = (
            (self.theme_icon_color or self._default_theme_icon_color)
            if not self.disabled
            else "Custom"
        )
        if self._default_icon_color == "PrimaryHue":
            default_icon_color = text_colors[self.theme_cls.primary_palette][
                self.theme_cls.primary_hue
            ]
        elif self._default_icon_color == "Primary":
            default_icon_color = self.theme_cls.primary_color
        else:
            default_icon_color = self.theme_cls.text_color
        self._icon_color = self.icon_color or default_icon_color

    def set_radius(self, *args) -> None:
        """
        Set the radius, if we are a rounded button, based on the
        current height.
        """

        if self.rounded_button:
            self._radius = self.height / 2

    # Touch events that cause transparent buttons to fade to background
    def on_touch_down(self, touch):
        """
        Animates fade to background on press, for buttons with no
        background color.
        """

        if touch.is_mouse_scrolling:
            return False
        elif not self.collide_point(touch.x, touch.y):
            return False
        elif self in touch.ud:
            return False
        elif self.disabled:
            return False
        else:
            if self._md_bg_color[3] == 0.0:
                self._animation_fade_bg = Animation(
                    duration=0.5, _md_bg_color=[0.0, 0.0, 0.0, 0.1]
                )
                self._animation_fade_bg.start(self)
            return super().on_touch_down(touch)

    def on_touch_up(self, touch):
        """Animates return to original background on touch release."""

        if not self.disabled and self._animation_fade_bg:
            self._animation_fade_bg.stop_property(self, "_md_bg_color")
            self._animation_fade_bg = None
            md_bg_color = (
                self.md_bg_color
                or self._default_md_bg_color
                or self.theme_cls.primary_color
            )
            Animation(duration=0.05, _md_bg_color=md_bg_color).start(self)
        return super().on_touch_up(touch)

    def on_disabled(self, instance_button, disabled_value: bool) -> None:
        if hasattr(super(), "on_disabled"):
            if self.disabled is True:
                Animation.cancel_all(self, "elevation")
            super().on_disabled(instance_button, disabled_value)
        Clock.schedule_once(self.set_disabled_color)


class ButtonElevationBehaviour(CommonElevationBehavior):
    """
    Implements elevation behavior as well as the recommended down/disabled
    colors for raised buttons.

    The minimum elevation for any raised button is `'1dp'`,
    by default, set to `'2dp'`.

    The `_elevation_raised` is automatically computed and is set to
    `self.elevation + 6` each time `self.elevation` is updated.
    """

    _elevation_raised = NumericProperty()
    _anim_raised = ObjectProperty(None, allownone=True)
    _default_elevation = 2

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        if self.elevation == 0:
            self.elevation = self._default_elevation
        if hasattr(self, "radius"):
            self.bind(_radius=self.setter("radius"))
        Clock.schedule_once(self.create_anim_raised)
        self.on_disabled(self, self.disabled)

    def create_anim_raised(self, *args) -> None:
        if self.elevation:
            self._elevation_raised = self.elevation
            self._anim_raised = Animation(elevation=self.elevation + 1, d=0.15)

    def on_touch_down(self, touch):
        if not self.disabled:
            if touch.is_mouse_scrolling:
                return False
            if not self.collide_point(touch.x, touch.y):
                return False
            if self in touch.ud:
                return False
            if self._anim_raised and self.elevation:
                self._anim_raised.start(self)
        return super().on_touch_down(touch)

    def on_touch_up(self, touch):
        if not self.disabled:
            if self in touch.ud:
                self.stop_elevation_anim()
                return super().on_touch_up(touch)
        return super().on_touch_up(touch)

    def stop_elevation_anim(self):
        Animation.cancel_all(self, "elevation")
        if self._anim_raised and self.elevation:
            self.elevation = self._elevation_raised


class ButtonContentsText:
    """Contents for :class:`~BaseButton` class consisting of a single label."""


class ButtonContentsIcon:
    """
    Contents for a round BaseButton consisting of an :class:`~MDIcon` class.
    """

    _min_width = NumericProperty(0)

    def on_text_color(self, instance_button, color: list) -> None:
        """
        Set icon_color equal to text_color.
        For backwards compatibility - can use text_color instead
        of icon_color.
        """

        if color:
            self.icon_color = color


class ButtonContentsIconText:
    """
    Contents for :class:`~BaseButton` class consisting of a
    :class:`~kivy.uix.boxlayout.BoxLayout` with an icon and a label.
    """

    padding = VariableListProperty([dp(12), dp(8), dp(16), dp(8)])
    """
    Padding between the widget box and its children, in pixels:
    [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].

    .. versionadded:: 1.0.0

    :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
    and defaults to [12dp, 8dp, 16dp, 8dp].
    """


# Old MD Button classes


class OldButtonIconMixin:
    """Backwards-compatibility for icons."""

    icon = StringProperty("android")

    def on_icon_color(self, instance_button, color: list) -> None:
        """
        If we are setting an icon color, set theme_icon_color to Custom.
        For backwards compatibility (before theme_icon_color existed).
        """

        if color and (self.theme_text_color == "Custom"):
            self.theme_icon_color = "Custom"


class MDFlatButton(BaseButton, ButtonContentsText):
    """
    A flat rectangular button with (by default) no border or background.
    Text is the default text color.

    For more information, see in the
    :class:`~BaseButton` and :class:`~ButtonContentsText`
    classes documentation.
    """

    padding = VariableListProperty([dp(8), dp(8), dp(8), dp(8)])
    """
    Padding between the widget box and its children, in pixels:
    [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].

    .. versionadded:: 1.0.0

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


class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText):
    """
    A flat button with (by default) a primary color fill and matching
    color text.

    For more information, see in the
    :class:`~BaseButton` and
    :class:`~ButtonElevationBehaviour` and
    :class:`~ButtonContentsText`
    classes documentation.
    """

    # FIXME: Move the underlying attributes to the :class:`~BaseButton` class.
    #  This applies to all classes of buttons that have similar attributes.
    _default_md_bg_color = None
    _default_md_bg_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_text_color = "PrimaryHue"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.shadow_softness = RAISED_BUTTON_SOFTNESS
        self.shadow_offset = RAISED_BUTTON_OFFSET
        # self.shadow_radius = self._radius * 2


class MDRectangleFlatButton(BaseButton, ButtonContentsText):
    """
    A flat button with (by default) a primary color border and primary
    color text.

    For more information, see in the
    :class:`~BaseButton` and :class:`~ButtonContentsText`
    classes documentation.
    """

    _default_line_color = None
    _default_line_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_text_color = "Primary"


class MDRectangleFlatIconButton(
    BaseButton, OldButtonIconMixin, ButtonContentsIconText
):
    """
    A flat button with (by default) a primary color border, primary color text
    and a primary color icon on the left.

    For more information, see in the
    :class:`~BaseButton` and
    :class:`~OldButtonIconMixin` and
    :class:`~ButtonContentsIconText`
    classes documentation.
    """

    _default_line_color = None
    _default_line_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_theme_icon_color = "Custom"
    _default_text_color = "Primary"
    _default_icon_color = "Primary"


class MDRoundFlatButton(BaseButton, ButtonContentsText):
    """
    A flat button with (by default) fully rounded corners, a primary
    color border and primary color text.

    For more information, see in the
    :class:`~BaseButton` and :class:`~ButtonContentsText`
    classes documentation.
    """

    _default_line_color = None
    _default_line_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_text_color = "Primary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rounded_button = True


class MDRoundFlatIconButton(
    BaseButton, OldButtonIconMixin, ButtonContentsIconText
):
    """
    A flat button with (by default) rounded corners, a primary color border,
    primary color text and a primary color icon on the left.

    For more information, see in the
    :class:`~BaseButton` and
    :class:`~OldButtonIconMixin` and
    :class:`~ButtonContentsIconText`
    classes documentation.
    """

    _default_line_color = None
    _default_line_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_theme_icon_color = "Custom"
    _default_text_color = "Primary"
    _default_icon_color = "Primary"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rounded_button = True


class MDFillRoundFlatButton(BaseButton, ButtonContentsText):
    """
    A flat button with (by default) rounded corners, a primary color fill
    and primary color text.

    For more information, see in the
    :class:`~BaseButton` and :class:`~ButtonContentsText`
    classes documentation.
    """

    _default_md_bg_color = None
    _default_md_bg_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_text_color = "PrimaryHue"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rounded_button = True


class MDFillRoundFlatIconButton(
    BaseButton, OldButtonIconMixin, ButtonContentsIconText
):
    """
    A flat button with (by default) rounded corners, a primary color fill,
    primary color text and a primary color icon on the left.

    For more information, see in the
    :class:`~BaseButton` and
    :class:`~OldButtonIconMixin` and
    :class:`~ButtonContentsIconText`
    classes documentation.
    """

    _default_md_bg_color = None
    _default_md_bg_color_disabled = None
    _default_theme_text_color = "Custom"
    _default_theme_icon_color = "Custom"
    _default_text_color = "PrimaryHue"
    _default_icon_color = "PrimaryHue"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rounded_button = True


class MDIconButton(BaseButton, OldButtonIconMixin, ButtonContentsIcon):
    """
    A simple rounded icon button.

    For more information, see in the
    :class:`~BaseButton` and
    :class:`~OldButtonIconMixin` and
    :class:`~ButtonContentsIcon` classes documentation.
    """

    icon = StringProperty("checkbox-blank-circle")
    """
    Button icon.

    :attr:`icon` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'checkbox-blank-circle'`.
    """

    _min_width = NumericProperty(0)
    _default_icon_pad = max(dp(48) - sp(24), 0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rounded_button = True
        # FIXME: GraphicException: Invalid width value, must be > 0
        self.line_width = 0.001
        Clock.schedule_once(self.set_size)

    def set_size(self, interval: Union[int, float]) -> None:
        """
        Sets the icon width/height based on the current `icon_size`
        attribute, or the default value if it is zero. The icon size
        is set to `(48, 48)` for an icon with the default font_size 24sp.
        """
        diameter = self._default_icon_pad + (self.icon_size or sp(24))
        self.width = diameter
        self.height = diameter


class MDFloatingActionButton(
    BaseButton, OldButtonIconMixin, ButtonElevationBehaviour, ButtonContentsIcon
):
    """
    Implementation
    `FAB <https://m3.material.io/components/floating-action-button/overview>`_
    button.

    For more information, see in the
    :class:`~BaseButton` and
    :class:`~OldButtonIconMixin` and
    :class:`~ButtonElevationBehaviour` and
    :class:`~ButtonContentsIcon` classes documentation.
    """

    type = OptionProperty("standard", options=["small", "large", "standard"])
    """
    Type of M3 button.

    .. versionadded:: 1.0.0

    Available options are: 'small', 'large', 'standard'.

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-types.png
        :align: center

    :attr:`type` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'standard'`.
    """

    _default_md_bg_color = None
    _default_md_bg_color_disabled = None
    _default_theme_icon_color = "Custom"
    _default_icon_color = "PrimaryHue"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # FIXME: GraphicException: Invalid width value, must be > 0
        self.line_width = 0.001
        self.theme_cls.bind(material_style=self.set_size_and_radius)
        Clock.schedule_once(self.set_size)
        Clock.schedule_once(self.set__radius)
        Clock.schedule_once(self.set_font_size)

    def set_font_size(self, *args) -> None:
        if self.theme_cls.material_style == "M3":
            if self.type == "large":
                self.icon_size = "36sp"
            else:
                self.icon_size = 0

    def set__radius(self, *args) -> None:
        if self.theme_cls.material_style == "M2":
            self.shadow_radius = self.height / 2
            self.elevation = FLOATING_ACTION_BUTTON_M2_ELEVATION
            self.shadow_offset = FLOATING_ACTION_BUTTON_M2_OFFSET
            self.rounded_button = True
        else:
            self.shadow_softness = FLOATING_ACTION_BUTTON_M3_SOFTNESS
            self.shadow_offset = FLOATING_ACTION_BUTTON_M3_OFFSET
            self.elevation = FLOATING_ACTION_BUTTON_M3_ELEVATION
            self.rounded_button = False

            if self.type == "small":
                self._radius = dp(12)
            elif self.type == "standard":
                self._radius = dp(16)
            elif self.type == "large":
                self._radius = dp(28)

            self.shadow_radius = self._radius

    def set_size_and_radius(self, *args) -> None:
        self.set_size(args)
        self.set__radius(args)

    def set_size(self, *args) -> None:
        if self.theme_cls.material_style == "M2":
            self.size = dp(56), dp(56)
        else:
            if self.type == "small":
                self.size = dp(40), dp(40)
            elif self.type == "standard":
                self.size = dp(56), dp(56)
            elif self.type == "large":
                self.size = dp(96), dp(96)

    def on_type(self, instance_md_floating_action_button, type: str) -> None:
        self.set_size()
        self.set_font_size()


class MDTextButton(ButtonBehavior, MDLabel):
    """
    Text button class.

    For more information, see in the
    :class:`~kivy.uix.behaviors.ButtonBehavior` and
    :class:`~kivymd.uix.label.MDLabel` classes documentation.
    """

    color = ColorProperty(None)
    """
    Button color in (r, g, b, a) or string format.

    :attr:`color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    color_disabled = ColorProperty(None)
    """
    Button color disabled in (r, g, b, a) or string format.

    :attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    _color = ColorProperty(None)  # last current button text color

    def animation_label(self) -> None:
        def set_default_state_label(*args):
            Animation(opacity=1, d=0.1, t="in_out_cubic").start(self)

        anim = Animation(opacity=0.5, d=0.2, t="in_out_cubic")
        anim.bind(on_complete=set_default_state_label)
        anim.start(self)

    def on_press(self, *args):
        self.animation_label()
        return super().on_press(*args)

    def on_disabled(self, instance_button, disabled_value) -> None:
        if disabled_value:
            if not self.color_disabled:
                self.color_disabled = self.theme_cls.disabled_hint_text_color
                self._color = self.color
            self.text_color = self.color_disabled
        else:
            self.text_color = self._color


# SpeedDial classes


class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip):
    _canvas_width = NumericProperty(0)
    _padding_right = NumericProperty(0)
    _bg_color = ColorProperty(None)

    def set_size(self, interval: Union[int, float]) -> None:
        self.width = "46dp"
        self.height = "46dp"


class MDFloatingBottomButton(BaseFloatingBottomButton):
    _bg_color = ColorProperty(None)


class MDFloatingRootButton(RotateBehavior, MDFloatingActionButton):
    rotate_value_angle = NumericProperty(0)


class MDFloatingLabel(MDLabel):
    bg_color = ColorProperty([0, 0, 0, 0])


class MDFloatingActionButtonSpeedDial(
    DeclarativeBehavior, ThemableBehavior, FloatLayout
):
    """
    For more information, see in the
    :class:`~kivy.uix.floatlayout.FloatLayout` class documentation.

    For more information, see in the
    :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
    :class:`~kivymd.theming.ThemableBehavior` and
    :class:`~kivy.uix.floatlayout.FloatLayout`
    lasses documentation.

    :Events:
        :attr:`on_open`
            Called when a stack is opened.
        :attr:`on_close`
            Called when a stack is closed.
        :attr:`on_press_stack_button`
            Called at the on_press event for the stack button.
        :attr:`on_release_stack_button`
            Called at the on_press event for the stack button.
    """

    icon = StringProperty("plus")
    """
    Root button icon name.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            icon: "pencil"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-icon.png
        :align: center

    :attr:`icon` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'plus'`.
    """

    anchor = OptionProperty("right", option=["right"])
    """
    Stack anchor. Available options are: `'right'`.

    :attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
    and defaults to `'right'`.
    """

    label_text_color = ColorProperty(None)
    """
    Color of floating text labels in (r, g, b, a) or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            label_text_color: "orange"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-text-color.png
        :align: center

    :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    label_bg_color = ColorProperty([0, 0, 0, 0])
    """
    Background color of floating text labels in (r, g, b, a) or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            label_text_color: "black"
            label_bg_color: "orange"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-bg-color.png
        :align: center

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

    label_radius = VariableListProperty([0], length=4)
    """
    The radius of the background of floating text labels.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            label_text_color: "black"
            label_bg_color: "orange"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-radius.png
        :align: center

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

    data = DictProperty()
    """
    Must be a dictionary.

    .. code-block:: python

        {
            'name-icon': 'Text label',
            ...,
            ...,
        }
    """

    right_pad = BooleanProperty(False)
    """
    If `True`, the background for the floating text label will increase by the
    number of pixels specified in the :attr:`~right_pad_value` parameter.

    Works only if the :attr:`~hint_animation` parameter is set to `True`.

    .. rubric:: False

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            hint_animation: True
            right_pad: False

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif
        :align: center

    .. rubric:: True

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            hint_animation: True
            right_pad: True
            right_pad_value: "10dp"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif
        :align: center

    :attr:`right_pad` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    right_pad_value = NumericProperty(0)
    """
    See :attr:`~right_pad` parameter for more information.

    :attr:`right_pad_value` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0`.
    """

    root_button_anim = BooleanProperty(False)
    """
    If ``True`` then the root button will rotate 45 degrees when the stack
    is opened.

    :attr:`root_button_anim` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    opening_transition = StringProperty("out_cubic")
    """
    The name of the stack opening animation type.

    :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    closing_transition = StringProperty("out_cubic")
    """
    The name of the stack closing animation type.

    :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    opening_transition_button_rotation = StringProperty("out_cubic")
    """
    The name of the animation type to rotate the root button when opening the
    stack.

    :attr:`opening_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    closing_transition_button_rotation = StringProperty("out_cubic")
    """
    The name of the animation type to rotate the root button when closing the
    stack.

    :attr:`closing_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    opening_time = NumericProperty(0.5)
    """
    Time required for the stack to go to: attr:`state` `'open'`.

    :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    closing_time = NumericProperty(0.2)
    """
    Time required for the stack to go to: attr:`state` `'close'`.

    :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    opening_time_button_rotation = NumericProperty(0.2)
    """
    Time required to rotate the root button 45 degrees during the stack
    opening animation.

    :attr:`opening_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    closing_time_button_rotation = NumericProperty(0.2)
    """
    Time required to rotate the root button 0 degrees during the stack
    closing animation.

    :attr:`closing_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    state = OptionProperty("close", options=("close", "open"))
    """
    Indicates whether the stack is closed or open.
    Available options are: `'close'`, `'open'`.

    :attr:`state` is a :class:`~kivy.properties.OptionProperty`
    and defaults to `'close'`.
    """

    bg_color_root_button = ColorProperty(None)
    """
    Background color of root button in (r, g, b, a) or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            bg_color_root_button: "red"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-root-button.png
        :align: center

    :attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    bg_color_stack_button = ColorProperty(None)
    """
    Background color of the stack buttons in (r, g, b, a) or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            bg_color_root_button: "red"
            bg_color_stack_button: "red"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-stack-button.png
        :align: center

    :attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    color_icon_stack_button = ColorProperty(None)
    """
    The color icon of the stack buttons in (r, g, b, a) or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            bg_color_root_button: "red"
            bg_color_stack_button: "red"
            color_icon_stack_button: "white"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-stack-button.png
        :align: center

    :attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    color_icon_root_button = ColorProperty(None)
    """
    The color icon of the root button in (r, g, b, a) or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            bg_color_root_button: "red"
            bg_color_stack_button: "red"
            color_icon_stack_button: "white"
            color_icon_root_button: self.color_icon_stack_button

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-root-button.png
        :align: center

    :attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    bg_hint_color = ColorProperty(None)
    """
    Background color for the floating text of the buttons in (r, g, b, a)
    or string format.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            bg_hint_color: "red"
            hint_animation: True

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-hint-color.png
        :align: center

    :attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    hint_animation = BooleanProperty(False)
    """
    Whether to use button extension animation to display floating text.

    :attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    stack_buttons = DictProperty()

    _label_pos_y_set = False
    _anim_buttons_data = {}
    _anim_labels_data = {}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type("on_open")
        self.register_event_type("on_close")
        self.register_event_type("on_press_stack_button")
        self.register_event_type("on_release_stack_button")
        Window.bind(on_resize=self._update_pos_buttons)

    def on_open(self, *args):
        """Called when a stack is opened."""

    def on_close(self, *args):
        """Called when a stack is closed."""

    def on_leave(self, instance_button: MDFloatingBottomButton) -> None:
        """Called when the mouse cursor goes outside the button of stack."""

        if self.state == "open":
            for widget in self.children:
                if isinstance(widget, MDFloatingLabel) and self.hint_animation:
                    Animation.cancel_all(widget)
                    for item in self.data.items():
                        if widget.text in item:
                            Animation(
                                _canvas_width=0,
                                _padding_right=0,
                                d=self.opening_time,
                                t=self.opening_transition,
                                _elevation=0,
                            ).start(instance_button)
                            Animation(
                                opacity=0, d=0.1, t=self.opening_transition
                            ).start(widget)

    def on_enter(self, instance_button: MDFloatingBottomButton) -> None:
        """Called when the mouse cursor is over a button from the stack."""

        if self.state == "open":
            for widget in self.children:
                if isinstance(widget, MDFloatingLabel) and self.hint_animation:
                    Animation.cancel_all(widget)
                    for item in self.data.items():
                        if widget.text in item:
                            Animation(
                                _canvas_width=widget.width + dp(24),
                                _padding_right=self.right_pad_value
                                if self.right_pad
                                else 0,
                                d=self.opening_time,
                                t=self.opening_transition,
                            ).start(instance_button)
                            if (
                                instance_button.icon
                                == self.data[f"{widget.text}"]
                                or instance_button.icon
                                == self.data[f"{widget.text}"][0]
                            ):
                                Animation(
                                    opacity=1,
                                    d=self.opening_time,
                                    t=self.opening_transition,
                                ).start(widget)
                            else:
                                Animation(
                                    opacity=0, d=0.1, t=self.opening_transition
                                ).start(widget)

    def on_data(self, instance_speed_dial, data: dict) -> None:
        """Creates a stack of buttons."""

        def on_data(*args):
            # Bottom buttons.
            for name, parameters in data.items():
                name_icon = (
                    parameters if (type(parameters) is str) else parameters[0]
                )

                bottom_button = MDFloatingBottomButton(
                    icon=name_icon,
                    on_enter=self.on_enter,
                    on_leave=self.on_leave,
                    opacity=0,
                )
                bottom_button.bind(
                    on_press=lambda x: self.dispatch("on_press_stack_button"),
                    on_release=lambda x: self.dispatch(
                        "on_release_stack_button"
                    ),
                )

                if "on_press" in parameters:
                    callback = parameters[parameters.index("on_press") + 1]
                    bottom_button.bind(on_press=callback)

                if "on_release" in parameters:
                    callback = parameters[parameters.index("on_release") + 1]
                    bottom_button.bind(on_release=callback)

                self.set_pos_bottom_buttons(bottom_button)
                self.add_widget(bottom_button)
                self.stack_buttons[name] = WeakProxy(bottom_button)
                # Labels.
                floating_text = name
                if floating_text:
                    label = MDFloatingLabel(text=floating_text, opacity=0)
                    label.bg_color = self.label_bg_color
                    label.radius = self.label_radius
                    label.text_color = (
                        self.label_text_color
                        if self.label_text_color
                        else self.theme_cls.text_color
                    )
                    self.add_widget(label)
            # Top root button.
            root_button = MDFloatingRootButton(on_release=self.open_stack)
            root_button.icon = self.icon
            self.set_pos_root_button(root_button)
            self.add_widget(root_button)

        self.clear_widgets()
        self.stack_buttons = {}
        self._anim_buttons_data = {}
        self._anim_labels_data = {}
        self._label_pos_y_set = False
        Clock.schedule_once(on_data)

    def on_icon(self, instance_speed_dial, name_icon: str) -> None:
        self._set_button_property(MDFloatingRootButton, "icon", name_icon)

    def on_label_text_color(
        self, instance_speed_dial, color: list | str
    ) -> None:
        for widget in self.children:
            if isinstance(widget, MDFloatingLabel):
                widget.text_color = color

    def on_color_icon_stack_button(
        self, instance_speed_dial, color: list
    ) -> None:
        self._set_button_property(MDFloatingBottomButton, "icon_color", color)

    def on_hint_animation(self, instance_speed_dial, value: bool) -> None:
        for widget in self.children:
            if isinstance(widget, MDFloatingLabel):
                widget.md_bg_color = (0, 0, 0, 0)

    def on_bg_hint_color(self, instance_speed_dial, color: list) -> None:
        setattr(MDFloatingBottomButton, "_bg_color", color)

    def on_color_icon_root_button(
        self, instance_speed_dial, color: list
    ) -> None:
        self._set_button_property(MDFloatingRootButton, "icon_color", color)

    def on_bg_color_stack_button(
        self, instance_speed_dial, color: list
    ) -> None:
        self._set_button_property(MDFloatingBottomButton, "md_bg_color", color)

    def on_bg_color_root_button(self, instance_speed_dial, color: list) -> None:
        self._set_button_property(MDFloatingRootButton, "md_bg_color", color)

    def on_press_stack_button(self, *args) -> None:
        """
        Called at the on_press event for the stack button.

        .. code-block:: kv

            MDFloatingActionButtonSpeedDial:
                on_press_stack_button: print(*args)

        .. versionadded:: 1.1.0
        """

    def on_release_stack_button(self, *args) -> None:
        """
        Called at the on_release event for the stack button.

        .. code-block:: kv

            MDFloatingActionButtonSpeedDial:
                on_release_stack_button: print(*args)

        .. versionadded:: 1.1.0
        """

    def set_pos_labels(self, instance_floating_label: MDFloatingLabel) -> None:
        """
        Sets the position of the floating labels.
        Called when the application's root window is resized.
        """

        if self.anchor == "right":
            instance_floating_label.x = (
                Window.width - instance_floating_label.width - dp(86)
            )

    def set_pos_root_button(
        self, instance_floating_root_button: MDFloatingRootButton
    ) -> None:
        """
        Sets the position of the root button.
        Called when the application's root window is resized.
        """

        def set_pos_root_button(*args):
            if self.anchor == "right":
                instance_floating_root_button.y = dp(20)
                instance_floating_root_button.x = self.parent.width - (
                    dp(56) + dp(20)
                )

        Clock.schedule_once(set_pos_root_button)

    def set_pos_bottom_buttons(
        self, instance_floating_bottom_button: MDFloatingBottomButton
    ) -> None:
        """
        Sets the position of the bottom buttons in a stack.
        Called when the application's root window is resized.
        """

        if self.anchor == "right":
            if self.state != "open":
                instance_floating_bottom_button.y = (
                    instance_floating_bottom_button.height / 2
                )
            instance_floating_bottom_button.x = Window.width - (
                instance_floating_bottom_button.height
                + instance_floating_bottom_button.width / 2
            )

    def open_stack(
        self, instance_floating_root_button: MDFloatingRootButton
    ) -> None:
        """Opens a button stack."""

        for widget in self.children:
            if isinstance(widget, MDFloatingLabel):
                Animation.cancel_all(widget)

        if self.state != "open":
            y = 0
            label_position = dp(54)
            anim_buttons_data = {}
            anim_labels_data = {}

            for widget in self.children:
                if isinstance(widget, MDFloatingBottomButton):
                    # Sets new button positions.
                    y += dp(56)
                    widget.y = widget.y * 2 + y
                    if not self._anim_buttons_data:
                        anim_buttons_data[widget] = Animation(
                            opacity=1,
                            d=self.opening_time,
                            t=self.opening_transition,
                        )
                elif isinstance(widget, MDFloatingLabel):
                    # Sets new labels positions.
                    label_position += dp(56)
                    # Sets the position of signatures only once.
                    if not self._label_pos_y_set:
                        widget.y = widget.y * 2 + label_position
                        widget.x = Window.width - widget.width - dp(86)
                    if not self._anim_labels_data:
                        anim_labels_data[widget] = Animation(
                            opacity=1, d=self.opening_time
                        )
                elif (
                    isinstance(widget, MDFloatingRootButton)
                    and self.root_button_anim
                ):
                    # Rotates the root button 45 degrees.
                    Animation(
                        rotate_value_angle=-45,
                        d=self.opening_time_button_rotation,
                        t=self.opening_transition_button_rotation,
                    ).start(widget)

            if anim_buttons_data:
                self._anim_buttons_data = anim_buttons_data
            if anim_labels_data and not self.hint_animation:
                self._anim_labels_data = anim_labels_data

            self.state = "open"
            self.dispatch("on_open")
            self.do_animation_open_stack(self._anim_buttons_data)
            self.do_animation_open_stack(self._anim_labels_data)
            if not self._label_pos_y_set:
                self._label_pos_y_set = True
        else:
            self.close_stack()

    def do_animation_open_stack(self, anim_data: dict) -> None:
        """
        :param anim_data:
            {
                <kivymd.uix.button.MDFloatingBottomButton object>:
                    <kivy.animation.Animation>,
                <kivymd.uix.button.MDFloatingBottomButton object>:
                    <kivy.animation.Animation object>,
                ...,
            }
        """

        def on_progress(animation, widget, value):
            if value >= 0.1:
                animation_open_stack()

        def animation_open_stack(*args):
            try:
                widget = next(widgets_list)
                animation = anim_data[widget]
                animation.bind(on_progress=on_progress)
                animation.start(widget)
            except StopIteration:
                pass

        widgets_list = iter(list(anim_data.keys()))
        animation_open_stack()

    def close_stack(self):
        """Closes the button stack."""

        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                Animation(
                    y=widget.height / 2,
                    d=self.closing_time,
                    t=self.closing_transition,
                    opacity=0,
                ).start(widget)
            elif isinstance(widget, MDFloatingLabel):
                if widget.opacity > 0:
                    Animation(opacity=0, d=0.1).start(widget)
            elif (
                isinstance(widget, MDFloatingRootButton)
                and self.root_button_anim
            ):
                Animation(
                    rotate_value_angle=0,
                    d=self.closing_time_button_rotation,
                    t=self.closing_transition_button_rotation,
                ).start(widget)
        self.state = "close"
        self.dispatch("on_close")

    def _update_pos_buttons(self, instance, width, height):
        # Updates button positions when resizing screen.
        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                self.set_pos_bottom_buttons(widget)
            elif isinstance(widget, MDFloatingRootButton):
                self.set_pos_root_button(widget)
            elif isinstance(widget, MDFloatingLabel):
                self.set_pos_labels(widget)

    def _set_button_property(
        self, instance, property_name: str, property_value: str | list
    ):
        def set_count_widget(*args):
            if self.children:
                for widget in self.children:
                    if isinstance(widget, instance):
                        setattr(instance, property_name, property_value)
                        Clock.unschedule(set_count_widget)
                        break

        Clock.schedule_interval(set_count_widget, 0)