Sideband/sbapp/kivymd/uix/textfield/textfield.py

1440 lines
45 KiB
Python
Raw Normal View History

2022-07-07 16:16:10 -04:00
"""
Components/TextField
====================
.. 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_
2022-07-07 16:16:10 -04:00
- 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"
2022-07-07 16:16:10 -04:00
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}
2022-07-07 16:16:10 -04:00
'''
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
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"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode.gif
:align: center
Round mode
---------
.. code-block:: kv
MDTextField:
hint_text: "Round mode"
mode: "round"
max_text_length: 15
helper_text: "Massage"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.png
2022-07-07 16:16:10 -04:00
: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.
Clickable icon for MDTextField
------------------------------
.. 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
MDTextField:
id: text_field
hint_text: root.hint_text
text: root.text
password: True
icon_left: "key-variant"
MDIconButton:
icon: "eye-off"
pos_hint: {"center_y": .5}
pos: text_field.width - self.width + dp(8), 0
theme_text_color: "Hint"
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()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-clickable_right-icon.gif
:align: center
.. seealso::
See more information in the :class:`~MDTextFieldRect` class.
"""
2022-10-02 11:16:59 -04:00
__all__ = ("MDTextField", "MDTextFieldRect")
2022-07-07 16:16:10 -04:00
import os
import re
from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger
2022-07-07 16:16:10 -04:00
from kivy.metrics import dp, sp
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
DictProperty,
2022-07-07 16:16:10 -04:00
ListProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivymd import uix_path
from kivymd.font_definitions import theme_font_styles
from kivymd.theming import ThemableBehavior
2022-10-02 11:16:59 -04:00
from kivymd.uix.behaviors import DeclarativeBehavior
2022-07-07 16:16:10 -04:00
from kivymd.uix.label import MDIcon
with open(
os.path.join(uix_path, "textfield", "textfield.kv"), encoding="utf-8"
) as kv_file:
Builder.load_string(kv_file.read())
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):
"""Base texture for :class:`~MDTextField` class."""
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(DeclarativeBehavior, ThemableBehavior, TextInput):
2022-07-07 16:16:10 -04:00
helper_text = StringProperty()
"""
Text for ``helper_text`` mode.
:attr:`helper_text` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
helper_text_mode = OptionProperty(
"on_focus", options=["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`.
"""
mode = OptionProperty(
"line", options=["rectangle", "round", "fill", "line"]
)
"""
Text field mode.
Available options are: `'line'`, `'rectangle'`, `'fill'`, `'round'`.
:attr:`mode` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'line'`.
"""
line_color_normal = ColorProperty([0, 0, 0, 0])
"""
Line color normal (static underline line) in ``rgba`` format.
2022-07-07 16:16:10 -04:00
.. code-block:: kv
MDTextField:
hint_text: "line_color_normal"
line_color_normal: 1, 0, 1, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.gif
2022-07-07 16:16:10 -04:00
:align: center
:attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
line_color_focus = ColorProperty([0, 0, 0, 0])
"""
Line color focus (active underline line) in ``rgba`` format.
2022-07-07 16:16:10 -04:00
.. code-block:: kv
MDTextField:
hint_text: "line_color_focus"
line_color_focus: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-focus.gif
:align: center
:attr:`line_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
line_anim = BooleanProperty(True)
"""
If True, then text field shows animated underline when on focus.
:attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
error_color = ColorProperty([0, 0, 0, 0])
"""
Error color in ``rgba`` format for ``required = True``.
2022-07-07 16:16:10 -04:00
:attr:`error_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
2022-10-02 11:16:59 -04:00
fill_color_normal = ColorProperty([0, 0, 0, 0])
2022-07-07 16:16:10 -04:00
"""
Fill background color in 'fill' mode when text field is out of focus.
2022-07-07 16:16:10 -04:00
:attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
fill_color_focus = ColorProperty([0, 0, 0, 0])
"""
Fill background color in 'fill' mode when the text field has focus.
2022-07-07 16:16:10 -04:00
:attr:`fill_color_focus` 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`.
"""
hint_text_color_normal = ColorProperty([0, 0, 0, 0])
"""
Hint text color when text field is out of focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
hint_text: "hint_text_color_normal"
hint_text_color_normal: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.gif
2022-07-07 16:16:10 -04:00
:align: center
:attr:`hint_text_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
hint_text_color_focus = ColorProperty([0, 0, 0, 0])
"""
Hint text color when the text field has focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
hint_text: "hint_text_color_focus"
hint_text_color_focus: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-focus.gif
:align: center
:attr:`hint_text_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
helper_text_color_normal = ColorProperty([0, 0, 0, 0])
"""
Helper text color when text field is out of focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
helper_text: "helper_text_color_normal"
helper_text_mode: "persistent"
helper_text_color_normal: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-normal.png
:align: center
:attr:`helper_text_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
helper_text_color_focus = ColorProperty([0, 0, 0, 0])
"""
Helper text color when the text field has focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
helper_text: "helper_text_color_focus"
helper_text_mode: "persistent"
helper_text_color_focus: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-focus.gif
:align: center
:attr:`helper_text_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
icon_right_color_normal = ColorProperty([0, 0, 0, 0])
"""
Color of right icon when text field is out of focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
icon_right: "language-python"
hint_text: "icon_right_color_normal"
icon_right_color_normal: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif
2022-07-07 16:16:10 -04:00
:align: center
:attr:`icon_right_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
icon_right_color_focus = ColorProperty([0, 0, 0, 0])
"""
Color of right icon when the text field has focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
icon_right: "language-python"
hint_text: "icon_right_color_focus"
icon_right_color_focus: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif
:align: center
:attr:`icon_right_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
icon_left_color_normal = ColorProperty([0, 0, 0, 0])
"""
Color of right icon when text field is out of focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
icon_right: "language-python"
hint_text: "icon_right_color_normal"
icon_left_color_normal: 0, 1, 0, 1
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif
:align: center
2022-07-07 16:16:10 -04:00
:attr:`icon_left_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
icon_left_color_focus = ColorProperty([0, 0, 0, 0])
"""
Color of right icon when the text field has focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
icon_right: "language-python"
hint_text: "icon_right_color_focus"
icon_right_color_focus: 0, 1, 0, 1
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif
:align: center
2022-07-07 16:16:10 -04:00
:attr:`icon_left_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
max_length_text_color = ColorProperty([0, 0, 0, 0])
"""
Text color of the maximum length of characters to be input.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
hint_text: "max_length_text_color"
max_length_text_color: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
max_text_length: 5
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.gif
2022-07-07 16:16:10 -04:00
:align: center
:attr:`max_length_text_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
icon_right = StringProperty()
"""
Right icon texture.
.. note:: It's just a texture. It has no press/touch events.
:attr:`icon_right` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
icon_left = StringProperty()
"""
Left icon texture.
.. versionadded:: 1.0.0
.. note:: It's just a texture. It has no press/touch events.
Also note that you cannot use the left and right icons at the same time yet.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-left-icon.png
:align: center
:attr:`icon_left` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
text_color_normal = ColorProperty([0, 0, 0, 0])
"""
Text color in ``rgba`` format when text field is out of focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
hint_text: "text_color_normal"
text_color_normal: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.gif
2022-07-07 16:16:10 -04:00
:align: center
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
text_color_focus = ColorProperty([0, 0, 0, 0])
"""
Text color in ``rgba`` format when text field has focus.
2022-07-07 16:16:10 -04:00
.. versionadded:: 1.0.0
.. code-block:: kv
MDTextField:
hint_text: "text_color_focus"
text_color_focus: 0, 1, 0, 1
2022-07-07 16:16:10 -04:00
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.gif
:align: center
:attr:`text_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`.
"""
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`.
.. code-block:: kv
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}
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode-multiline-max-height.gif
:align: center
: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'`.
"""
# The x-axis position of the hint text in the text field.
_hint_x = NumericProperty(0)
# The y-axis position of the hint text in the text field.
_hint_y = NumericProperty("38dp")
# Width of underline that animates when the focus of the text field.
_underline_width = NumericProperty(0)
# Font size for hint text.
_hint_text_font_size = NumericProperty(sp(16))
# Label object for `helper_text` parameter.
_helper_text_label = None
# Label object for `max_text_length` parameter.
_max_length_label = None
# Label object for `hint_text` parameter.
_hint_text_label = None
# `MDIcon` object for the icon on the right.
_icon_right_label = None
# `MDIcon` object for the icon on the left.
_icon_left_label = None
# The left and right coordinates of the text field in 'rectangle' mode.
#
# ┍──blank_space_left blank_space_right──────────────┑
# | |
# | |
# | |
# ┕──────────────────────────────────────────────────────┙
_line_blank_space_right_point = NumericProperty(0)
_line_blank_space_left_point = NumericProperty(0)
# The values of colors that are used in the KV file to display the color
# of the corresponding texture.
_fill_color = ColorProperty([0, 0, 0, 0])
_text_color_normal = ColorProperty([0, 0, 0, 0])
_hint_text_color = ColorProperty([0, 0, 0, 0])
_helper_text_color = ColorProperty([0, 0, 0, 0])
_max_length_text_color = ColorProperty([0, 0, 0, 0])
_icon_right_color = ColorProperty([0, 0, 0, 0])
_icon_left_color = ColorProperty([0, 0, 0, 0])
_line_color_normal = ColorProperty([0, 0, 0, 0])
_line_color_focus = ColorProperty([0, 0, 0, 0])
# Text to restore the text of the tale after clearing the text field.
__hint_text = StringProperty()
# List of color attribute names that should be updated when changing the
# application color palette.
_colors_to_updated = ListProperty()
2022-10-02 11:16:59 -04:00
def __init__(self, *args, **kwargs):
2022-07-07 16:16:10 -04:00
self.set_objects_labels()
Clock.schedule_once(self._set_attr_names_to_updated)
Clock.schedule_once(self.set_colors_to_updated)
Clock.schedule_once(self.set_default_colors)
2022-10-02 11:16:59 -04:00
super().__init__(*args, **kwargs)
2022-07-07 16:16:10 -04:00
self.bind(
_hint_text_font_size=self._hint_text_label.setter("font_size"),
_icon_right_color=self._icon_right_label.setter("text_color"),
_icon_left_color=self._icon_left_label.setter("text_color"),
text=self.set_text,
)
self.theme_cls.bind(
primary_color=lambda x, y: self.set_default_colors(0, True),
theme_style=lambda x, y: self.set_default_colors(0, True),
2022-07-07 16:16:10 -04:00
)
Clock.schedule_once(self.check_text)
# TODO: Is this method necessary?
# During testing, a quick double-click on the text box does not stop
# the animation of the hint text height.
def cancel_all_animations_on_double_click(self) -> None:
"""
Cancels the animations of the text field when double-clicking on the
text field.
"""
if (
self._hint_y == dp(38)
and not self.text
or self._hint_y == dp(14)
and self.text
):
Animation.cancel_all(
self,
"_underline_width",
"_hint_y",
"_hint_x",
"_hint_text_font_size",
)
def set_colors_to_updated(self, interval: Union[float, int]) -> None:
for attr_name in self._attr_names_to_updated.keys():
if getattr(self, attr_name) == [0, 0, 0, 0]:
self._colors_to_updated.append(attr_name)
def set_default_colors(
self, interval: Union[float, int], updated: bool = False
) -> None:
"""
Sets the default text field colors when initializing a text field
object. Also called when the application palette changes.
:param updated: If `True` - the color theme of the application has
been changed. Updating the meanings of the colors.
"""
self._set_attr_names_to_updated(0)
for attr_name in self._attr_names_to_updated.keys():
self._set_color(
attr_name, self._attr_names_to_updated[attr_name], updated
)
if self.error_color == [0, 0, 0, 0] or updated:
self.error_color = self.theme_cls.error_color
2022-07-07 16:16:10 -04:00
if self.max_length_text_color == [0, 0, 0, 0] or updated:
self.max_length_text_color = self.theme_cls.disabled_hint_text_color
2022-07-07 16:16:10 -04:00
self._hint_text_color = self.hint_text_color_normal
self._text_color_normal = self.text_color_normal
self._fill_color = self.fill_color_normal
self._icon_right_color = self.icon_right_color_normal
self._icon_left_color = self.icon_left_color_normal
self._max_length_text_color = [0, 0, 0, 0]
if self.helper_text_mode in ("on_focus", "on_error"):
self._helper_text_color = [0, 0, 0, 0]
elif self.helper_text_mode == "persistent":
self._helper_text_color = self.helper_text_color_normal
self._line_color_normal = self.line_color_normal
self._line_color_focus = self.line_color_focus
def set_notch_rectangle(self, joining: bool = False) -> None:
"""
Animates a notch for the hint text in the rectangle of the text field
of type `rectangle`.
"""
def on_progress(*args):
self._line_blank_space_right_point = (
2022-10-02 11:16:59 -04:00
self._hint_text_label.width + dp(17) if not joining else 0
2022-07-07 16:16:10 -04:00
)
if self.hint_text:
animation = Animation(
2022-10-02 11:16:59 -04:00
_line_blank_space_left_point=self._hint_text_label.x - dp(-7)
2022-07-07 16:16:10 -04:00
if not joining
else 0,
duration=0.2,
t="out_quad",
)
animation.bind(on_progress=on_progress)
animation.start(self)
def set_active_underline_width(self, width: Union[float, int]) -> None:
"""Animates the width of the active underline line."""
Animation(
_underline_width=width,
duration=(0.2 if self.line_anim else 0),
t="out_quad",
).start(self)
def set_static_underline_color(self, color: list) -> None:
"""Animates the color of a static underline line."""
Animation(
_line_color_normal=color,
duration=(0.2 if self.line_anim else 0),
t="out_quad",
).start(self)
def set_active_underline_color(self, color: list) -> None:
"""Animates the fill color for 'fill' mode."""
Animation(_line_color_focus=color, duration=0.2, t="out_quad").start(
self
)
def set_fill_color(self, color: list) -> None:
"""Animates the color of the hint text."""
Animation(_fill_color=color, duration=0.2, t="out_quad").start(self)
def set_helper_text_color(self, color: list) -> None:
"""Animates the color of the hint text."""
Animation(_helper_text_color=color, duration=0.2, t="out_quad").start(
self
)
def set_max_length_text_color(self, color: list) -> None:
"""Animates the color of the max length text."""
Animation(
_max_length_text_color=color, duration=0.2, t="out_quad"
).start(self)
def set_icon_right_color(self, color: list) -> None:
"""Animates the color of the icon right."""
Animation(_icon_right_color=color, duration=0.2, t="out_quad").start(
self
)
def set_icon_left_color(self, color: list) -> None:
"""Animates the color of the icon left."""
Animation(_icon_left_color=color, duration=0.2, t="out_quad").start(
self
)
def set_hint_text_color(self, focus: bool, error: bool = False) -> None:
"""Animates the color of the hint text."""
if self.mode != "round":
Animation(
_hint_text_color=(
self.hint_text_color_normal
if not focus
else self.hint_text_color_focus
)
if not error
else self.error_color,
duration=0.2,
t="out_quad",
).start(self)
2022-10-02 11:16:59 -04:00
def set_pos_hint_text(self, y: float, x: float = 12) -> None:
2022-07-07 16:16:10 -04:00
"""Animates the x-axis width and y-axis height of the hint text."""
if self.mode != "round":
Animation(_hint_y=y, duration=0.2, t="out_quad").start(self)
if self.mode == "rectangle":
if not self.icon_left:
_hint_x = x
else:
if y == dp(10):
2022-10-02 11:16:59 -04:00
_hint_x = dp(-4)
2022-07-07 16:16:10 -04:00
else:
_hint_x = dp(20)
Animation(
_hint_x=_hint_x,
duration=0.2,
t="out_quad",
).start(self)
elif self.mode == "fill":
Animation(
_hint_x=dp(16) if not self.icon_left else dp(36),
duration=0.2,
t="out_quad",
).start(self)
elif self.mode == "line":
Animation(
_hint_x=dp(0) if not self.icon_left else dp(36),
duration=0.2,
t="out_quad",
).start(self)
def set_hint_text_font_size(self, font_size: float) -> None:
"""Animates the font size of the hint text."""
if self.mode != "round":
Animation(
_hint_text_font_size=font_size, duration=0.2, t="out_quad"
).start(self)
def set_max_text_length(self) -> None:
"""Called when text is entered into a text field."""
if self.max_text_length:
self._max_length_label.text = (
f"{len(self.text)}/{self.max_text_length}"
)
def check_text(self, interval: Union[float, int]) -> None:
self.set_text(self, self.text)
def set_text(self, instance_text_field, text: str) -> None:
"""Called when text is entered into a text field."""
self.text = re.sub("\n", " ", text) if not self.multiline else text
self.set_max_text_length()
if self.text and self.max_length_text_color and self._get_has_error():
2022-07-07 16:16:10 -04:00
self.error = True
if (
self.text
and self.max_length_text_color
and not self._get_has_error()
):
self.error = False
# Start the appropriate texture animations when programmatically
# pasting text into a text field.
if len(self.text) != 0 and not self.focus:
self.set_pos_hint_text(
(dp(28) if self.mode != "line" else dp(18))
if self.mode != "rectangle"
else dp(10)
)
self.set_hint_text_font_size(sp(12))
if self.mode == "rectangle":
self.set_notch_rectangle()
if not self.text and not self.focus:
self.on_focus(instance_text_field, False)
if self.mode == "round" and self.text:
self.hint_text = ""
if self.mode == "round" and not self.text:
self.hint_text = self.__hint_text
def set_x_pos(self):
pass
def set_objects_labels(self) -> None:
"""
Creates labels objects for the parameters`helper_text`,`hint_text`,
etc.
"""
self._helper_text_label = TextfieldLabel(
font_style="Caption",
halign="left",
valign="middle",
field=self,
font_name=self.font_name_helper_text,
)
self._max_length_label = TextfieldLabel(
font_style="Caption",
halign="right",
valign="middle",
text="",
field=self,
)
self._hint_text_label = TextfieldLabel(
font_style="Subtitle1", halign="left", valign="middle", field=self
)
self._icon_right_label = MDIcon(theme_text_color="Custom")
self._icon_left_label = MDIcon(theme_text_color="Custom")
def on_helper_text(self, instance_text_field, helper_text: str) -> None:
self._helper_text_label.text = helper_text
def on_focus(self, instance_text_field, focus: bool) -> None:
# TODO: See `cancel_all_animations_on_double_click` method.
# self.cancel_all_animations_on_double_click()
if focus:
if self.mode == "rectangle":
self.set_notch_rectangle()
self.set_static_underline_color([0, 0, 0, 0])
if (
self.helper_text_mode in ("on_focus", "persistent")
and self.helper_text
):
self.set_helper_text_color(self.helper_text_color_focus)
if self.mode == "fill":
self.set_fill_color(self.fill_color_focus)
self.set_active_underline_width(self.width)
self.set_pos_hint_text(
(dp(28) if self.mode != "line" else dp(18))
if self.mode != "rectangle"
else dp(10)
)
self.set_hint_text_color(focus)
self.set_hint_text_font_size(sp(12))
if self.max_text_length:
self.set_max_length_text_color(self.max_length_text_color)
if self.icon_right:
self.set_icon_right_color(self.icon_right_color_focus)
if self.icon_left:
self.set_icon_left_color(self.icon_left_color_focus)
if self.error:
if self.hint_text:
self.set_hint_text_color(focus, self.error)
if self.helper_text:
self.set_helper_text_color(self.error_color)
if self.max_text_length:
self.set_max_length_text_color(self.error_color)
if self.icon_right:
self.set_icon_right_color(self.error_color)
if self.icon_left:
self.set_icon_left_color(self.error_color)
else:
if self.helper_text_mode == "persistent" and self.helper_text:
self.set_helper_text_color(self.helper_text_color_normal)
if self.mode == "rectangle" and not self.text:
self.set_notch_rectangle(joining=True)
if not self.text:
if self.mode == "rectangle":
y = dp(38)
elif self.mode == "fill":
y = dp(46)
else:
y = dp(34)
self.set_pos_hint_text(y)
self.set_hint_text_font_size(sp(16))
if self.icon_right:
self.set_icon_right_color(self.icon_right_color_normal)
if self.icon_left:
self.set_icon_left_color(self.icon_left_color_normal)
if self.hint_text:
self.set_hint_text_color(focus, self.error)
self.set_active_underline_width(0)
self.set_max_length_text_color([0, 0, 0, 0])
if self.mode == "fill":
self.set_fill_color(self.fill_color_normal)
self.error = self._get_has_error() or self.error
if self.error:
self.set_static_underline_color(self.error_color)
else:
self.set_static_underline_color(self.line_color_normal)
def on_icon_left(self, instance_text_field, icon_name: str) -> None:
self._icon_left_label.icon = icon_name
def on_icon_right(self, instance_text_field, icon_name: str) -> None:
self._icon_right_label.icon = icon_name
def on_disabled(self, instance_text_field, disabled_value: bool) -> None:
pass
def on_error(self, instance_text_field, error: bool) -> None:
"""
Changes the primary colors of the text box to match the `error` value
(text field is in an error state or not).
"""
if error:
self.set_max_length_text_color(self.error_color)
self.set_active_underline_color(self.error_color)
if self.hint_text:
self.set_hint_text_color(self.focus, self.error)
if self.helper_text:
self.set_helper_text_color(self.error_color)
if self.icon_right:
self.set_icon_right_color(self.error_color)
if self.icon_left:
self.set_icon_left_color(self.error_color)
if self.helper_text_mode == "on_error":
self.set_helper_text_color(self.error_color)
else:
self.set_max_length_text_color(self.max_length_text_color)
self.set_active_underline_color(self.line_color_focus)
if self.hint_text:
self.set_hint_text_color(self.focus)
if self.helper_text:
self.set_helper_text_color(self.helper_text_color_focus)
if self.icon_right:
self.set_icon_right_color(self.icon_right_color_focus)
if self.icon_left:
self.set_icon_left_color(self.icon_left_color_focus)
if self.helper_text_mode in ("on_focus", "on_error"):
self.set_helper_text_color([0, 0, 0, 0])
elif self.helper_text_mode == "persistent":
self.set_helper_text_color(self.helper_text_color_normal)
def on_hint_text(self, instance_text_field, hint_text: str) -> None:
if hint_text:
self.__hint_text = hint_text
self._hint_text_label.text = hint_text
self._hint_text_label.font_size = sp(16)
def on_width(self, instance_text_field, width: float) -> None:
"""Called when the application window is resized."""
if self.focus:
self._underline_width = self.width
def on_height(self, instance_text_field, value_height: float) -> None:
if value_height >= self.max_height and self.max_height:
self.height = self.max_height
def on_text_color_normal(self, instance_text_field, color: list):
2022-07-07 16:16:10 -04:00
self._text_color_normal = color
def on_hint_text_color_normal(self, instance_text_field, color: list):
2022-07-07 16:16:10 -04:00
self._hint_text_color = color
def on_helper_text_color_normal(self, instance_text_field, color: list):
2022-07-07 16:16:10 -04:00
self._helper_text_color = color
def on_icon_right_color_normal(self, instance_text_field, color: list):
2022-07-07 16:16:10 -04:00
self._icon_right_color = color
def on_line_color_normal(self, instance_text_field, color: list):
2022-07-07 16:16:10 -04:00
self._line_color_normal = color
def on_max_length_text_color(self, instance_text_field, color: list):
2022-07-07 16:16:10 -04:00
self._max_length_text_color = color
def _set_color(self, attr_name: str, color: str, updated: bool) -> None:
if attr_name in self._colors_to_updated or updated:
if attr_name in self._colors_to_updated:
setattr(self, attr_name, color)
def _set_attr_names_to_updated(self, interval: Union[float, int]) -> None:
"""
Sets and update the default color dictionary for text field textures.
"""
self._attr_names_to_updated = {
"line_color_normal": self.theme_cls.disabled_hint_text_color,
"line_color_focus": self.theme_cls.primary_color,
"hint_text_color_normal": self.theme_cls.disabled_hint_text_color,
"hint_text_color_focus": self.theme_cls.primary_color,
"helper_text_color_normal": self.theme_cls.disabled_hint_text_color,
"helper_text_color_focus": self.theme_cls.disabled_hint_text_color,
"text_color_normal": self.theme_cls.disabled_hint_text_color,
"text_color_focus": self.theme_cls.primary_color,
"fill_color_normal": self.theme_cls.bg_darkest,
"fill_color_focus": self.theme_cls.bg_dark,
"icon_right_color_normal": self.theme_cls.disabled_hint_text_color,
"icon_right_color_focus": self.theme_cls.primary_color,
"icon_left_color_normal": self.theme_cls.disabled_hint_text_color,
"icon_left_color_focus": self.theme_cls.primary_color,
}
def _get_has_error(self) -> bool:
"""
Returns `False` or `True` depending on the state of the text field,
for example when the allowed character limit has been exceeded or when
the :attr:`~MDTextField.required` parameter is set to `True`.
"""
if self.max_text_length and len(self.text) > self.max_text_length:
has_error = True
else:
if all((self.required, len(self.text) == 0)):
has_error = True
else:
has_error = False
return has_error
def _refresh_hint_text(self):
"""Method override to avoid duplicate hint text texture."""
if __name__ == "__main__":
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivymd.app import MDApp
KV = """
MDScreen:
MDBoxLayout:
id: box
orientation: "vertical"
spacing: "20dp"
adaptive_height: True
size_hint_x: .8
pos_hint: {"center_x": .5, "center_y": .5}
MDTextField:
hint_text: "Label"
helper_text: "Error massage"
2022-07-07 16:16:10 -04:00
mode: "rectangle"
max_text_length: 5
MDTextField:
icon_left: "git"
hint_text: "Label"
helper_text: "Error massage"
2022-07-07 16:16:10 -04:00
mode: "rectangle"
MDTextField:
icon_left: "git"
hint_text: "Label"
helper_text: "Error massage"
2022-07-07 16:16:10 -04:00
mode: "fill"
MDTextField:
hint_text: "Label"
helper_text: "Error massage"
2022-07-07 16:16:10 -04:00
mode: "fill"
MDTextField:
hint_text: "Label"
helper_text: "Error massage"
2022-07-07 16:16:10 -04:00
MDTextField:
icon_left: "git"
hint_text: "Label"
helper_text: "Error massage"
2022-07-07 16:16:10 -04:00
MDTextField:
hint_text: "Round mode"
mode: "round"
max_text_length: 15
helper_text: "Massage"
2022-07-07 16:16:10 -04:00
MDFlatButton:
text: "SET TEXT"
pos_hint: {"center_x": .5}
on_release: app.set_text()
"""
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def set_text(self):
for widget in self.root.ids.box.children:
if issubclass(widget.__class__, TextInput):
widget.text = "Input text"
Test().run()