"""
Components/Text Field
=====================

.. seealso::

    `Material Design spec, Text fields <https://material.io/components/text-fields>`_

.. rubric:: Text fields let users enter and edit text.

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

`KivyMD` provides the following field classes for use:

- MDTextField_
- MDTextFieldRound_
- MDTextFieldRect_

.. Note:: :class:`~MDTextField` inherited from
    :class:`~kivy.uix.textinput.TextInput`. Therefore, most parameters and all
    events of the :class:`~kivy.uix.textinput.TextInput` class are also
    available in the :class:`~MDTextField` class.

.. MDTextField:
MDTextField
-----------


:class:`~MDTextField` can be with helper text and without.

Without helper text mode
------------------------

.. code-block:: kv

    MDTextField:
        hint_text: "No helper text"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-no-helper-mode.gif
    :align: center

Helper text mode on ``on_focus`` event
--------------------------------------

.. code-block:: kv

    MDTextField:
        hint_text: "Helper text on focus"
        helper_text: "This will disappear when you click off"
        helper_text_mode: "on_focus"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-on-focus.gif
    :align: center

Persistent helper text mode
---------------------------

.. code-block:: kv

    MDTextField:
        hint_text: "Persistent helper text"
        helper_text: "Text is always here"
        helper_text_mode: "persistent"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-persistent.gif
    :align: center

Helper text mode `'on_error'`
-----------------------------

To display an error in a text field when using the
``helper_text_mode: "on_error"`` parameter, set the `"error"` text field
parameter to `True`:

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp

    KV = '''
    BoxLayout:
        padding: "10dp"

        MDTextField:
            id: text_field_error
            hint_text: "Helper text on error (press 'Enter')"
            helper_text: "There will always be a mistake"
            helper_text_mode: "on_error"
            pos_hint: {"center_y": .5}
    '''


    class Test(MDApp):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.screen = Builder.load_string(KV)

        def build(self):
            self.screen.ids.text_field_error.bind(
                on_text_validate=self.set_error_message,
                on_focus=self.set_error_message,
            )
            return self.screen

        def set_error_message(self, instance_textfield):
            self.screen.ids.text_field_error.error = True


    Test().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-on-error.gif
    :align: center

Helper text mode `'on_error'` (with required)
---------------------------------------------

.. code-block:: kv

    MDTextField:
        hint_text: "required = True"
        required: True
        helper_text_mode: "on_error"
        helper_text: "Enter text"

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

Text length control
-------------------

.. code-block:: kv

    MDTextField:
        hint_text: "Max text length = 5"
        max_text_length: 5

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


Multi line text
---------------

.. code-block:: kv

    MDTextField:
        multiline: True
        hint_text: "Multi-line text"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-multi-line.gif
    :align: center

Color mode
----------

.. code-block:: kv

    MDTextField:
        hint_text: "color_mode = 'accent'"
        color_mode: 'accent'

Available options are  `'primary'`, `'accent'` or `'custom`'.

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-color-mode.gif
    :align: center

.. code-block:: kv

    MDTextField:
        hint_text: "color_mode = 'custom'"
        color_mode: 'custom'
        helper_text_mode: "on_focus"
        helper_text: "Color is defined by 'line_color_focus' property"
        line_color_focus: 1, 0, 1, 1

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-color-mode-custom.gif
    :align: center

.. code-block:: kv

    MDTextField:
        hint_text: "Line color normal"
        line_color_normal: app.theme_cls.accent_color

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

Rectangle mode
--------------

.. code-block:: kv

    MDTextField:
        hint_text: "Rectangle mode"
        mode: "rectangle"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rectangle-mode.gif
    :align: center

Fill mode
---------

.. code-block:: kv

    MDTextField:
        hint_text: "Fill mode"
        mode: "fill"
        fill_color: 0, 0, 0, .4

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode.gif
    :align: center

Maximum height
--------------

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp

    KV = '''
    MDScreen

        MDTextField:
            size_hint_x: .5
            hint_text: "multiline=True"
            max_height: "200dp"
            mode: "fill"
            fill_color: 0, 0, 0, .4
            multiline: True
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


    class Example(MDApp):
        def build(self):
            return Builder.load_string(KV)


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode-multiline-max-height.gif
    :align: center

.. MDTextFieldRect:
MDTextFieldRect
---------------

.. Note:: :class:`~MDTextFieldRect` inherited from
    :class:`~kivy.uix.textinput.TextInput`. You can use all parameters and
    attributes of the :class:`~kivy.uix.textinput.TextInput` class in the
    :class:`~MDTextFieldRect` class.

.. code-block:: kv

    MDTextFieldRect:
        size_hint: 1, None
        height: "30dp"

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

.. Warning:: While there is no way to change the color of the border.

.. MDTextFieldRound:
MDTextFieldRound
----------------

Without icon
------------

.. code-block:: kv

    MDTextFieldRound:
        hint_text: 'Empty field'

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

With left icon
--------------

.. Warning:: The icons in the :class:`~MDTextFieldRound` are static. You cannot
    bind events to them.

.. code-block:: kv

    MDTextFieldRound:
        icon_left: "email"
        hint_text: "Field with left icon"

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-left-icon.png
    :align: center

With left and right icons
-------------------------

.. code-block:: kv

    MDTextFieldRound:
        icon_left: 'key-variant'
        icon_right: 'eye-off'
        hint_text: 'Field with left and right icons'

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-left-right-icon.png
    :align: center

Control background color
------------------------

.. code-block:: kv

    MDTextFieldRound:
        icon_left: 'key-variant'
        normal_color: app.theme_cls.accent_color

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-normal-color.gif
    :align: center

.. code-block:: kv

    MDTextFieldRound:
        icon_left: 'key-variant'
        normal_color: app.theme_cls.accent_color
        color_active: 1, 0, 0, 1

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-active-color.gif
    :align: center

Clickable icon for MDTextFieldRound
-----------------------------------

.. code-block:: python

    from kivy.lang import Builder
    from kivy.properties import StringProperty

    from kivymd.app import MDApp
    from kivymd.uix.relativelayout import MDRelativeLayout

    KV = '''
    <ClickableTextFieldRound>:
        size_hint_y: None
        height: text_field.height

        MDTextFieldRound:
            id: text_field
            hint_text: root.hint_text
            text: root.text
            password: True
            color_active: app.theme_cls.primary_light
            icon_left: "key-variant"
            padding:
                self._lbl_icon_left.texture_size[1] + dp(10) if self.icon_left else dp(15), \
                (self.height / 2) - (self.line_height / 2), \
                self._lbl_icon_right.texture_size[1] + dp(20), \
                0

        MDIconButton:
            icon: "eye-off"
            ripple_scale: .5
            pos_hint: {"center_y": .5}
            pos: text_field.width - self.width + dp(8), 0
            on_release:
                self.icon = "eye" if self.icon == "eye-off" else "eye-off"
                text_field.password = False if text_field.password is True else True


    MDScreen:

        ClickableTextFieldRound:
            size_hint_x: None
            width: "300dp"
            hint_text: "Password"
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


    class ClickableTextFieldRound(MDRelativeLayout):
        text = StringProperty()
        hint_text = StringProperty()
        # Here specify the required parameters for MDTextFieldRound:
        # [...]


    class Test(MDApp):
        def build(self):
            return Builder.load_string(KV)


    Test().run()

With right icon
---------------

.. Note:: The icon on the right is available for use in all text fields.

.. code-block:: kv

    MDTextField:
        hint_text: "Name"
        mode: "fill"
        fill_color: 0, 0, 0, .4
        icon_right: "arrow-down-drop-circle-outline"
        icon_right_color: app.theme_cls.primary_color

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode-icon.png
    :align: center

.. code-block:: kv

    MDTextField:
        hint_text: "Name"
        icon_right: "arrow-down-drop-circle-outline"
        icon_right_color: app.theme_cls.primary_color

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

.. code-block:: kv

    MDTextField:
        hint_text: "Name"
        mode: "rectangle"
        icon_right: "arrow-down-drop-circle-outline"
        icon_right_color: app.theme_cls.primary_color

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rectangle-right-icon.png
    :align: center

.. seealso::

    See more information in the :class:`~MDTextFieldRect` class.
"""

__all__ = ("MDTextField", "MDTextFieldRect", "MDTextFieldRound")

import re
import sys

from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp, sp
from kivy.properties import (
    AliasProperty,
    BooleanProperty,
    ColorProperty,
    ListProperty,
    NumericProperty,
    ObjectProperty,
    OptionProperty,
    StringProperty,
)
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput

from kivymd.font_definitions import theme_font_styles
from kivymd.theming import ThemableBehavior
from kivymd.uix.label import MDIcon

Builder.load_string(
    """
#:import images_path kivymd.images_path


<MDTextField>

    canvas.before:
        Clear

        # Disabled line.
        Color:
            rgba:
                (self.line_color_normal \
                if self.line_color_normal else self.theme_cls.divider_color) \
                if root.mode == "line" else (0, 0, 0, 0)
        Line:
            points: self.x, self.y + dp(16), self.x + self.width, self.y + dp(16)
            width: 1
            dash_length: dp(3)
            dash_offset: 2 if self.disabled else 0

        # Active line.
        Color:
            rgba: self._current_line_color if root.mode in ("line", "fill") and root.active_line else (0, 0, 0, 0)
        Rectangle:
            size: self._line_width, dp(2)
            pos: self.center_x - (self._line_width / 2), self.y + (dp(16) if root.mode != "fill" else 0)

        # Helper text.
        Color:
            rgba: self._current_error_color
        Rectangle:
            texture: self._msg_lbl.texture
            size:
                self._msg_lbl.texture_size[0] - (dp(3) if root.mode in ("fill", "rectangle") else 0), \
                self._msg_lbl.texture_size[1] - (dp(3) if root.mode in ("fill", "rectangle") else 0)
            pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + (dp(3) if root.mode in ("fill", "rectangle") else 0)

        # Texture of right Icon.
        Color:
            rgba: self.icon_right_color if self.focus else self._current_hint_text_color
        Rectangle:
            texture: self._lbl_icon_right.texture
            size: self._lbl_icon_right.texture_size if self.icon_right else (0, 0)
            pos:
                (self.width + self.x) - (self._lbl_icon_right.texture_size[1]) - dp(8), \
                self.center[1] - self._lbl_icon_right.texture_size[1] / 2 + (dp(8) if root.mode != "fill" else 0) \
                if root.mode != "rectangle" else \
                self.center[1] - self._lbl_icon_right.texture_size[1] / 2 - dp(4)

        Color:
            rgba: self._current_right_lbl_color
        Rectangle:
            texture: self._right_msg_lbl.texture
            size: self._right_msg_lbl.texture_size
            pos: self.x + self.width - self._right_msg_lbl.texture_size[0] - dp(16), self.y

        Color:
            rgba:
                (self._current_line_color if self.focus and not \
                self._cursor_blink else (0, 0, 0, 0))
        Rectangle:
            pos: (int(x) for x in self.cursor_pos)
            size: 1, -self.line_height

        # Hint text.
        Color:
            rgba: self._current_hint_text_color if not self.current_hint_text_color else self.current_hint_text_color
        Rectangle:
            texture: self._hint_lbl.texture
            size: self._hint_lbl.texture_size
            pos: self.x + (dp(8) if root.mode == "fill" else 0), self.y + self.height - self._hint_y

        Color:
            rgba:
                self.disabled_foreground_color if self.disabled else\
                (self.hint_text_color if not self.text and not\
                self.focus else self.foreground_color)

        # "rectangle" mode
        Color:
            rgba:
                (self._current_line_color if not self.text_color else self.text_color) \
                if self.focus else self._current_hint_text_color
        Line:
            width: dp(1) if root.mode == "rectangle" else dp(0.00001)
            points:
                (
                self.x + root._line_blank_space_right_point, self.top - self._hint_lbl.texture_size[1] // 2,
                self.right + dp(12), self.top - self._hint_lbl.texture_size[1] // 2,
                self.right + dp(12), self.y,
                self.x - dp(12), self.y,
                self.x - dp(12), self.top - self._hint_lbl.texture_size[1] // 2,
                self.x + root._line_blank_space_left_point, self.top - self._hint_lbl.texture_size[1] // 2
                )

    # "fill" mode.
    canvas.after:
        Color:
            rgba: root._fill_color if root.mode == "fill" else (0, 0, 0, 0)
        RoundedRectangle:
            pos: self.x, self.y
            size: self.width, self.height + dp(8)
            radius: root.radius

    font_name: "Roboto" if not root.font_name else root.font_name
    foreground_color: self.theme_cls.text_color
    bold: False
    padding:
        0 if root.mode != "fill" else "8dp", \
        "16dp" if root.mode != "fill" else "24dp", \
        0 if root.mode != "fill" and not root.icon_right else ("14dp" if not root.icon_right else self._lbl_icon_right.texture_size[1] + dp(20)), \
        "16dp" if root.mode == "fill" else "10dp"
    multiline: False
    size_hint_y: None
    height: self.minimum_height + (dp(8) if root.mode != "fill" else 0)


<TextfieldLabel>
    size_hint_x: None
    width: self.texture_size[0]
    shorten: True
    shorten_from: "right"


<MDTextFieldRect>
    on_focus:
        root.anim_rect((root.x, root.y, root.right, root.y, root.right, \
        root.top, root.x, root.top, root.x, root.y), 1) if root.focus \
        else root.anim_rect((root.x - dp(60), root.y - dp(60), \
        root.right + dp(60), root.y - dp(60),
        root.right + dp(60), root.top + dp(60), \
        root.x - dp(60), root.top + dp(60), \
        root.x - dp(60), root.y - dp(60)), 0)

    canvas.after:
        Color:
            group: "color"
            rgba: root._primary_color
        Line:
            group: "rectangle"
            width: dp(1.5)
            points:
                (
                self.x - dp(60), self.y - dp(60),
                self.right + dp(60), self.y - dp(60),
                self.right + dp(60), self.top + dp(60),
                self.x - dp(60), self.top + dp(60),
                self.x - dp(60), self.y - dp(60)
                )


<MDTextFieldRound>:
    multiline: False
    size_hint: 1, None
    height: self.line_height + dp(10)
    background_active: f'{images_path}transparent.png'
    background_normal: f'{images_path}transparent.png'
    hint_text_color: self.theme_cls.disabled_hint_text_color
    padding:
        self._lbl_icon_left.texture_size[1] + dp(10) if self.icon_left else dp(15), \
        (self.height / 2) - (self.line_height / 2), \
        self._lbl_icon_right.texture_size[1] + dp(20) if self.icon_right else dp(15), \
        0

    canvas.before:
        Color:
            rgba: self.normal_color if not self.focus else self._color_active
        Ellipse:
            angle_start: 180
            angle_end: 360
            pos: self.pos[0] - self.size[1] / 2, self.pos[1]
            size: self.size[1], self.size[1]
        Ellipse:
            angle_start: 360
            angle_end: 540
            pos: self.size[0] + self.pos[0] - self.size[1]/2.0, self.pos[1]
            size: self.size[1], self.size[1]
        Rectangle:
            pos: self.pos
            size: self.size

        # Texture of left Icon.
        Color:
            rgba:
                self.icon_left_color \
                if self.focus else self.theme_cls.disabled_hint_text_color
        Rectangle:
            texture: self._lbl_icon_left.texture
            size:
                self._lbl_icon_left.texture_size if self.icon_left \
                else (0, 0)
            pos:
                self.x, \
                self.center[1] - self._lbl_icon_right.texture_size[1] / 2

        # Texture of right Icon.
        Color:
            rgba:
                self.icon_right_color \
                if self.focus else self.theme_cls.disabled_hint_text_color
        Rectangle:
            texture: self._lbl_icon_right.texture
            size:
                self._lbl_icon_right.texture_size if self.icon_right \
                else (0, 0)
            pos:
                (self.width + self.x) - (self._lbl_icon_right.texture_size[1]), \
                self.center[1] - self._lbl_icon_right.texture_size[1] / 2

        Color:
            rgba:
                self.hint_text_color if not self.text else root.foreground_color

    canvas.after:
        Color:
            rgba: self.line_color if self.focus else self.theme_cls.disabled_hint_text_color
        Line:
            points: self.pos[0] , self.pos[1], self.pos[0] + self.size[0], self.pos[1]
        Line:
            points: self.pos[0], self.pos[1] + self.size[1], self.pos[0] + self.size[0], self.pos[1] + self.size[1]
        Line:
            ellipse: self.pos[0] - self.size[1] / 2, self.pos[1], self.size[1], self.size[1], 180, 360
        Line:
            ellipse: self.size[0] + self.pos[0] - self.size[1] / 2.0, self.pos[1], self.size[1], self.size[1], 360, 540
"""
)


class MDTextFieldRect(ThemableBehavior, TextInput):
    line_anim = BooleanProperty(True)
    """
    If True, then text field shows animated line when on focus.

    :attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    def get_rect_instruction(self):
        canvas_instructions = self.canvas.after.get_group("rectangle")
        return canvas_instructions[0]

    _rectangle = AliasProperty(get_rect_instruction, cache=True)
    """
    It is the :class:`~kivy.graphics.vertex_instructions.Line`
    instruction reference of the field rectangle.

    :attr:`_rectangle` is an :class:`~kivy.properties.AliasProperty`.
    """

    def get_color_instruction(self):
        canvas_instructions = self.canvas.after.get_group("color")
        return canvas_instructions[0]

    _rectangle_color = AliasProperty(get_color_instruction, cache=True)
    """
    It is the :class:`~kivy.graphics.context_instructions.Color`
    instruction reference of the field rectangle.

    :attr:`_rectangle_color` is an :class:`~kivy.properties.AliasProperty`.
    """

    _primary_color = ColorProperty((0, 0, 0, 0))

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._update_primary_color()
        self.theme_cls.bind(primary_color=self._update_primary_color)

    def anim_rect(self, points, alpha):
        if alpha == 1:
            d_line = 0.3
            d_color = 0.4
        else:
            d_line = 0.05
            d_color = 0.05

        Animation(
            points=points, d=(d_line if self.line_anim else 0), t="out_cubic"
        ).start(self._rectangle)
        Animation(a=alpha, d=(d_color if self.line_anim else 0)).start(
            self._rectangle_color
        )

    def _update_primary_color(self, *args):
        self._primary_color = self.theme_cls.primary_color
        self._primary_color[3] = 0


class TextfieldLabel(ThemableBehavior, Label):
    font_style = OptionProperty("Body1", options=theme_font_styles)
    # <kivymd.uix.textfield.MDTextField object>
    field = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.font_size = sp(self.theme_cls.font_styles[self.font_style][1])


class MDTextField(ThemableBehavior, TextInput):
    helper_text = StringProperty("This field is required")
    """
    Text for ``helper_text`` mode.

    :attr:`helper_text` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'This field is required'`.
    """

    helper_text_mode = OptionProperty(
        "none", options=["none", "on_error", "persistent", "on_focus"]
    )
    """
    Helper text mode. Available options are: `'on_error'`, `'persistent'`,
    `'on_focus'`.

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

    max_text_length = NumericProperty(None)
    """
    Maximum allowed value of characters in a text field.

    :attr:`max_text_length` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `None`.
    """

    required = BooleanProperty(False)
    """
    Required text. If True then the text field requires text.

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

    color_mode = OptionProperty(
        "primary", options=["primary", "accent", "custom"]
    )
    """
    Color text mode. Available options are: `'primary'`, `'accent'`,
    `'custom'`.

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

    mode = OptionProperty("line", options=["rectangle", "fill"])
    """
    Text field mode. Available options are: `'line'`, `'rectangle'`, `'fill'`.

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

    line_color_normal = ColorProperty(None)
    """
    Line color normal in ``rgba`` format.

    :attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    line_color_focus = ColorProperty(None)
    """
    Line color focus in ``rgba`` format.

    :attr:`line_color_focus` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    line_anim = BooleanProperty(True)
    """
    If True, then text field shows animated line when on focus.

    :attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    error_color = ColorProperty(None)
    """
    Error color in ``rgba`` format for ``required = True``.

    :attr:`error_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    fill_color = ColorProperty([0, 0, 0, 0])
    """
    The background color of the fill in rgba format when the ``mode`` parameter
    is "fill".

    :attr:`fill_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `(0, 0, 0, 0)`.
    """

    active_line = BooleanProperty(True)
    """
    Show active line or not.

    :attr:`active_line` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    error = BooleanProperty(False)
    """
    If True, then the text field goes into ``error`` mode.

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

    current_hint_text_color = ColorProperty(None)
    """
    ``hint_text`` text color.

    :attr:`current_hint_text_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    icon_right = StringProperty()
    """
    Right icon.

    :attr:`icon_right` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    icon_right_color = ColorProperty((0, 0, 0, 1))
    """
    Color of right icon in ``rgba`` format.

    :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `(0, 0, 0, 1)`.
    """

    text_color = ColorProperty(None)
    """
    Text color in ``rgba`` format.

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

    font_size = NumericProperty("16sp")
    """
    Font size of the text in pixels.

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

    # TODO: Add minimum allowed height. Otherwise, if the value is,
    #  for example, 20, the text field will simply be lessened.
    max_height = NumericProperty(0)
    """
    Maximum height of the text box when `multiline = True`.

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

    radius = ListProperty([10, 10, 0, 0])
    """
    The corner radius for a text field in `fill` mode.

    :attr:`radius` is a :class:`~kivy.properties.ListProperty` and
    defaults to `[10, 10, 0, 0]`.
    """

    font_name_helper_text = StringProperty("Roboto")
    """
    Font name for helper text.

    :attr:`font_name_helper_text` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'Roboto'`.
    """

    font_name_hint_text = StringProperty("Roboto")
    """
    Font name for hint text.

    :attr:`font_name_hint_text` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'Roboto'`.
    """

    font_name_max_length = StringProperty("Roboto")
    """
    Font name for max text length.

    :attr:`font_name_max_length` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'Roboto'`.
    """

    _text_len_error = BooleanProperty(False)
    _hint_lbl_font_size = NumericProperty("16sp")
    _line_blank_space_right_point = NumericProperty(0)
    _line_blank_space_left_point = NumericProperty(0)
    _hint_y = NumericProperty("38dp")
    _line_width = NumericProperty(0)
    _current_line_color = ColorProperty((0, 0, 0, 0))
    _current_error_color = ColorProperty((0, 0, 0, 0))
    _current_hint_text_color = ColorProperty((0, 0, 0, 0))
    _current_right_lbl_color = ColorProperty((0, 0, 0, 0))
    _fill_color = ColorProperty((0, 0, 0, 0))
    _msg_lbl = None
    _right_msg_lbl = None
    _hint_lbl = None
    _lbl_icon_right = None

    def __init__(self, **kwargs):
        self.set_objects_labels()
        super().__init__(**kwargs)
        # Sets default colors.
        self.line_color_normal = self.theme_cls.divider_color
        self.line_color_focus = self.theme_cls.primary_color
        self.error_color = self.theme_cls.error_color
        self._current_hint_text_color = self.theme_cls.disabled_hint_text_color
        self._current_line_color = self.theme_cls.primary_color

        self.bind(
            helper_text=self._set_msg,
            hint_text=self.on_hint_text,
            _hint_lbl_font_size=self._hint_lbl.setter("font_size"),
            helper_text_mode=self._set_message_mode,
            max_text_length=self._set_max_text_length,
            text=self.set_text,
        )
        self.theme_cls.bind(
            primary_color=self._update_primary_color,
            theme_style=self._update_theme_style,
            accent_color=self._update_accent_color,
        )
        self.has_had_text = False
        self._better_texture_size = None
        Clock.schedule_once(self.check_text)
        Clock.schedule_once(self._set_fill_color)
        self._set_msg(self, self.helper_text)

    def check_text(self, interval):
        self.set_text(self, self.text)

    def _set_fill_color(self, interval):
        self._fill_color = self.fill_color

    def set_objects_labels(self):
        """Creates labels objects for the parameters
        `helper_text`,`hint_text`, etc."""

        # Label object for `helper_text` parameter.
        self._msg_lbl = TextfieldLabel(
            font_style="Caption",
            halign="left",
            valign="middle",
            text=self.helper_text,
            field=self,
            font_name=self.font_name_helper_text,
        )
        # Label object for `max_text_length` parameter.
        self._right_msg_lbl = TextfieldLabel(
            font_style="Caption",
            halign="right",
            valign="middle",
            text="",
            field=self,
        )
        # Label object for `hint_text` parameter.
        self._hint_lbl = TextfieldLabel(
            font_style="Subtitle1", halign="left", valign="middle", field=self
        )
        # MDIcon object for the icon on the right.
        self._lbl_icon_right = MDIcon(theme_text_color="Custom")

    def on_font_name_helper_text(self, instance, value):
        self._msg_lbl.font_name = value

    def on_font_name_hint_text(self, instance, value):
        self._hint_lbl.font_name = value

    def on_font_name_max_length(self, instance, value):
        self._right_msg_lbl.font_name = value

    def on_icon_right(self, instance, value):
        self._lbl_icon_right.icon = value

    def on_icon_right_color(self, instance, value):
        self._lbl_icon_right.text_color = value

    def on_width(self, instance, width):
        """Called when the application window is resized."""

        if (
            any((self.focus, self.error, self._text_len_error))
            and instance is not None
        ):
            # Bottom line width when active focus.
            self._line_width = width
        self._msg_lbl.width = self.width
        self._right_msg_lbl.width = self.width

    def on_focus(self, *args):
        disabled_hint_text_color = self.theme_cls.disabled_hint_text_color
        Animation.cancel_all(
            self, "_line_width", "_hint_y", "_hint_lbl_font_size"
        )
        self._set_text_len_error()

        if self.focus:
            if not self._get_has_error():

                def on_progress(*args):
                    self._line_blank_space_right_point = (
                        self._hint_lbl.width + dp(5)
                    )

                animation = Animation(
                    _line_blank_space_left_point=self._hint_lbl.x - dp(5),
                    _current_hint_text_color=self.line_color_focus,
                    _fill_color=self.fill_color[:-1]
                    + [self.fill_color[-1] - 0.1],
                    duration=0.2,
                    t="out_quad",
                )
                animation.bind(on_progress=on_progress)
                animation.start(self)
            self.has_had_text = True
            Animation.cancel_all(
                self, "_line_width", "_hint_y", "_hint_lbl_font_size"
            )
            if not self.text:
                self._anim_lbl_font_size(dp(14), sp(12))
            Animation(
                _line_width=self.width,
                duration=(0.2 if self.line_anim else 0),
                t="out_quad",
            ).start(self)
            if self._get_has_error():
                self._anim_current_error_color(self.error_color)
                if self.helper_text_mode == "on_error" and (
                    self.error or self._text_len_error
                ):
                    self._anim_current_error_color(self.error_color)
                elif (
                    self.helper_text_mode == "on_error"
                    and not self.error
                    and not self._text_len_error
                ):
                    self._anim_current_error_color((0, 0, 0, 0))
                elif self.helper_text_mode in ("persistent", "on_focus"):
                    self._anim_current_error_color(disabled_hint_text_color)
            else:
                self._anim_current_right_lbl_color(disabled_hint_text_color)
                Animation(
                    duration=0.2, _current_hint_text_color=self.line_color_focus
                ).start(self)
                if self.helper_text_mode == "on_error":
                    self._anim_current_error_color((0, 0, 0, 0))
                if self.helper_text_mode in ("persistent", "on_focus"):
                    self._anim_current_error_color(disabled_hint_text_color)
        else:
            Animation(
                _fill_color=self.fill_color[:-1] + [self.fill_color[-1] + 0.1],
                duration=0.2,
                t="out_quad",
            ).start(self)
            if not self.text:
                self._anim_lbl_font_size(dp(38), sp(16))
                Animation(
                    _line_blank_space_right_point=0,
                    _line_blank_space_left_point=0,
                    duration=0.2,
                    t="out_quad",
                ).start(self)
            if self._get_has_error():
                self._anim_get_has_error_color(self.error_color)
                if self.helper_text_mode == "on_error" and (
                    self.error or self._text_len_error
                ):
                    self._anim_current_error_color(self.error_color)
                elif (
                    self.helper_text_mode == "on_error"
                    and not self.error
                    and not self._text_len_error
                ):
                    self._anim_current_error_color((0, 0, 0, 0))
                elif self.helper_text_mode == "persistent":
                    self._anim_current_error_color(disabled_hint_text_color)
                elif self.helper_text_mode == "on_focus":
                    self._anim_current_error_color((0, 0, 0, 0))
            else:
                Animation(duration=0.2, color=(1, 1, 1, 1)).start(
                    self._hint_lbl
                )
                self._anim_get_has_error_color()
                if self.helper_text_mode == "on_error":
                    self._anim_current_error_color((0, 0, 0, 0))
                elif self.helper_text_mode == "persistent":
                    self._anim_current_error_color(disabled_hint_text_color)
                elif self.helper_text_mode == "on_focus":
                    self._anim_current_error_color((0, 0, 0, 0))
                Animation(
                    _line_width=0,
                    duration=(0.2 if self.line_anim else 0),
                    t="out_quad",
                ).start(self)

    def on_disabled(self, *args):
        if self.disabled:
            self._update_colors(self.theme_cls.disabled_hint_text_color)
        elif not self.disabled:
            if self.color_mode == "primary":
                self._update_primary_color()
            elif self.color_mode == "accent":
                self._update_accent_color()
            elif self.color_mode == "custom":
                self._update_colors(self.line_color_focus)

    def set_text(self, instance, text):
        self.text = re.sub("\n", " ", text) if not self.multiline else text
        if len(text) > 0:
            self.has_had_text = True
        if self.max_text_length is not None:
            self._right_msg_lbl.text = f"{len(text)}/{self.max_text_length}"
        self._set_text_len_error()
        if self.error or self._text_len_error:
            self._anim_current_line_color(self.error_color)
            if self.helper_text_mode == "on_error" and (
                self.error or self._text_len_error
            ):
                self._anim_current_error_color(self.error_color)
            if self._text_len_error:
                self._anim_current_right_lbl_color(self.error_color)
        else:
            self._anim_current_right_lbl_color(
                self.theme_cls.disabled_hint_text_color
            )
            self._anim_current_line_color(self.line_color_focus)
            if self.helper_text_mode == "on_error":
                self._anim_current_error_color((0, 0, 0, 0))
            self.on_focus(self, self.focus)

        if len(self.text) != 0 and not self.focus:
            self._hint_y = dp(14)
            self._hint_lbl_font_size = sp(12)

    def on_text_validate(self):
        self.has_had_text = True
        self._set_text_len_error()

    def on_color_mode(self, instance, mode):
        if mode == "primary":
            self._update_primary_color()
        elif mode == "accent":
            self._update_accent_color()
        elif mode == "custom":
            self._update_colors(self.line_color_focus)

    def on_line_color_focus(self, *args):
        if self.color_mode == "custom":
            self._update_colors(self.line_color_focus)

    def on__hint_text(self, instance, value):
        pass

    def on_hint_text(self, instance, value):
        self._hint_lbl.text = value
        self._hint_lbl.font_size = sp(16)

    def on_height(self, instance, value):
        if value >= self.max_height and self.max_height:
            self.height = self.max_height

    def _anim_get_has_error_color(self, color=None):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_get_has_error.png
        if not color:
            line_color = self.line_color_focus
            hint_text_color = (
                self.theme_cls.disabled_hint_text_color
                if not self.current_hint_text_color
                else self.current_hint_text_color
            )
            right_lbl_color = (0, 0, 0, 0)
        else:
            line_color = color
            hint_text_color = color
            right_lbl_color = color
        Animation(
            duration=0.2,
            _current_line_color=line_color,
            _current_hint_text_color=hint_text_color,
            _current_right_lbl_color=right_lbl_color,
        ).start(self)

    def _anim_current_line_color(self, color):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_line_color.gif
        Animation(
            duration=0.2,
            _current_hint_text_color=color,
            _current_line_color=color,
        ).start(self)

    def _anim_lbl_font_size(self, hint_y, font_size):
        Animation(
            _hint_y=hint_y,
            _hint_lbl_font_size=font_size,
            duration=0.2,
            t="out_quad",
        ).start(self)

    def _anim_current_right_lbl_color(self, color, duration=0.2):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_right_lbl_color.png
        Animation(duration=duration, _current_right_lbl_color=color).start(self)

    def _anim_current_error_color(self, color, duration=0.2):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_disabled_color.gif
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_fade.gif
        Animation(duration=duration, _current_error_color=color).start(self)

    def _update_colors(self, color):
        self.line_color_focus = color
        if not self.error and not self._text_len_error:
            self._current_line_color = color
            if self.focus:
                self._current_line_color = color

    def _update_accent_color(self, *args):
        if self.color_mode == "accent":
            self._update_colors(self.theme_cls.accent_color)

    def _update_primary_color(self, *args):
        if self.color_mode == "primary":
            self._update_colors(self.theme_cls.primary_color)

    def _update_theme_style(self, *args):
        self.line_color_normal = self.theme_cls.divider_color
        if not any([self.error, self._text_len_error]):
            if not self.focus:
                self._current_hint_text_color = (
                    self.theme_cls.disabled_hint_text_color
                )
                self._current_right_lbl_color = (
                    self.theme_cls.disabled_hint_text_color
                )
                if self.helper_text_mode == "persistent":
                    self._current_error_color = (
                        self.theme_cls.disabled_hint_text_color
                    )

    def _get_has_error(self):
        if self.error or all(
            [
                self.max_text_length is not None
                and len(self.text) > self.max_text_length
            ]
        ):
            has_error = True
        else:
            if all((self.required, len(self.text) == 0, self.has_had_text)):
                has_error = True
            else:
                has_error = False
        return has_error

    def _get_max_text_length(self):
        """Returns the maximum number of characters that can be entered in a
        text field."""

        return (
            sys.maxsize
            if self.max_text_length is None
            else self.max_text_length
        )

    def _set_text_len_error(self):
        if len(self.text) > self._get_max_text_length() or all(
            (self.required, len(self.text) == 0, self.has_had_text)
        ):
            self._text_len_error = True
        else:
            self._text_len_error = False

    def _set_msg(self, instance, text):
        self._msg_lbl.text = text
        self.helper_text = text

    def _set_message_mode(self, instance, text):
        self.helper_text_mode = text
        if self.helper_text_mode == "persistent":
            self._anim_current_error_color(
                self.theme_cls.disabled_hint_text_color, 0.1
            )

    def _set_max_text_length(self, instance, length):
        self.max_text_length = length
        self._right_msg_lbl.text = f"{len(self.text)}/{length}"

    def _refresh_hint_text(self):
        pass


class MDTextFieldRound(ThemableBehavior, TextInput):
    icon_left = StringProperty()
    """
    Left icon.

    :attr:`icon_left` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    icon_left_color = ColorProperty((0, 0, 0, 1))
    """
    Color of left icon in ``rgba`` format.

    :attr:`icon_left_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `(0, 0, 0, 1)`.
    """

    icon_right = StringProperty()
    """
    Right icon.

    :attr:`icon_right` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    icon_right_color = ColorProperty((0, 0, 0, 1))
    """
    Color of right icon.

    :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `(0, 0, 0, 1)`.
    """

    line_color = ColorProperty(None)
    """
    Field line color.

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

    normal_color = ColorProperty(None)
    """
    Field color if `focus` is `False`.

    :attr:`normal_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    color_active = ColorProperty(None)
    """
    Field color if `focus` is `True`.

    :attr:`color_active` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    _color_active = ColorProperty(None)
    _icon_left_color_copy = ColorProperty(None)
    _icon_right_color_copy = ColorProperty(None)

    def __init__(self, **kwargs):
        self._lbl_icon_left = MDIcon(theme_text_color="Custom")
        self._lbl_icon_right = MDIcon(theme_text_color="Custom")
        super().__init__(**kwargs)
        self.cursor_color = self.theme_cls.primary_color
        self.icon_left_color = self.theme_cls.text_color
        self.icon_right_color = self.theme_cls.text_color

        if not self.normal_color:
            self.normal_color = self.theme_cls.primary_light
        if not self.line_color:
            self.line_color = self.theme_cls.primary_dark
        if not self.color_active:
            self._color_active = (0.5, 0.5, 0.5, 0.5)

    def on_focus(self, instance, value):
        if value:
            self.icon_left_color = self.theme_cls.primary_color
            self.icon_right_color = self.theme_cls.primary_color
        else:
            self.icon_left_color = (
                self._icon_left_color_copy or self.theme_cls.text_color
            )
            self.icon_right_color = (
                self._icon_right_color_copy or self.theme_cls.text_color
            )

    def on_icon_left(self, instance, value):
        self._lbl_icon_left.icon = value

    def on_icon_left_color(self, instance, value):
        self._lbl_icon_left.text_color = value
        if (
            not self._icon_left_color_copy
            and value != self.theme_cls.text_color
            and value != self.theme_cls.primary_color
        ):
            self._icon_left_color_copy = value

    def on_icon_right(self, instance, value):
        self._lbl_icon_right.icon = value

    def on_icon_right_color(self, instance, value):
        self._lbl_icon_right.text_color = value
        if (
            not self._icon_right_color_copy
            and value != self.theme_cls.text_color
            and value != self.theme_cls.primary_color
        ):
            self._icon_right_color_copy = value

    def on_color_active(self, instance, value):
        if value != [0, 0, 0, 0.5]:
            self._color_active = value
            self._color_active[-1] = 0.5
        else:
            self._color_active = value
