"""
Themes/Theming
==============

.. seealso::

   `Material Design spec, Material theming <https://material.io/design/material-theming>`_

Material App
------------

The main class of your application, which in `Kivy` inherits from the
:class:`~kivy.app.App` class, in `KivyMD` must inherit from the
:class:`~kivymd.app.MDApp` class. The :class:`~kivymd.app.MDApp` class has
properties that allow you to control application properties such as
:attr:`color/style/font` of interface elements and much more.

Control material properties
---------------------------

The main application class inherited from the :class:`~kivymd.app.MDApp` class
has the :attr:`~kivymd.app.MDApp.theme_cls` attribute, with which you control
the material properties of your application.

Changing the theme colors
-------------------------

The standard theme_cls is designed to provide the standard themes and colors as
defined by Material Design.

We do not recommend that you change this.

However, if you do need to change the standard colors, for instance to meet branding
guidelines, you can do this by overloading the `color_definitions.py` object.

Create a custom color defintion object. This should have the same format as
the `colors <https://kivymd.readthedocs.io/en/latest/themes/color-definitions/#module-kivymd.color_definitions>`_
object in `color_definitions.py` and contain definitions for at least the
primary color, the accent color and the Light and Dark backgrounds.

.. note:: Your custom colors *must* use the names of the
    `existing colors as defined in the palette <https://kivymd.readthedocs.io/en/latest/themes/color-definitions/#kivymd.color_definitions.palette>`_
    e.g. You can have `Blue` but you cannot have `NavyBlue`.

Add the custom theme to the :class:`~kivymd.app.MDApp` as shown in the
following snippet.

.. tabs::

    .. tab:: Imperative python style with KV

        .. code-block:: python

            from kivy.lang import Builder
            from kivy.properties import ObjectProperty

            from kivymd.app import MDApp
            from kivymd.uix.floatlayout import MDFloatLayout
            from kivymd.uix.tab import MDTabsBase
            from kivymd.icon_definitions import md_icons

            colors = {
                "Teal": {
                    "200": "#212121",
                    "500": "#212121",
                    "700": "#212121",
                },
                "Red": {
                    "200": "#C25554",
                    "500": "#C25554",
                    "700": "#C25554",
                },
                "Light": {
                    "StatusBar": "E0E0E0",
                    "AppBar": "#202020",
                    "Background": "#2E3032",
                    "CardsDialogs": "#FFFFFF",
                    "FlatButtonDown": "#CCCCCC",
                },
            }


            KV = '''
            MDBoxLayout:
                orientation: "vertical"

                MDTopAppBar:
                    title: "Custom theme"

                MDTabs:
                    id: tabs


            <Tab>

                MDIconButton:
                    id: icon
                    icon: root.icon
                    icon_size: "48sp"
                    theme_icon_color: "Custom"
                    icon_color: "white"
                    pos_hint: {"center_x": .5, "center_y": .5}
            '''


            class Tab(MDFloatLayout, MDTabsBase):
                '''Class implementing content for a tab.'''

                icon = ObjectProperty()


            class Example(MDApp):
                icons = list(md_icons.keys())[15:30]

                def build(self):
                    self.theme_cls.colors = colors
                    self.theme_cls.primary_palette = "Teal"
                    self.theme_cls.accent_palette = "Red"
                    return Builder.load_string(KV)

                def on_start(self):
                    for name_tab in self.icons:
                        tab = Tab(title="This is " + name_tab, icon=name_tab)
                        self.root.ids.tabs.add_widget(tab)


            Example().run()

    .. tab:: Declarative python style

        .. code-block:: python

            from kivy.properties import ObjectProperty

            from kivymd.app import MDApp
            from kivymd.uix.boxlayout import MDBoxLayout
            from kivymd.uix.button import MDIconButton
            from kivymd.uix.floatlayout import MDFloatLayout
            from kivymd.uix.tab import MDTabsBase, MDTabs
            from kivymd.icon_definitions import md_icons
            from kivymd.uix.toolbar import MDTopAppBar

            colors = {
                "Teal": {
                    "200": "#212121",
                    "500": "#212121",
                    "700": "#212121",
                },
                "Red": {
                    "200": "#C25554",
                    "500": "#C25554",
                    "700": "#C25554",
                },
                "Light": {
                    "StatusBar": "E0E0E0",
                    "AppBar": "#202020",
                    "Background": "#2E3032",
                    "CardsDialogs": "#FFFFFF",
                    "FlatButtonDown": "#CCCCCC",
                },
            }


            class Tab(MDFloatLayout, MDTabsBase):
                '''Class implementing content for a tab.'''

                icon = ObjectProperty()


            class Example(MDApp):
                icons = list(md_icons.keys())[15:30]

                def build(self):
                    self.theme_cls.colors = colors
                    self.theme_cls.primary_palette = "Teal"
                    self.theme_cls.accent_palette = "Red"

                    return (
                        MDBoxLayout(
                            MDTopAppBar(title="Custom theme"),
                            MDTabs(id="tabs"),
                            orientation="vertical",
                        )
                    )

                def on_start(self):
                    for name_tab in self.icons:
                        self.root.ids.tabs.add_widget(
                            Tab(
                                MDIconButton(
                                    icon=name_tab,
                                    icon_size="48sp",
                                    theme_icon_color="Custom",
                                    icon_color="white",
                                    pos_hint={"center_x": .5, "center_y": .5},
                                ),
                                title="This is " + name_tab,
                                icon=name_tab,
                            )
                        )


            Example().run()

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

This will change the theme colors to your custom definition. In all other
respects, the theming stays as documented.

.. warning:: Please note that the key ``'Red'`` is a required key for the
    dictionary :attr:`kivymd.color_definition.colors`.
"""

from kivy.animation import Animation
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.metrics import dp
from kivy.properties import (
    AliasProperty,
    BooleanProperty,
    ColorProperty,
    DictProperty,
    NumericProperty,
    ObjectProperty,
    OptionProperty,
    StringProperty,
)
from kivy.utils import get_color_from_hex

from kivymd.color_definitions import colors, hue, palette
from kivymd.font_definitions import theme_font_styles
from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE


class ThemeManager(EventDispatcher):
    primary_palette = OptionProperty("Blue", options=palette)
    """
    The name of the color scheme that the application will use.
    All major `material` components will have the color
    of the specified color theme.

    Available options are: `'Red'`, `'Pink'`, `'Purple'`, `'DeepPurple'`,
    `'Indigo'`, `'Blue'`, `'LightBlue'`, `'Cyan'`, `'Teal'`, `'Green'`,
    `'LightGreen'`, `'Lime'`, `'Yellow'`, `'Amber'`, `'Orange'`, `'DeepOrange'`,
    `'Brown'`, `'Gray'`, `'BlueGray'`.

    To change the color scheme of an application:

    .. tabs::

        .. tab:: Imperative python style with KV

            .. code-block:: python

                from kivy.lang import Builder

                from kivymd.app import MDApp

                KV = '''
                MDScreen:

                    MDRectangleFlatButton:
                        text: "Hello, World"
                        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 = "Red"  # "Purple", "Red"

                        return Builder.load_string(KV)


                Example().run()

        .. tab:: Declarative python style

            .. code-block:: python

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


                class Example(MDApp):
                    def build(self):
                        self.theme_cls.theme_style = "Dark"
                        self.theme_cls.primary_palette = "Orange"  # "Purple", "Red"

                        return (
                            MDScreen(
                                MDRectangleFlatButton(
                                    text="Hello, World",
                                    pos_hint={"center_x": 0.5, "center_y": 0.5},
                                )
                            )
                        )


                Example().run()

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

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

    primary_hue = OptionProperty("500", options=hue)
    """
    The color hue of the application.

    Available options are: `'50'`, `'100'`, `'200'`, `'300'`, `'400'`, `'500'`,
    `'600'`, `'700'`, `'800'`, `'900'`, `'A100'`, `'A200'`, `'A400'`, `'A700'`.

    To change the hue color scheme of an application:

    .. tabs::

        .. tab:: Imperative python style with KV

            .. code-block:: python

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


                class MainApp(MDApp):
                    def build(self):
                        self.theme_cls.primary_palette = "Orange"
                        self.theme_cls.primary_hue = "200"  # "500"
                        screen = MDScreen()
                        screen.add_widget(
                            MDRectangleFlatButton(
                                text="Hello, World",
                                pos_hint={"center_x": 0.5, "center_y": 0.5},
                            )
                        )
                        return screen


                MainApp().run()

        .. tab:: Declarative python style

            .. code-block:: python

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


                class Example(MDApp):
                    def build(self):
                        self.theme_cls.primary_palette = "Orange"
                        self.theme_cls.theme_style = "Dark"
                        self.theme_cls.primary_hue = "200"  # "500"

                        return (
                            MDScreen(
                                MDRectangleFlatButton(
                                    text="Hello, World",
                                    pos_hint={"center_x": 0.5, "center_y": 0.5},
                                )
                            )
                        )


                Example().run()

    With a value of ``self.theme_cls.primary_hue = "200"`` and ``self.theme_cls.primary_hue = "500"``:

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

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

    primary_light_hue = OptionProperty("200", options=hue)
    """
    Hue value for :attr:`primary_light`.

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

    primary_dark_hue = OptionProperty("700", options=hue)
    """
    Hue value for :attr:`primary_dark`.

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

    def _get_primary_color(self) -> list:
        return get_color_from_hex(
            self.colors[self.primary_palette][self.primary_hue]
        )

    primary_color = AliasProperty(
        _get_primary_color, bind=("primary_palette", "primary_hue")
    )
    """
    The color of the current application theme.

    :attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value of the current application theme, property is readonly.
    """

    def _get_primary_light(self) -> list:
        return get_color_from_hex(
            self.colors[self.primary_palette][self.primary_light_hue]
        )

    primary_light = AliasProperty(
        _get_primary_light, bind=("primary_palette", "primary_light_hue")
    )
    """
    Colors of the current application color theme (in lighter color).

    .. tabs::

        .. tab:: Declarative style with KV

            .. code-block:: python

                from kivy.lang import Builder

                from kivymd.app import MDApp


                KV = '''
                MDScreen:

                    MDRaisedButton:
                        text: "primary_light"
                        pos_hint: {"center_x": 0.5, "center_y": 0.7}
                        md_bg_color: app.theme_cls.primary_light

                    MDRaisedButton:
                        text: "primary_color"
                        pos_hint: {"center_x": 0.5, "center_y": 0.5}

                    MDRaisedButton:
                        text: "primary_dark"
                        pos_hint: {"center_x": 0.5, "center_y": 0.3}
                        md_bg_color: app.theme_cls.primary_dark
                '''


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


                MainApp().run()

        .. tab:: Declarative python style

            .. code-block:: python

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


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

                        return (
                            MDScreen(
                                MDRaisedButton(
                                    text="Primary light",
                                    pos_hint={"center_x": 0.5, "center_y": 0.7},
                                    md_bg_color=self.theme_cls.primary_light,
                                ),
                                MDRaisedButton(
                                    text="Primary color",
                                    pos_hint={"center_x": 0.5, "center_y": 0.5},
                                ),
                                MDRaisedButton(
                                    text="Primary dark",
                                    pos_hint={"center_x": 0.5, "center_y": 0.3},
                                    md_bg_color=self.theme_cls.primary_dark,
                                ),
                            )
                        )


                Example().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png
        :align: center

    :attr:`primary_light` is an :class:`~kivy.properties.AliasProperty` that
    returns the value of the current application theme (in lighter color),
    property is readonly.
    """

    def _get_primary_dark(self) -> list:
        return get_color_from_hex(
            self.colors[self.primary_palette][self.primary_dark_hue]
        )

    primary_dark = AliasProperty(
        _get_primary_dark, bind=("primary_palette", "primary_dark_hue")
    )
    """
    Colors of the current application color theme (in darker color).

    :attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value of the current application theme (in darker color),
    property is readonly.
    """

    accent_palette = OptionProperty("Amber", options=palette)
    """
    The application color palette used for items such as the tab indicator
    in the :class:`~kivymd.uix.tab.MDTabsBar` class and so on.
    See :attr:`kivymd.uix.tab.MDTabsBar.indicator_color` attribute.

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

    accent_hue = OptionProperty("500", options=hue)
    """
    Similar to :attr:`primary_hue`, but returns a value for :attr:`accent_palette`.

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

    accent_light_hue = OptionProperty("200", options=hue)
    """
    Hue value for :attr:`accent_light`.

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

    accent_dark_hue = OptionProperty("700", options=hue)
    """
    Hue value for :attr:`accent_dark`.

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

    def _get_accent_color(self) -> list:
        return get_color_from_hex(
            self.colors[self.accent_palette][self.accent_hue]
        )

    accent_color = AliasProperty(
        _get_accent_color, bind=["accent_palette", "accent_hue"]
    )
    """
    Similar to :attr:`primary_color`, but returns a value for :attr:`accent_color`.

    :attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`accent_color`, property is
    readonly.
    """

    def _get_accent_light(self) -> list:
        return get_color_from_hex(
            self.colors[self.accent_palette][self.accent_light_hue]
        )

    accent_light = AliasProperty(
        _get_accent_light, bind=["accent_palette", "accent_light_hue"]
    )
    """
    Similar to :attr:`primary_light`, but returns a value for :attr:`accent_light`.

    :attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`accent_light`, property is
    readonly.
    """

    def _get_accent_dark(self) -> list:
        return get_color_from_hex(
            self.colors[self.accent_palette][self.accent_dark_hue]
        )

    accent_dark = AliasProperty(
        _get_accent_dark, bind=["accent_palette", "accent_dark_hue"]
    )
    """
    Similar to :attr:`primary_dark`, but returns a value for :attr:`accent_dark`.

    :attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`accent_dark`, property is
    readonly.
    """

    material_style = OptionProperty("M2", options=["M2", "M3"])
    """
    Material design style.
    Available options are: 'M2', 'M3'.

    .. versionadded:: 1.0.0

    .. seealso::

       `Material Design 2 <https://material.io/>`_ and
       `Material Design 3 <https://m3.material.io>`_


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

    theme_style_switch_animation = BooleanProperty(False)
    """
    Animate app colors when switching app color scheme ('Dark/light').

    .. versionadded:: 1.1.0

    .. tabs::

        .. tab:: Declarative KV style

            .. code-block:: python

                from kivy.lang import Builder

                from kivymd.app import MDApp

                KV = '''
                MDScreen:

                    MDCard:
                        orientation: "vertical"
                        padding: 0, 0, 0 , "36dp"
                        size_hint: .5, .5
                        pos_hint: {"center_x": .5, "center_y": .5}
                        elevation: 4
                        shadow_radius: 6
                        shadow_offset: 0, 2

                        MDLabel:
                            text: "Theme style - {}".format(app.theme_cls.theme_style)
                            halign: "center"
                            valign: "center"
                            bold: True
                            font_style: "H5"

                        MDRaisedButton:
                            text: "Set theme"
                            on_release: app.switch_theme_style()
                            pos_hint: {"center_x": .5}
                '''


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

                    def switch_theme_style(self):
                        self.theme_cls.primary_palette = (
                            "Orange" if self.theme_cls.primary_palette == "Red" else "Red"
                        )
                        self.theme_cls.theme_style = (
                            "Dark" if self.theme_cls.theme_style == "Light" else "Light"
                        )


                Example().run()

        .. tab:: Declarative python style

            .. code-block:: python

                from kivymd.app import MDApp
                from kivymd.uix.button import MDRaisedButton
                from kivymd.uix.card import MDCard
                from kivymd.uix.label import MDLabel
                from kivymd.uix.screen import MDScreen


                class Example(MDApp):
                    def build(self):
                        self.theme_cls.theme_style_switch_animation = True
                        self.theme_cls.theme_style = "Dark"
                        self.theme_cls.primary_palette = "Orange"
                        return (
                            MDScreen(
                                MDCard(
                                    MDLabel(
                                        id="label",
                                        text="Theme style - {}".format(self.theme_cls.theme_style),
                                        halign="center",
                                        valign="center",
                                        bold=True,
                                        font_style="H5",
                                    ),
                                    MDRaisedButton(
                                        text="Set theme",
                                        on_release=self.switch_theme_style,
                                        pos_hint={"center_x": 0.5},
                                    ),
                                    id="card",
                                    orientation="vertical",
                                    padding=(0, 0, 0, "36dp"),
                                    size_hint=(0.5, 0.5),
                                    pos_hint={"center_x": 0.5, "center_y": 0.5},
                                    elevation=4,
                                    shadow_radius=6,
                                    shadow_offset=(0, 2),
                                )
                            )
                        )

                    def switch_theme_style(self, *args):
                        self.theme_cls.primary_palette = (
                            "Orange" if self.theme_cls.primary_palette == "Red" else "Red"
                        )
                        self.theme_cls.theme_style = (
                            "Dark" if self.theme_cls.theme_style == "Light" else "Light"
                        )
                        self.root.ids.card.ids.label.text = (
                            "Theme style - {}".format(self.theme_cls.theme_style)
                        )


                Example().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation.gif
        :align: center

    :attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    theme_style_switch_animation_duration = NumericProperty(0.2)
    """
    Duration of the animation of switching the color scheme of the application
    ("Dark/light").

    .. versionadded:: 1.1.0

    .. code-block:: python

        class Example(MDApp):
            def build(self):
                self.theme_cls.theme_style_switch_animation = True
                self.theme_cls.theme_style_switch_animation_duration = 0.8

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation-duration.gif
        :align: center

    :attr:`theme_style_switch_animation_duration` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    theme_style = OptionProperty("Light", options=["Light", "Dark"])
    """
    App theme style.

    .. tabs::

        .. tab:: Imperative python style

            .. code-block:: python

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


                class MainApp(MDApp):
                    def build(self):
                        self.theme_cls.primary_palette = "Orange"
                        self.theme_cls.theme_style = "Dark"  # "Light"
                        screen = MDScreen()
                        screen.add_widget(
                            MDRectangleFlatButton(
                                text="Hello, World",
                                pos_hint={"center_x": 0.5, "center_y": 0.5},
                            )
                        )
                        return screen


                MainApp().run()

        .. tab:: Declarative python style

            .. code-block:: python

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


                class Example(MDApp):
                    def build(self):
                        self.theme_cls.primary_palette = "Orange"
                        self.theme_cls.theme_style = "Dark"  # "Light"

                        return (
                            MDScreen(
                                MDRectangleFlatButton(
                                    text="Hello, World",
                                    pos_hint={"center_x": 0.5, "center_y": 0.5},
                                ),
                            )
                        )


                Example().run()

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

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

    def _get_theme_style(self, opposite: bool) -> str:
        if opposite:
            return "Light" if self.theme_style == "Dark" else "Dark"
        else:
            return self.theme_style

    def _get_bg_darkest(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(self.colors["Light"]["StatusBar"])
        elif theme_style == "Dark":
            return get_color_from_hex(self.colors["Dark"]["StatusBar"])

    bg_darkest = AliasProperty(_get_bg_darkest, bind=["theme_style"])
    """
    Similar to :attr:`bg_dark`,
    but the color values are a tone lower (darker) than :attr:`bg_dark`.

    .. tabs::

        .. tab:: Declarative style with KV

            .. code-block:: python

                from kivy.lang import Builder

                from kivymd.app import MDApp

                KV = '''
                MDBoxLayout:

                    MDWidget:
                        md_bg_color: app.theme_cls.bg_light

                    MDBoxLayout:
                        md_bg_color: app.theme_cls.bg_normal

                    MDBoxLayout:
                        md_bg_color: app.theme_cls.bg_dark

                    MDBoxLayout:
                        md_bg_color: app.theme_cls.bg_darkest
                '''


                class MainApp(MDApp):
                    def build(self):
                        self.theme_cls.theme_style = "Dark"  # "Light"
                        return Builder.load_string(KV)


                MainApp().run()

        .. tab:: Declarative python style

            .. code-block:: python

                from kivymd.app import MDApp
                from kivymd.uix.boxlayout import MDBoxLayout
                from kivymd.uix.widget import MDWidget


                class Example(MDApp):
                    def build(self):
                        self.theme_cls.theme_style = "Dark"  # "Light"

                        return (
                            MDBoxLayout(
                                MDWidget(
                                    md_bg_color=self.theme_cls.bg_light,
                                ),
                                MDWidget(
                                    md_bg_color=self.theme_cls.bg_normal,
                                ),
                                MDWidget(
                                    md_bg_color=self.theme_cls.bg_dark,
                                ),
                                MDWidget(
                                    md_bg_color=self.theme_cls.bg_darkest,
                                ),
                            )
                        )


                Example().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png
        :align: center

    :attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_darkest`,
    property is readonly.
    """

    def _get_op_bg_darkest(self) -> list:
        return self._get_bg_darkest(True)

    opposite_bg_darkest = AliasProperty(
        _get_op_bg_darkest, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`bg_darkest`.

    :attr:`opposite_bg_darkest` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_bg_darkest`,
    property is readonly.
    """

    def _get_bg_dark(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(self.colors["Light"]["AppBar"])
        elif theme_style == "Dark":
            return get_color_from_hex(self.colors["Dark"]["AppBar"])

    bg_dark = AliasProperty(_get_bg_dark, bind=["theme_style"])
    """
    Similar to :attr:`bg_normal`,
    but the color values are one tone lower (darker) than :attr:`bg_normal`.

    :attr:`bg_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_dark`,
    property is readonly.
    """

    def _get_op_bg_dark(self) -> list:
        return self._get_bg_dark(True)

    opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_dark`.

    :attr:`opposite_bg_dark` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`opposite_bg_dark`,
    property is readonly.
    """

    def _get_bg_normal(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(self.colors["Light"]["Background"])
        elif theme_style == "Dark":
            return get_color_from_hex(self.colors["Dark"]["Background"])

    bg_normal = AliasProperty(_get_bg_normal, bind=["theme_style"])
    """
    Similar to :attr:`bg_light`,
    but the color values are one tone lower (darker) than :attr:`bg_light`.

    :attr:`bg_normal` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_normal`,
    property is readonly.
    """

    def _get_op_bg_normal(self) -> list:
        return self._get_bg_normal(True)

    opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_normal`.

    :attr:`opposite_bg_normal` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_bg_normal`,
    property is readonly.
    """

    def _get_bg_light(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            return get_color_from_hex(self.colors["Light"]["CardsDialogs"])
        elif theme_style == "Dark":
            return get_color_from_hex(self.colors["Dark"]["CardsDialogs"])

    bg_light = AliasProperty(_get_bg_light, bind=["theme_style"])
    """"
    Depending on the style of the theme (`'Dark'` or `'Light`')
    that the application uses, :attr:`bg_light` contains the color value
    in ``rgba`` format for the widgets background.

    :attr:`bg_light` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`bg_light`,
    property is readonly.
    """

    def _get_op_bg_light(self) -> list:
        return self._get_bg_light(True)

    opposite_bg_light = AliasProperty(_get_op_bg_light, bind=["theme_style"])
    """
    The opposite value of color in the :attr:`bg_light`.

    :attr:`opposite_bg_light` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_bg_light`,
    property is readonly.
    """

    def _get_divider_color(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
        color[3] = 0.12
        return color

    divider_color = AliasProperty(_get_divider_color, bind=["theme_style"])
    """
    Color for dividing lines such as  :class:`~kivymd.uix.card.MDSeparator`.

    :attr:`divider_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`divider_color`,
    property is readonly.
    """

    def _get_op_divider_color(self) -> list:
        return self._get_divider_color(True)

    opposite_divider_color = AliasProperty(
        _get_op_divider_color, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`divider_color`.

    :attr:`opposite_divider_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_divider_color`,
    property is readonly.
    """

    def _get_disabled_primary_color(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        lum = sum(self.primary_color[0:3]) / 3.0
        if theme_style == "Light":
            a = 0.38
        elif theme_style == "Dark":
            a = 0.50
        return [lum, lum, lum, a]

    disabled_primary_color = AliasProperty(
        _get_disabled_primary_color, bind=["theme_style"]
    )
    """
    The greyscale disabled version of the current application theme color
    in ``rgba`` format.

    .. versionadded:: 1.0.0

    :attr:`disabled_primary_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`disabled_primary_color`,
    property is readonly.
    """

    def _get_op_disabled_primary_color(self) -> list:
        return self._get_disabled_primary_color(True)

    opposite_disabled_primary_color = AliasProperty(
        _get_op_disabled_primary_color, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`disabled_primary_color`.

    .. versionadded:: 1.0.0

    :attr:`opposite_disabled_primary_color` is an
    :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`opposite_disabled_primary_color`,
    property is readonly.
    """

    def _get_text_color(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.87
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
        return color

    text_color = AliasProperty(_get_text_color, bind=["theme_style"])
    """
    Color of the text used in the :class:`~kivymd.uix.label.MDLabel`.

    :attr:`text_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`text_color`,
    property is readonly.
    """

    def _get_op_text_color(self) -> list:
        return self._get_text_color(True)

    opposite_text_color = AliasProperty(
        _get_op_text_color, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`text_color`.

    :attr:`opposite_text_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_text_color`,
    property is readonly.
    """

    def _get_secondary_text_color(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.54
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
            color[3] = 0.70
        return color

    secondary_text_color = AliasProperty(
        _get_secondary_text_color, bind=["theme_style"]
    )
    """
    The color for the secondary text that is used in classes
    from the module :class:`~kivymd/uix/list.TwoLineListItem`.

    :attr:`secondary_text_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`secondary_text_color`,
    property is readonly.
    """

    def _get_op_secondary_text_color(self) -> list:
        return self._get_secondary_text_color(True)

    opposite_secondary_text_color = AliasProperty(
        _get_op_secondary_text_color, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`secondary_text_color`.

    :attr:`opposite_secondary_text_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`opposite_secondary_text_color`,
    property is readonly.
    """

    def _get_icon_color(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.54
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
        return color

    icon_color = AliasProperty(_get_icon_color, bind=["theme_style"])
    """
    Color of the icon used in the :class:`~kivymd.uix.button.MDIconButton`.

    :attr:`icon_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`icon_color`,
    property is readonly.
    """

    def _get_op_icon_color(self) -> list:
        return self._get_icon_color(True)

    opposite_icon_color = AliasProperty(
        _get_op_icon_color, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`icon_color`.

    :attr:`opposite_icon_color` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`opposite_icon_color`,
    property is readonly.
    """

    def _get_disabled_hint_text_color(self, opposite: bool = False) -> list:
        theme_style = self._get_theme_style(opposite)
        if theme_style == "Light":
            color = get_color_from_hex("000000")
            color[3] = 0.38
        elif theme_style == "Dark":
            color = get_color_from_hex("FFFFFF")
            color[3] = 0.50
        return color

    disabled_hint_text_color = AliasProperty(
        _get_disabled_hint_text_color, bind=["theme_style"]
    )
    """
    Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`.

    :attr:`disabled_hint_text_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`disabled_hint_text_color`,
    property is readonly.
    """

    def _get_op_disabled_hint_text_color(self) -> list:
        return self._get_disabled_hint_text_color(True)

    opposite_disabled_hint_text_color = AliasProperty(
        _get_op_disabled_hint_text_color, bind=["theme_style"]
    )
    """
    The opposite value of color in the :attr:`disabled_hint_text_color`.

    :attr:`opposite_disabled_hint_text_color`
    is an :class:`~kivy.properties.AliasProperty` that returns the value
    in ``rgba`` format for :attr:`opposite_disabled_hint_text_color`,
    property is readonly.
    """

    # Hardcoded because muh standard
    def _get_error_color(self) -> list:
        return get_color_from_hex(self.colors["Red"]["A700"])

    error_color = AliasProperty(_get_error_color, bind=["theme_style"])
    """
    Color of the error text used
    in the :class:`~kivymd.uix.textfield.MDTextField`.

    :attr:`error_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`error_color`,
    property is readonly.
    """

    def _get_ripple_color(self) -> list:
        return self._ripple_color

    def _set_ripple_color(self, value) -> None:
        self._ripple_color = value

    _ripple_color = ColorProperty(colors["Gray"]["400"])
    """Private value."""

    ripple_color = AliasProperty(
        _get_ripple_color, _set_ripple_color, bind=["_ripple_color"]
    )
    """
    Color of ripple effects.

    :attr:`ripple_color` is an :class:`~kivy.properties.AliasProperty` that
    returns the value in ``rgba`` format for :attr:`ripple_color`,
    property is readonly.
    """

    def _determine_device_orientation(self, _, window_size) -> None:
        if window_size[0] > window_size[1]:
            self.device_orientation = "landscape"
        elif window_size[1] >= window_size[0]:
            self.device_orientation = "portrait"

    device_orientation = StringProperty("")
    """
    Device orientation.

    :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`.
    """

    def _get_standard_increment(self) -> float:
        if DEVICE_TYPE == "mobile":
            if self.device_orientation == "landscape":
                return dp(48)
            else:
                return dp(56)
        else:
            return dp(64)

    standard_increment = AliasProperty(
        _get_standard_increment, bind=["device_orientation"]
    )
    """
    Value of standard increment.

    :attr:`standard_increment` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`standard_increment`,
    property is readonly.
    """

    def _get_horizontal_margins(self) -> float:
        if DEVICE_TYPE == "mobile":
            return dp(16)
        else:
            return dp(24)

    horizontal_margins = AliasProperty(_get_horizontal_margins)
    """
    Value of horizontal margins.

    :attr:`horizontal_margins` is an :class:`~kivy.properties.AliasProperty`
    that returns the value in ``rgba`` format for :attr:`horizontal_margins`,
    property is readonly.
    """

    def on_theme_style(self, interval: int, theme_style: str) -> None:
        if (
            hasattr(App.get_running_app(), "theme_cls")
            and App.get_running_app().theme_cls == self
        ):
            self.set_clearcolor_by_theme_style(theme_style)

    _set_clearcolor = False

    def set_clearcolor_by_theme_style(self, theme_style):
        if self.theme_style_switch_animation and self._set_clearcolor:
            Animation(
                clearcolor=get_color_from_hex(
                    self.colors[theme_style]["Background"]
                ),
                d=self.theme_style_switch_animation_duration,
                t="linear",
            ).start(Window)
        else:
            Window.clearcolor = get_color_from_hex(
                self.colors[theme_style]["Background"]
            )
            self._set_clearcolor = True

    # Font name, size (sp), always caps, letter spacing (sp).
    font_styles = DictProperty(
        {
            "H1": ["RobotoLight", 96, False, -1.5],
            "H2": ["RobotoLight", 60, False, -0.5],
            "H3": ["Roboto", 48, False, 0],
            "H4": ["Roboto", 34, False, 0.25],
            "H5": ["Roboto", 24, False, 0],
            "H6": ["RobotoMedium", 20, False, 0.15],
            "Subtitle1": ["Roboto", 16, False, 0.15],
            "Subtitle2": ["RobotoMedium", 14, False, 0.1],
            "Body1": ["Roboto", 16, False, 0.5],
            "Body2": ["Roboto", 14, False, 0.25],
            "Button": ["RobotoMedium", 14, True, 1.25],
            "Caption": ["Roboto", 12, False, 0.4],
            "Overline": ["Roboto", 10, True, 1.5],
            "Icon": ["Icons", 24, False, 0],
        }
    )
    """
    Data of default font styles.

    Add custom font
    ---------------

    .. tabs::

        .. tab:: Declarative style with KV

            .. code-block:: python

                from kivy.core.text import LabelBase
                from kivy.lang import Builder

                from kivymd.app import MDApp
                from kivymd.font_definitions import theme_font_styles

                KV = '''
                MDScreen:

                    MDLabel:
                        text: "JetBrainsMono"
                        halign: "center"
                        font_style: "JetBrainsMono"
                '''


                class MainApp(MDApp):
                    def build(self):
                        self.theme_cls.theme_style = "Dark"

                        LabelBase.register(
                            name="JetBrainsMono",
                            fn_regular="JetBrainsMono-Regular.ttf")

                        theme_font_styles.append('JetBrainsMono')
                        self.theme_cls.font_styles["JetBrainsMono"] = [
                            "JetBrainsMono",
                            16,
                            False,
                            0.15,
                        ]
                        return Builder.load_string(KV)


                MainApp().run()

        .. tab:: Declarative python style

            .. code-block:: python

                from kivy.core.text import LabelBase

                from kivymd.app import MDApp
                from kivymd.uix.screen import MDScreen
                from kivymd.uix.label import MDLabel
                from kivymd.font_definitions import theme_font_styles


                class MainApp(MDApp):
                    def build(self):
                        self.theme_cls.theme_style = "Dark"

                        LabelBase.register(
                            name="JetBrainsMono",
                            fn_regular="JetBrainsMono-Regular.ttf")

                        theme_font_styles.append('JetBrainsMono')
                        self.theme_cls.font_styles["JetBrainsMono"] = [
                            "JetBrainsMono",
                            16,
                            False,
                            0.15,
                        ]
                        return (
                            MDScreen(
                                MDLabel(
                                    text="JetBrainsMono",
                                    halign="center",
                                    font_style="JetBrainsMono",
                                )
                            )
                        )


                MainApp().run()

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

    :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
    """

    def set_colors(
        self,
        primary_palette: str,
        primary_hue: str,
        primary_light_hue: str,
        primary_dark_hue: str,
        accent_palette: str,
        accent_hue: str,
        accent_light_hue: str,
        accent_dark_hue: str,
    ) -> None:
        """
        Courtesy method to allow all of the theme color attributes to be set in one call.

        :attr:`set_colors` allows all of the following to be set in one method call:

        * primary palette color,
        * primary hue,
        * primary light hue,
        * primary dark hue,
        * accent palette color,
        * accent hue,
        * accent ligth hue, and
        * accent dark hue.

        Note that all values *must* be provided. If you only want to set one or two values
        use the appropriate method call for that.

        .. tabs::

            .. tab:: Imperative python style

                .. code-block:: python

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

                    class MainApp(MDApp):
                        def build(self):
                            self.theme_cls.set_colors(
                                "Blue", "600", "50", "800", "Teal", "600", "100", "800"
                            )
                            screen = MDScreen()
                            screen.add_widget(
                                MDRectangleFlatButton(
                                    text="Hello, World",
                                    pos_hint={"center_x": 0.5, "center_y": 0.5},
                                )
                            )
                            return screen


                    MainApp().run()

            .. tab:: Declarative python style

                .. code-block:: python

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

                    class MainApp(MDApp):
                        def build(self):
                            self.theme_cls.set_colors(
                                "Blue", "600", "50", "800", "Teal", "600", "100", "800"
                            )
                            return (
                                MDScreen(
                                    MDRectangleFlatButton(
                                        text="Hello, World",
                                        pos_hint={"center_x": 0.5, "center_y": 0.5},
                                    )
                                )
                            )


                    MainApp().run()
        """

        self.primary_palette = primary_palette
        self.primary_hue = primary_hue
        self.primary_light_hue = primary_light_hue
        self.primary_dark_hue = primary_dark_hue
        self.accent_palette = accent_palette
        self.accent_hue = accent_hue
        self.accent_light_hue = accent_light_hue
        self.accent_dark_hue = accent_dark_hue

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style))
        self._determine_device_orientation(None, Window.size)
        Window.bind(size=self._determine_device_orientation)
        self.bind(font_styles=self.sync_theme_styles)
        self.colors = colors
        Clock.schedule_once(self.sync_theme_styles)

    def sync_theme_styles(self, *args) -> None:
        # Syncs the values from self.font_styles to theme_font_styles
        # this will ensure continuity when someone registers a new font_style.
        for num, style in enumerate(theme_font_styles):
            if style not in self.font_styles:
                theme_font_styles.pop(num)
        for style in self.font_styles.keys():
            theme_font_styles.append(style)


class ThemableBehavior(EventDispatcher):
    theme_cls = ObjectProperty()
    """
    Instance of :class:`~ThemeManager` class.

    :attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
    """

    device_ios = BooleanProperty(DEVICE_IOS)
    """
    ``True`` if device is ``iOS``.

    :attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`.
    """

    widget_style = OptionProperty(
        "android", options=["android", "ios", "desktop"]
    )
    """
    Allows to set one of the three style properties for the widget:
    `'android'`, `'ios'`, `'desktop'`.

    For example, for the class :class:`~kivymd.uix.selectioncontrol.MDSwitch`
    has two styles - `'android'` and `'ios'`:

    .. code-block:: kv

        MDSwitch:
            widget_style: "ios"

    .. code-block:: kv

        MDSwitch:
            widget_style: "android"

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

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

    opposite_colors = BooleanProperty(False)
    """
    For some widgets, for example, for a widget
    :class:`~kivymd.uix.toolbar.MDTopAppBar` changes the color of the label to
    the color opposite to the main theme.

    .. code-block:: kv

        MDTopAppBar:
            title: "MDTopAppBar"
            opposite_colors: True

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

    .. code-block:: kv

        MDTopAppBar:
            title: "MDTopAppBar"
            opposite_colors: True

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-false.png
        :align: center
    """

    def __init__(self, **kwargs):
        self.unbind_properties = [
            "theme_style",
            "material_style",
            "device_orientation",
            "primary_color",
            "primary_palette",
            "accent_palette",
            "text_color",
        ]

        if self.theme_cls is not None:
            pass
        else:
            try:
                if not isinstance(
                    App.get_running_app().property("theme_cls", True),
                    ObjectProperty,
                ):
                    raise ValueError(
                        "KivyMD: App object must be inherited from "
                        "`kivymd.app.MDApp`"
                    )
            except AttributeError:
                raise ValueError(
                    "KivyMD: App object must be initialized before loading "
                    "root widget. See "
                    "https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions"
                )
            self.theme_cls = App.get_running_app().theme_cls
        super().__init__(**kwargs)

        # Fix circular imports.
        from kivymd.uix.behaviors import CommonElevationBehavior

        self.common_elevation_behavior = CommonElevationBehavior

    def dec_disabled(self, *args, **kwargs) -> None:
        callabacks = self.theme_cls.get_property_observers("theme_style")

        for callaback in callabacks:
            try:
                if hasattr(callaback, "proxy") and hasattr(
                    callaback.proxy, "theme_cls"
                ):
                    for property_name in self.unbind_properties:
                        self.theme_cls.unbind(
                            **{
                                property_name: getattr(
                                    callaback.proxy, callaback.method_name
                                )
                            }
                        )
            except ReferenceError:
                pass

        # Canceling the schedule of calling the on_pos method for objects
        # that inherited the elevation behavior.
        if issubclass(self.__class__, self.common_elevation_behavior):
            Window.unbind(on_draw=self.on_pos)

        super().dec_disabled(*args, **kwargs)
