globalforest/kivymd/uix/textfield.py
2020-10-14 23:38:48 +03:00

1266 lines
40 KiB
Python
Executable file

"""
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
.. 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 sys
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.metrics import dp, sp
from kivy.properties import (
BooleanProperty,
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.material_resources import DEVICE_TYPE
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 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
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
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: (10, 10, 0, 0, 0)
font_name: "Roboto" if not root.font_name else root.font_name
foreground_color: app.theme_cls.text_color
font_size: "16sp"
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:
rgba: root._primary_color
Line:
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'
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
Color:
rgba: self.line_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
# Texture of left Icon.
Color:
rgba: self.icon_left_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
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:
root.theme_cls.disabled_hint_text_color if not self.focus \
else root.foreground_color
"""
)
class MDTextFieldRect(ThemableBehavior, TextInput):
_primary_color = ListProperty((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):
instance_line = self.canvas.children[-1].children[-1]
instance_color = self.canvas.children[-1].children[0]
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, t="out_cubic").start(instance_line)
Animation(a=alpha, d=d_color).start(instance_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 = ListProperty()
"""
Line color normal in ``rgba`` format.
:attr:`line_color_normal` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
line_color_focus = ListProperty()
"""
Line color focus in ``rgba`` format.
:attr:`line_color_focus` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
error_color = ListProperty()
"""
Error color in ``rgba`` format for ``required = True``.
:attr:`error_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
fill_color = ListProperty((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.ListProperty`
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 = ListProperty()
"""
``hint_text`` text color.
:attr:`current_hint_text_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
icon_right = StringProperty()
"""Right icon.
:attr:`icon_right` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
icon_right_color = ListProperty((0, 0, 0, 1))
"""Color of right icon in ``rgba`` format.
:attr:`icon_right_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `(0, 0, 0, 1)`.
"""
_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 = ListProperty((0, 0, 0, 0))
_current_error_color = ListProperty((0, 0, 0, 0))
_current_hint_text_color = ListProperty((0, 0, 0, 0))
_current_right_lbl_color = ListProperty((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._set_hint,
_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.on_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
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,
)
# 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_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:
self._line_blank_space_right_point = (
self._get_line_blank_space_right_point()
)
_fill_color = self.fill_color
_fill_color[3] = self.fill_color[3] - 0.1
if not self._get_has_error():
Animation(
_line_blank_space_right_point=self._line_blank_space_right_point,
_line_blank_space_left_point=self._hint_lbl.x - dp(5),
_current_hint_text_color=self.line_color_focus,
fill_color=_fill_color,
duration=0.2,
t="out_quad",
).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, 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:
_fill_color = self.fill_color
_fill_color[3] = self.fill_color[3] + 0.1
Animation(fill_color=_fill_color, 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, t="out_quad").start(self)
def on_text(self, instance, 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:
if self.focus:
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:
if self.focus:
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))
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 _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
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_line_blank_space_right_point(self):
# https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_line_blank_space_right_point.png
return (
self._hint_lbl.texture_size[0]
- self._hint_lbl.texture_size[0] / 100 * dp(18)
if DEVICE_TYPE == "desktop"
else dp(10)
)
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_hint(self, instance, text):
self._hint_lbl.text = text
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 = ListProperty((0, 0, 0, 1))
"""Color of left icon in ``rgba`` format.
:attr:`icon_left_color` is an :class:`~kivy.properties.ListProperty`
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 = ListProperty((0, 0, 0, 1))
"""Color of right icon.
:attr:`icon_right_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `(0, 0, 0, 1)`.
"""
line_color = ListProperty()
"""Field line color.
:attr:`line_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
normal_color = ListProperty()
"""Field color if `focus` is `False`.
:attr:`normal_color` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
color_active = ListProperty()
"""Field color if `focus` is `True`.
:attr:`color_active` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
_color_active = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._lbl_icon_left = MDIcon(theme_text_color="Custom")
self._lbl_icon_right = MDIcon(theme_text_color="Custom")
self.cursor_color = self.theme_cls.primary_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.theme_cls.text_color
self.icon_right_color = 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
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_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