Updated KivyMD

This commit is contained in:
Mark Qvist 2022-10-08 17:17:59 +02:00
parent 9cf33ec050
commit 4b619f385d
98 changed files with 6018 additions and 3706 deletions

View File

@ -49,6 +49,9 @@ images_path = os.path.join(path, f"images{os.sep}")
uix_path = os.path.join(path, "uix") uix_path = os.path.join(path, "uix")
"""Path to uix directory.""" """Path to uix directory."""
glsl_path = os.path.join(path, "data", "glsl")
"""Path to glsl directory."""
_log_message = ( _log_message = (
"KivyMD:" "KivyMD:"
+ (" Release" if release else "") + (" Release" if release else "")

View File

@ -43,9 +43,10 @@ __all__ = ("MDApp",)
import os import os
from kivy.app import App from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder from kivy.lang import Builder
from kivy.logger import Logger from kivy.logger import Logger
from kivy.properties import ObjectProperty from kivy.properties import ObjectProperty, StringProperty
from kivymd.theming import ThemeManager from kivymd.theming import ThemeManager
@ -56,13 +57,16 @@ class FpsMonitoring:
def fps_monitor_start(self) -> None: def fps_monitor_start(self) -> None:
"""Adds a monitor to the main application window.""" """Adds a monitor to the main application window."""
from kivy.core.window import Window def add_monitor(*args):
from kivy.core.window import Window
from kivymd.utils.fpsmonitor import FpsMonitor from kivymd.utils.fpsmonitor import FpsMonitor
monitor = FpsMonitor() monitor = FpsMonitor()
monitor.start() monitor.start()
Window.add_widget(monitor) Window.add_widget(monitor)
Clock.schedule_once(add_monitor)
class MDApp(App, FpsMonitoring): class MDApp(App, FpsMonitoring):
@ -71,6 +75,16 @@ class MDApp(App, FpsMonitoring):
information. information.
""" """
icon = StringProperty("kivymd/images/logo/kivymd-icon-512.png")
"""
See :attr:`~kivy.app.App.icon` attribute for more information.
.. versionadded:: 1.1.0
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
adn default to `kivymd/images/logo/kivymd-icon-512.png`.
"""
theme_cls = ObjectProperty() theme_cls = ObjectProperty()
""" """
Instance of :class:`~ThemeManager` class. Instance of :class:`~ThemeManager` class.

View File

@ -412,7 +412,7 @@ To demonstrate the shades of the palette, you can run the following code:
self.screen = Factory.Root() self.screen = Factory.Root()
for name_tab in colors.keys(): for name_tab in colors.keys():
tab = Tab(text=name_tab) tab = Tab(title=name_tab)
self.screen.ids.android_tabs.add_widget(tab) self.screen.ids.android_tabs.add_widget(tab)
return self.screen return self.screen
@ -427,7 +427,7 @@ To demonstrate the shades of the palette, you can run the following code:
{ {
"viewclass": "ItemColor", "viewclass": "ItemColor",
"md_bg_color": colors[tab_text][value_color], "md_bg_color": colors[tab_text][value_color],
"text": value_color, "title": value_color,
} }
) )

View File

@ -0,0 +1,51 @@
/*
The shader code has been refactored for the KivyMD library.
You can find the original code of this shaders at the links:
https://www.shadertoy.com/view/WtdSDs
https://www.shadertoy.com/view/fsdyzB
Additional thanks to iq for optimizing conditional block for individual
corner radius:
https://iquilezles.org/articles/distfunctions
*/
// For lower opengl version
float custom_smoothstep(float a, float b, float x) {
float t = clamp((x - a) / (b - a), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
float roundedBoxSDF(vec2 centerPosition, vec2 size, vec4 radius) {
radius.xy = (centerPosition.x > 0.0) ? radius.xy : radius.zw;
radius.x = (centerPosition.y > 0.0) ? radius.x : radius.y;
vec2 q = abs(centerPosition) - (size - shadow_softness) + radius.x;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius.x;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// Smooth the result (free antialiasing).
float edge0 = 0.0;
float smoothedAlpha = 1.0 - custom_smoothstep(0.0, edge0, 1.0);
// Get the resultant shape.
vec4 quadColor = mix(
vec4(
shadow_color[0],
shadow_color[1],
shadow_color[2],
0.0
),
shadow_color,
smoothedAlpha
);
// Apply a drop shadow effect.
float shadowDistance = roundedBoxSDF(
fragCoord.xy - mouse.xy - (size / 2.0), size / 2.0, shadow_radius
);
float shadowAlpha = 1.0 - custom_smoothstep(
-shadow_softness, shadow_softness, shadowDistance
);
fragColor = mix(quadColor, shadow_color, shadowAlpha - smoothedAlpha);
}

View File

@ -0,0 +1,10 @@
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 resolution;
uniform vec4 mouse;
uniform vec2 size;
uniform vec4 shadow_radius;
uniform float shadow_softness;
uniform vec4 shadow_color;

View File

@ -0,0 +1,10 @@
vec2 gfc(in vec4 fc) {
vec2 canvas_pos = resolution.zw;
vec2 uv = fc.xy;
uv.y -= canvas_pos.y;
return uv;
}
void main(void) {
mainImage(gl_FragColor, gfc(gl_FragCoord));
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1 +0,0 @@
{"quad_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "quad_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "quad_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1 +0,0 @@
{"rec_shadow-1.png": {"20": [2, 266, 256, 128], "21": [260, 266, 256, 128], "22": [518, 266, 256, 128], "23": [776, 266, 256, 128], "3": [260, 136, 256, 128], "2": [2, 136, 256, 128], "5": [776, 136, 256, 128], "4": [518, 136, 256, 128], "7": [260, 6, 256, 128], "6": [2, 6, 256, 128], "9": [776, 6, 256, 128], "8": [518, 6, 256, 128]}, "rec_shadow-0.png": {"11": [518, 266, 256, 128], "10": [260, 266, 256, 128], "13": [2, 136, 256, 128], "12": [776, 266, 256, 128], "15": [518, 136, 256, 128], "14": [260, 136, 256, 128], "17": [2, 6, 256, 128], "16": [776, 136, 256, 128], "19": [518, 6, 256, 128], "18": [260, 6, 256, 128], "1": [776, 6, 256, 128], "0": [2, 266, 256, 128]}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1 +0,0 @@
{"rec_st_shadow-0.png": {"11": [262, 138, 128, 256], "10": [132, 138, 128, 256], "13": [522, 138, 128, 256], "12": [392, 138, 128, 256], "15": [782, 138, 128, 256], "14": [652, 138, 128, 256], "16": [912, 138, 128, 256], "0": [2, 138, 128, 256]}, "rec_st_shadow-1.png": {"20": [522, 138, 128, 256], "21": [652, 138, 128, 256], "17": [2, 138, 128, 256], "23": [912, 138, 128, 256], "19": [262, 138, 128, 256], "18": [132, 138, 128, 256], "22": [782, 138, 128, 256], "1": [392, 138, 128, 256]}, "rec_st_shadow-2.png": {"3": [132, 138, 128, 256], "2": [2, 138, 128, 256], "5": [392, 138, 128, 256], "4": [262, 138, 128, 256], "7": [652, 138, 128, 256], "6": [522, 138, 128, 256], "9": [912, 138, 128, 256], "8": [782, 138, 128, 256]}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1 +0,0 @@
{"round_shadow-1.png": {"20": [2, 136, 128, 128], "21": [132, 136, 128, 128], "22": [262, 136, 128, 128], "23": [2, 6, 128, 128], "19": [132, 266, 128, 128], "18": [2, 266, 128, 128], "1": [262, 266, 128, 128], "3": [262, 6, 128, 128], "2": [132, 6, 128, 128]}, "round_shadow-0.png": {"11": [262, 266, 128, 128], "10": [132, 266, 128, 128], "13": [132, 136, 128, 128], "12": [2, 136, 128, 128], "15": [2, 6, 128, 128], "14": [262, 136, 128, 128], "17": [262, 6, 128, 128], "16": [132, 6, 128, 128], "0": [2, 266, 128, 128]}, "round_shadow-2.png": {"5": [132, 266, 128, 128], "4": [2, 266, 128, 128], "7": [2, 136, 128, 128], "6": [262, 266, 128, 128], "9": [262, 136, 128, 128], "8": [132, 136, 128, 128]}}

View File

@ -35,8 +35,15 @@ assert "Icons" in LabelBase._fonts.keys() # NOQA
images = os.listdir(kivymd.images_path) images = os.listdir(kivymd.images_path)
print(images) print(images)
assert "logo" in images
assert "alpha_layer.png" in images
assert "black.png" in images
assert "blue.png" in images
assert "red.png" in images
assert "green.png" in images
assert "yellow.png" in images
assert "folder.png" in images assert "folder.png" in images
assert "rec_shadow.atlas" in images assert "transparent.png" in images
""" """
) )
pyi_main.run( pyi_main.run(

View File

@ -1,14 +1,13 @@
def test_create_project(): def test_create_project():
import os import os
import sys
os.system( os.system(
f"{sys.executable} -m kivymd.tools.patterns.create_project " f"python3.10 -m kivymd.tools.patterns.create_project "
f"MVC " f"MVC "
f"{os.path.expanduser('~')} " f"{os.path.expanduser('~')} "
f"TestProject " f"TestProject "
f"{sys.executable} " f"python3.10 "
f"master " f"stable "
f"--name_screen TestProjectScreen " f"--name_screen TestProjectScreen "
f"--name_database restdb " f"--name_database restdb "
f"--use_hotreload yes" f"--use_hotreload yes"

View File

@ -212,9 +212,8 @@ respects, the theming stays as documented.
dictionary :attr:`kivymd.color_definition.colors`. dictionary :attr:`kivymd.color_definition.colors`.
""" """
from kivy.animation import Animation
from kivy.app import App from kivy.app import App
from kivy.atlas import Atlas
from kivy.clock import Clock from kivy.clock import Clock
from kivy.core.window import Window from kivy.core.window import Window
from kivy.event import EventDispatcher from kivy.event import EventDispatcher
@ -224,13 +223,13 @@ from kivy.properties import (
BooleanProperty, BooleanProperty,
ColorProperty, ColorProperty,
DictProperty, DictProperty,
NumericProperty,
ObjectProperty, ObjectProperty,
OptionProperty, OptionProperty,
StringProperty, StringProperty,
) )
from kivy.utils import get_color_from_hex from kivy.utils import get_color_from_hex
from kivymd import images_path
from kivymd.color_definitions import colors, hue, palette from kivymd.color_definitions import colors, hue, palette
from kivymd.font_definitions import theme_font_styles from kivymd.font_definitions import theme_font_styles
from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE
@ -624,6 +623,152 @@ class ThemeManager(EventDispatcher):
and defaults to `'M2'`. and defaults to `'M2'`.
""" """
theme_style_switch_animation = BooleanProperty(False)
"""
Animate app colors when switching app color scheme ('Dark/light').
.. versionadded:: 1.1.0
.. tabs::
.. tab:: Declarative KV style
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDCard:
orientation: "vertical"
padding: 0, 0, 0 , "36dp"
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 4
shadow_radius: 6
shadow_offset: 0, 2
MDLabel:
text: "Theme style - {}".format(app.theme_cls.theme_style)
halign: "center"
valign: "center"
bold: True
font_style: "H5"
MDRaisedButton:
text: "Set theme"
on_release: app.switch_theme_style()
pos_hint: {"center_x": .5}
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style_switch_animation = True
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def switch_theme_style(self):
self.theme_cls.primary_palette = (
"Orange" if self.theme_cls.primary_palette == "Red" else "Red"
)
self.theme_cls.theme_style = (
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style_switch_animation = True
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDCard(
MDLabel(
id="label",
text="Theme style - {}".format(self.theme_cls.theme_style),
halign="center",
valign="center",
bold=True,
font_style="H5",
),
MDRaisedButton(
text="Set theme",
on_release=self.switch_theme_style,
pos_hint={"center_x": 0.5},
),
id="card",
orientation="vertical",
padding=(0, 0, 0, "36dp"),
size_hint=(0.5, 0.5),
pos_hint={"center_x": 0.5, "center_y": 0.5},
elevation=4,
shadow_radius=6,
shadow_offset=(0, 2),
)
)
)
def switch_theme_style(self, *args):
self.theme_cls.primary_palette = (
"Orange" if self.theme_cls.primary_palette == "Red" else "Red"
)
self.theme_cls.theme_style = (
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
)
self.root.ids.card.ids.label.text = (
"Theme style - {}".format(self.theme_cls.theme_style)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation.gif
:align: center
:attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
theme_style_switch_animation_duration = NumericProperty(0.2)
"""
Duration of the animation of switching the color scheme of the application
("Dark/light").
.. versionadded:: 1.1.0
.. code-block:: python
class Example(MDApp):
def build(self):
self.theme_cls.theme_style_switch_animation = True
self.theme_cls.theme_style_switch_animation_duration = 0.8
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style-switch-animation-duration.gif
:align: center
:attr:`theme_style_switch_animation_duration` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
theme_style = OptionProperty("Light", options=["Light", "Dark"]) theme_style = OptionProperty("Light", options=["Light", "Dark"])
""" """
App theme style. App theme style.
@ -1184,14 +1329,22 @@ class ThemeManager(EventDispatcher):
): ):
self.set_clearcolor_by_theme_style(theme_style) self.set_clearcolor_by_theme_style(theme_style)
set_clearcolor = BooleanProperty(True) _set_clearcolor = False
def set_clearcolor_by_theme_style(self, theme_style): def set_clearcolor_by_theme_style(self, theme_style):
if not self.set_clearcolor: if self.theme_style_switch_animation and self._set_clearcolor:
return Animation(
Window.clearcolor = get_color_from_hex( clearcolor=get_color_from_hex(
self.colors[theme_style]["Background"] self.colors[theme_style]["Background"]
) ),
d=self.theme_style_switch_animation_duration,
t="linear",
).start(Window)
else:
Window.clearcolor = get_color_from_hex(
self.colors[theme_style]["Background"]
)
self._set_clearcolor = True
# Font name, size (sp), always caps, letter spacing (sp). # Font name, size (sp), always caps, letter spacing (sp).
font_styles = DictProperty( font_styles = DictProperty(
@ -1398,10 +1551,6 @@ class ThemeManager(EventDispatcher):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.rec_shadow = Atlas(f"{images_path}rec_shadow.atlas")
self.rec_st_shadow = Atlas(f"{images_path}rec_st_shadow.atlas")
self.quad_shadow = Atlas(f"{images_path}quad_shadow.atlas")
self.round_shadow = Atlas(f"{images_path}round_shadow.atlas")
Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style)) Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style))
self._determine_device_orientation(None, Window.size) self._determine_device_orientation(None, Window.size)
Window.bind(size=self._determine_device_orientation) Window.bind(size=self._determine_device_orientation)
@ -1487,6 +1636,16 @@ class ThemableBehavior(EventDispatcher):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.unbind_properties = [
"theme_style",
"material_style",
"device_orientation",
"primary_color",
"primary_palette",
"accent_palette",
"text_color",
]
if self.theme_cls is not None: if self.theme_cls is not None:
pass pass
else: else:
@ -1507,3 +1666,24 @@ class ThemableBehavior(EventDispatcher):
) )
self.theme_cls = App.get_running_app().theme_cls self.theme_cls = App.get_running_app().theme_cls
super().__init__(**kwargs) super().__init__(**kwargs)
def dec_disabled(self, *args, **kwargs) -> None:
callabacks = self.theme_cls.get_property_observers("theme_style")
for callaback in callabacks:
try:
if hasattr(callaback, "proxy") and hasattr(
callaback.proxy, "theme_cls"
):
for property_name in self.unbind_properties:
self.theme_cls.unbind(
**{
property_name: getattr(
callaback.proxy, callaback.method_name
)
}
)
except ReferenceError:
pass
super().dec_disabled(*args, **kwargs)

View File

@ -13,6 +13,18 @@ from pathlib import Path
import kivymd import kivymd
datas = [ datas = [
# Add `.frag` files from the `kivymd/data/glsl/elevation` directory.
(
str(Path(kivymd.glsl_path).joinpath("elevation")) + os.sep,
str(
Path("kivymd").joinpath(
str(Path(kivymd.glsl_path)).split(str(Path("kivymd")) + os.sep)[
1
]
+ f"{os.sep}elevation"
)
),
),
# Add `.ttf` files from the `kivymd/fonts` directory. # Add `.ttf` files from the `kivymd/fonts` directory.
( (
kivymd.fonts_path, kivymd.fonts_path,

View File

@ -35,8 +35,8 @@
if not root.front_layer_color \ if not root.front_layer_color \
else root.front_layer_color else root.front_layer_color
radius: radius:
[root.radius_left, root.radius_left, [root.radius_left, root.radius_right,
root.radius_right, root.radius_right] 0, 0]
OneLineListItem: OneLineListItem:
id: header_button id: header_button

View File

@ -202,7 +202,6 @@ from kivy.uix.boxlayout import BoxLayout
from kivymd import uix_path from kivymd import uix_path
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.floatlayout import MDFloatLayout
@ -528,5 +527,5 @@ class _BackLayer(BoxLayout):
pass pass
class _FrontLayer(MDCard, FakeRectangularElevationBehavior): class _FrontLayer(MDCard):
pass pass

View File

@ -35,7 +35,7 @@ Usage
MDTopAppBar: MDTopAppBar:
id: toolbar id: toolbar
title: "Example Banners" title: "Example Banners"
elevation: 10 elevation: 4
pos_hint: {'top': 1} pos_hint: {'top': 1}
MDBoxLayout: MDBoxLayout:
@ -157,7 +157,6 @@ from kivy.properties import (
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from kivymd import uix_path from kivymd import uix_path
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFlatButton from kivymd.uix.button import MDFlatButton
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
@ -177,7 +176,7 @@ with open(
Builder.load_string(kv_file.read()) Builder.load_string(kv_file.read())
class MDBanner(MDCard, FakeRectangularElevationBehavior): class MDBanner(MDCard):
vertical_pad = NumericProperty(dp(68)) vertical_pad = NumericProperty(dp(68))
""" """
Indent the banner at the top of the screen. Indent the banner at the top of the screen.

View File

@ -11,18 +11,20 @@ from .backgroundcolor_behavior import (
) )
# flake8: NOQA # flake8: NOQA
from .declarative_bahavior import DeclarativeBehavior from .declarative_behavior import DeclarativeBehavior
from .elevation import ( from .elevation import (
CircularElevationBehavior, CircularElevationBehavior,
CommonElevationBehavior, CommonElevationBehavior,
FakeCircularElevationBehavior, FakeCircularElevationBehavior,
FakeRectangularElevationBehavior, FakeRectangularElevationBehavior,
ObservableShadow,
RectangularElevationBehavior, RectangularElevationBehavior,
RoundedRectangularElevationBehavior, RoundedRectangularElevationBehavior,
) )
from .magic_behavior import MagicBehavior from .magic_behavior import MagicBehavior
from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior
from .rotate_behavior import RotateBehavior
from .scale_behavior import ScaleBehavior
from .stencil_behavior import StencilBehavior
from .touch_behavior import TouchBehavior from .touch_behavior import TouchBehavior
from .hover_behavior import HoverBehavior # isort:skip from .hover_behavior import HoverBehavior # isort:skip

View File

@ -7,8 +7,9 @@ Behaviors/Background Color
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior") __all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
from typing import List from typing import List, Union
from kivy.animation import Animation
from kivy.lang import Builder from kivy.lang import Builder
from kivy.properties import ( from kivy.properties import (
ColorProperty, ColorProperty,
@ -24,8 +25,6 @@ from kivy.utils import get_color_from_hex
from kivymd.color_definitions import hue, palette, text_colors from kivymd.color_definitions import hue, palette, text_colors
from kivymd.theming import ThemeManager from kivymd.theming import ThemeManager
from .elevation import CommonElevationBehavior
Builder.load_string( Builder.load_string(
""" """
#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout #:import RelativeLayout kivy.uix.relativelayout.RelativeLayout
@ -38,7 +37,7 @@ Builder.load_string(
angle: self.angle angle: self.angle
origin: self._background_origin origin: self._background_origin
Color: Color:
rgba: self.md_bg_color rgba: self._md_bg_color
RoundedRectangle: RoundedRectangle:
group: "Background_instruction" group: "Background_instruction"
size: self.size size: self.size
@ -67,7 +66,7 @@ Builder.load_string(
) )
class BackgroundColorBehavior(CommonElevationBehavior): class BackgroundColorBehavior:
background = StringProperty() background = StringProperty()
""" """
Background image path. Background image path.
@ -153,15 +152,26 @@ class BackgroundColorBehavior(CommonElevationBehavior):
_background_x = NumericProperty(0) _background_x = NumericProperty(0)
_background_y = NumericProperty(0) _background_y = NumericProperty(0)
_background_origin = ReferenceListProperty( _background_origin = ReferenceListProperty(_background_x, _background_y)
_background_x, _md_bg_color = ColorProperty([0, 0, 0, 0])
_background_y,
)
def __init__(self, **kwarg): def __init__(self, **kwarg):
super().__init__(**kwarg) super().__init__(**kwarg)
self.bind(pos=self.update_background_origin) self.bind(pos=self.update_background_origin)
def on_md_bg_color(self, instance_md_widget, color: Union[list, str]):
if (
hasattr(self, "theme_cls")
and self.theme_cls.theme_style_switch_animation
):
Animation(
_md_bg_color=color,
d=self.theme_cls.theme_style_switch_animation_duration,
t="linear",
).start(self)
else:
self._md_bg_color = color
def update_background_origin( def update_background_origin(
self, instance_md_widget, pos: List[float] self, instance_md_widget, pos: List[float]
) -> None: ) -> None:
@ -206,12 +216,14 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
super().__init__(**kwargs) super().__init__(**kwargs)
if hasattr(self, "theme_cls"): if hasattr(self, "theme_cls"):
self.theme_cls.bind( self.theme_cls.bind(
primary_palette=self._update_specific_text_color primary_palette=self._update_specific_text_color,
accent_palette=self._update_specific_text_color,
theme_style=self._update_specific_text_color,
) )
self.theme_cls.bind(accent_palette=self._update_specific_text_color) self.bind(
self.theme_cls.bind(theme_style=self._update_specific_text_color) background_hue=self._update_specific_text_color,
self.bind(background_hue=self._update_specific_text_color) background_palette=self._update_specific_text_color,
self.bind(background_palette=self._update_specific_text_color) )
self._update_specific_text_color(None, None) self._update_specific_text_color(None, None)
def _update_specific_text_color( def _update_specific_text_color(
@ -234,5 +246,17 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
secondary_color[3] = 0.54 secondary_color[3] = 0.54
else: else:
secondary_color[3] = 0.7 secondary_color[3] = 0.7
self.specific_text_color = color
self.specific_secondary_text_color = secondary_color if (
hasattr(self, "theme_cls")
and self.theme_cls.theme_style_switch_animation
):
Animation(
specific_text_color=color,
specific_secondary_text_color=secondary_color,
d=self.theme_cls.theme_style_switch_animation_duration,
t="linear",
).start(self)
else:
self.specific_text_color = color
self.specific_secondary_text_color = secondary_color

File diff suppressed because it is too large Load Diff

View File

@ -111,7 +111,7 @@ from kivy.properties import (
from kivy.uix.behaviors import ToggleButtonBehavior from kivy.uix.behaviors import ToggleButtonBehavior
class CommonRipple(object): class CommonRipple:
"""Base class for ripple effect.""" """Base class for ripple effect."""
ripple_rad_default = NumericProperty(1) ripple_rad_default = NumericProperty(1)

View File

@ -0,0 +1,133 @@
"""
Behaviors/Rotate
================
.. versionadded:: 1.1.0
Base class for controlling the rotate of the widget.
.. note:: See `kivy.graphics.Rotate
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Rotate>`_
for more information.
Kivy
----
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.app import App
from kivy.properties import NumericProperty
from kivy.uix.button import Button
KV = '''
Screen:
RotateButton:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_rotate(self)
canvas.before:
PushMatrix
Rotate:
angle: self.rotate_value_angle
axis: 0, 0, 1
origin: self.center
canvas.after:
PopMatrix
'''
class RotateButton(Button):
rotate_value_angle = NumericProperty(0)
class Test(App):
def build(self):
return Builder.load_string(KV)
def change_rotate(self, instance_button: Button) -> None:
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
Test().run()
KivyMD
------
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivymd.app import MDApp
from kivymd.uix.behaviors import RotateBehavior
from kivymd.uix.boxlayout import MDBoxLayout
KV = '''
MDScreen:
RotateBox:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_rotate(self)
md_bg_color: "red"
'''
class RotateBox(ButtonBehavior, RotateBehavior, MDBoxLayout):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def change_rotate(self, instance_button: RotateBox) -> None:
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
Test().run()
"""
__all__ = ("RotateBehavior",)
from kivy.lang import Builder
from kivy.properties import ListProperty, NumericProperty
Builder.load_string(
"""
<RotateBehavior>
canvas.before:
PushMatrix
Rotate:
angle: self.rotate_value_angle
axis: tuple(self.rotate_value_axis)
origin: self.center
canvas.after:
PopMatrix
"""
)
class RotateBehavior:
"""Base class for controlling the rotate of the widget."""
rotate_value_angle = NumericProperty(0)
"""
Property for getting/setting the angle of the rotation.
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
rotate_value_axis = ListProperty((0, 0, 1))
"""
Property for getting/setting the axis of the rotation.
:attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
and defaults to `(0, 0, 1)`.
"""

View File

@ -0,0 +1,156 @@
"""
Behaviors/Scale
===============
.. versionadded:: 1.1.0
Base class for controlling the scale of the widget.
.. note:: See `kivy.graphics.Rotate
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Scale>`_
for more information.
Kivy
----
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.button import Button
from kivy.app import App
KV = '''
Screen:
ScaleButton:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_scale(self)
canvas.before:
PushMatrix
Scale:
x: self.scale_value_x
y: self.scale_value_y
z: self.scale_value_x
origin: self.center
canvas.after:
PopMatrix
'''
class ScaleButton(Button):
scale_value_x = NumericProperty(1)
scale_value_y = NumericProperty(1)
scale_value_z = NumericProperty(1)
class Test(App):
def build(self):
return Builder.load_string(KV)
def change_scale(self, instance_button: Button) -> None:
Animation(
scale_value_x=0.5,
scale_value_y=0.5,
scale_value_z=0.5,
d=0.3,
).start(instance_button)
Test().run()
KivyMD
------
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivymd.app import MDApp
from kivymd.uix.behaviors import ScaleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
KV = '''
MDScreen:
ScaleBox:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_scale(self)
md_bg_color: "red"
'''
class ScaleBox(ButtonBehavior, ScaleBehavior, MDBoxLayout):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def change_scale(self, instance_button: ScaleBox) -> None:
Animation(
scale_value_x=0.5,
scale_value_y=0.5,
scale_value_z=0.5,
d=0.3,
).start(instance_button)
Test().run()
"""
__all__ = ("ScaleBehavior",)
from kivy.lang import Builder
from kivy.properties import NumericProperty
Builder.load_string(
"""
<ScaleBehavior>
canvas.before:
PushMatrix
Scale:
x: self.scale_value_x
y: self.scale_value_y
z: self.scale_value_x
origin: self.center
canvas.after:
PopMatrix
"""
)
class ScaleBehavior:
"""Base class for controlling the scale of the widget."""
scale_value_x = NumericProperty(1)
"""
X-axis value.
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
scale_value_y = NumericProperty(1)
"""
Y-axis value.
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
scale_value_z = NumericProperty(1)
"""
Z-axis value.
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""

View File

@ -0,0 +1,134 @@
"""
Behaviors/Stencil
=================
.. versionadded:: 1.1.0
Base class for controlling the stencil instructions of the widget.
.. note:: See `Stencil instructions
<https://kivy.org/doc/stable/api-kivy.graphics.stencil_instructions.html>`_
for more information.
Kivy
----
.. code-block:: python
from kivy.lang import Builder
from kivy.app import App
KV = '''
Carousel:
Button:
size_hint: .9, .8
pos_hint: {"center_x": .5, "center_y": .5}
canvas.before:
StencilPush
RoundedRectangle:
pos: root.pos
size: root.size
StencilUse
canvas.after:
StencilUnUse
RoundedRectangle:
pos: root.pos
size: root.size
StencilPop
'''
class Test(App):
def build(self):
return Builder.load_string(KV)
Test().run()
KivyMD
------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.behaviors import StencilBehavior
from kivymd.uix.fitimage import FitImage
KV = '''
#:import os os
#:import images_path kivymd.images_path
MDCarousel:
StencilImage:
size_hint: .9, .8
pos_hint: {"center_x": .5, "center_y": .5}
source: os.path.join(images_path, "logo", "kivymd-icon-512.png")
'''
class StencilImage(FitImage, StencilBehavior):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
"""
__all__ = ("StencilBehavior",)
from kivy.lang import Builder
from kivy.properties import VariableListProperty
Builder.load_string(
"""
<StencilBehavior>
canvas.before:
StencilPush
RoundedRectangle:
pos: root.pos
size: root.size
# FIXME: Sometimes the radius has the value [], which get a
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
radius: root.radius if root.radius else [0, 0, 0, 0]
StencilUse
canvas.after:
StencilUnUse
RoundedRectangle:
pos: root.pos
size: root.size
# FIXME: Sometimes the radius has the value [], which get a
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
radius: root.radius if root.radius else [0, 0, 0, 0]
StencilPop
"""
)
class StencilBehavior:
"""Base class for controlling the stencil instructions of the widget."""
radius = VariableListProperty([0], length=4)
"""
Canvas radius.
.. versionadded:: 1.0.0
.. code-block:: python
# Top left corner slice.
MDWidget:
radius: [25, 0, 0, 0]
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[0, 0, 0, 0]`.
"""

View File

@ -14,61 +14,104 @@ example:
pass pass
.. code-block:: python .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivymd.app import MDApp .. code-block:: python
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
from kivymd.uix.button import MDRectangleFlatButton
KV = ''' from kivy.lang import Builder
Screen:
MDBoxLayout: from kivymd.app import MDApp
adaptive_size: True from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
pos_hint: {"center_x": .5, "center_y": .5} from kivymd.uix.button import MDFlatButton
MyToggleButton: KV = '''
text: "Show ads" MDScreen:
group: "x"
MyToggleButton: MDBoxLayout:
text: "Do not show ads" adaptive_size: True
group: "x" spacing: "12dp"
pos_hint: {"center_x": .5, "center_y": .5}
MyToggleButton: MyToggleButton:
text: "Does not matter" text: "Show ads"
group: "x" group: "x"
'''
MyToggleButton:
text: "Do not show ads"
group: "x"
MyToggleButton:
text: "Does not matter"
group: "x"
'''
class MyToggleButton(MDRectangleFlatButton, MDToggleButton): class MyToggleButton(MDFlatButton, MDToggleButton):
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(**kwargs) super().__init__(*args, **kwargs)
self.background_down = self.theme_cls.primary_light self.background_down = self.theme_cls.primary_color
class Test(MDApp): class Test(MDApp):
def build(self): def build(self):
return Builder.load_string(KV) self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
Test().run() Test().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFlatButton
from kivymd.uix.screen import MDScreen
class MyToggleButton(MDFlatButton, MDToggleButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.background_down = self.theme_cls.primary_color
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDBoxLayout(
MyToggleButton(
text="Show ads",
group="x",
),
MyToggleButton(
text="Do not show ads",
group="x",
),
MyToggleButton(
text="Does not matter",
group="x",
),
adaptive_size=True,
spacing="12dp",
pos_hint={"center_x": .5, "center_y": .5},
),
)
)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-1.gif
:align: center :align: center
.. code-block:: python
class MyToggleButton(MDFillRoundFlatButton, MDToggleButton):
def __init__(self, **kwargs):
self.background_down = MDApp.get_running_app().theme_cls.primary_dark
super().__init__(**kwargs)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toggle-button-2.gif
:align: center
You can inherit the ``MyToggleButton`` class only from the following classes You can inherit the ``MyToggleButton`` class only from the following classes
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
@ -88,6 +131,7 @@ from kivy.properties import BooleanProperty, ColorProperty
from kivy.uix.behaviors import ToggleButtonBehavior from kivy.uix.behaviors import ToggleButtonBehavior
from kivymd.uix.button import ( from kivymd.uix.button import (
ButtonContentsIconText,
MDFillRoundFlatButton, MDFillRoundFlatButton,
MDFillRoundFlatIconButton, MDFillRoundFlatIconButton,
MDFlatButton, MDFlatButton,
@ -149,7 +193,8 @@ class MDToggleButton(ToggleButtonBehavior):
# Do the object inherited from the "supported" buttons? # Do the object inherited from the "supported" buttons?
if not issubclass(self.__class__, classinfo): if not issubclass(self.__class__, classinfo):
raise ValueError( raise ValueError(
f"Class {self.__class__} must be inherited from one of the classes in the list {classinfo}" f"Class {self.__class__} must be inherited from one of the "
f"classes in the list {classinfo}"
) )
if ( if (
not self.background_normal not self.background_normal
@ -165,10 +210,12 @@ class MDToggleButton(ToggleButtonBehavior):
): ):
self.__is_filled = True self.__is_filled = True
self.background_normal = self.theme_cls.primary_color self.background_normal = self.theme_cls.primary_color
# If not the background_normal must be the same as the inherited one: # If not background_normal must be the same as the inherited one.
else: else:
self.background_normal = self.md_bg_color[:] self.background_normal = (
# If no background_down is setted: self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
)
# If no background_down is setter.
if ( if (
not self.background_down not self.background_down
): # This means that if the value == [] or None will return True. ): # This means that if the value == [] or None will return True.
@ -200,3 +247,6 @@ class MDToggleButton(ToggleButtonBehavior):
): # If the background is transparent, the font color must be the ): # If the background is transparent, the font color must be the
# primary color. # primary color.
self.text_color = self.font_color_normal self.text_color = self.font_color_normal
if issubclass(self.__class__, ButtonContentsIconText):
self.icon_color = self.text_color

View File

@ -1,4 +1,3 @@
#:import sm kivy.uix.screenmanager
#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT #:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
@ -7,9 +6,9 @@
height: height:
STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp" STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp"
MDScreenManager: ScreenManager:
id: tab_manager id: tab_manager
transition: sm.FadeTransition(duration=.2) transition: root.transition(duration=root.transition_duration)
on_current: on_current:
root.dispatch( \ root.dispatch( \
"on_switch_tabs", \ "on_switch_tabs", \
@ -96,7 +95,7 @@
radius: [16,] radius: [16,]
size: root._selected_region_width, dp(32) size: root._selected_region_width, dp(32)
pos: pos:
self.center_x - self.width - dp(8), \ self.center_x - root._selected_region_width / 2, \
self.center_y - (dp(16)) self.center_y - (dp(16))
MDLabel: MDLabel:

View File

@ -62,59 +62,120 @@ For ease of understanding, this code works like this:
Example Example
------- -------
.. code-block:: python .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivymd.app import MDApp .. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
class Test(MDApp): class Test(MDApp):
def build(self): def build(self):
self.theme_cls.material_style = "M3" self.theme_cls.material_style = "M3"
return Builder.load_string( self.theme_cls.theme_style = "Dark"
''' return Builder.load_string(
MDScreen: '''
MDScreen:
MDBottomNavigation: MDBottomNavigation:
panel_color: "#eeeaea" #panel_color: "#eeeaea"
selected_color_background: "#97ecf8" selected_color_background: "orange"
text_color_active: 0, 0, 0, 1 text_color_active: "lightgrey"
MDBottomNavigationItem: MDBottomNavigationItem:
name: 'screen 1' name: 'screen 1'
text: 'Mail' text: 'Mail'
icon: 'gmail' icon: 'gmail'
badge_icon: "numeric-10" badge_icon: "numeric-10"
MDLabel: MDLabel:
text: 'Mail' text: 'Mail'
halign: 'center' halign: 'center'
MDBottomNavigationItem: MDBottomNavigationItem:
name: 'screen 2' name: 'screen 2'
text: 'Discord' text: 'Twitter'
icon: 'discord' icon: 'twitter'
badge_icon: "numeric-5" badge_icon: "numeric-5"
MDLabel: MDLabel:
text: 'Discord' text: 'Twitter'
halign: 'center' halign: 'center'
MDBottomNavigationItem: MDBottomNavigationItem:
name: 'screen 3' name: 'screen 3'
text: 'LinkedIN' text: 'LinkedIN'
icon: 'linkedin' icon: 'linkedin'
MDLabel: MDLabel:
text: 'LinkedIN' text: 'LinkedIN'
halign: 'center' halign: 'center'
''' '''
) )
Test().run() Test().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
class Test(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
self.theme_cls.theme_style = "Dark"
return (
MDScreen(
MDBottomNavigation(
MDBottomNavigationItem(
MDLabel(
text='Mail',
halign='center',
),
name='screen 1',
text='Mail',
icon='gmail',
badge_icon="numeric-10",
),
MDBottomNavigationItem(
MDLabel(
text='Twitter',
halign='center',
),
name='screen 1',
text='Twitter',
icon='twitter',
badge_icon="numeric-10",
),
MDBottomNavigationItem(
MDLabel(
text='LinkedIN',
halign='center',
),
name='screen 1',
text='LinkedIN',
icon='linkedin',
badge_icon="numeric-10",
),
selected_color_background="orange",
text_color_active="lightgrey",
)
)
)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif
:align: center :align: center
@ -192,16 +253,13 @@ from kivy.properties import (
) )
from kivy.uix.behaviors import ButtonBehavior from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManagerException from kivy.uix.screenmanager import FadeTransition, ScreenManagerException
from kivymd import uix_path from kivymd import uix_path
from kivymd.material_resources import STANDARD_INCREMENT from kivymd.material_resources import STANDARD_INCREMENT
from kivymd.theming import ThemableBehavior, ThemeManager from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.anchorlayout import MDAnchorLayout from kivymd.uix.anchorlayout import MDAnchorLayout
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior
DeclarativeBehavior,
FakeRectangularElevationBehavior,
)
from kivymd.uix.behaviors.backgroundcolor_behavior import ( from kivymd.uix.behaviors.backgroundcolor_behavior import (
SpecificBackgroundColorBehavior, SpecificBackgroundColorBehavior,
) )
@ -413,6 +471,28 @@ class MDBottomNavigationItem(MDTab):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def animate_header(
self, bottom_navigation_object, bottom_navigation_header_object
) -> None:
if bottom_navigation_object.use_text:
Animation(_label_font_size=sp(12), d=0.1).start(
bottom_navigation_object.previous_tab.header
)
Animation(
_selected_region_width=0,
t="in_out_sine",
d=0,
).start(bottom_navigation_header_object)
Animation(
_text_color_normal=bottom_navigation_header_object.text_color_normal
if bottom_navigation_object.previous_tab.header.text_color_normal
!= [1, 1, 1, 1]
else self.theme_cls.disabled_hint_text_color,
d=0.1,
).start(bottom_navigation_object.previous_tab.header)
bottom_navigation_object.previous_tab.header.active = False
self.header.active = True
def on_tab_press(self, *args) -> None: def on_tab_press(self, *args) -> None:
"""Called when clicking on a panel item.""" """Called when clicking on a panel item."""
@ -420,28 +500,13 @@ class MDBottomNavigationItem(MDTab):
bottom_navigation_header_object = ( bottom_navigation_header_object = (
bottom_navigation_object.previous_tab.header bottom_navigation_object.previous_tab.header
) )
bottom_navigation_object.ids.tab_manager.current = self.name
if bottom_navigation_object.previous_tab is not self: if bottom_navigation_object.previous_tab is not self:
if bottom_navigation_object.use_text: self.animate_header(
Animation(_label_font_size=sp(12), d=0.1).start( bottom_navigation_object, bottom_navigation_header_object
bottom_navigation_object.previous_tab.header )
)
Animation( super().on_tab_press(*args)
_selected_region_width=0,
t="in_out_sine",
d=0,
).start(bottom_navigation_header_object)
Animation(
_text_color_normal=bottom_navigation_header_object.text_color_normal
if bottom_navigation_object.previous_tab.header.text_color_normal
!= [1, 1, 1, 1]
else self.theme_cls.disabled_hint_text_color,
d=0.1,
).start(bottom_navigation_object.previous_tab.header)
bottom_navigation_object.previous_tab.header.active = False
self.header.active = True
bottom_navigation_object.previous_tab = self
def on_disabled( def on_disabled(
self, instance_bottom_navigation_item, disabled_value: bool self, instance_bottom_navigation_item, disabled_value: bool
@ -498,6 +563,26 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
""" """
transition = ObjectProperty(FadeTransition)
"""
Transition animation of bottom navigation screen manager.
.. versionadded:: 1.1.0
:attr:`transition` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `FadeTransition`.
"""
transition_duration = NumericProperty(0.2)
"""
Duration animation of bottom navigation screen manager.
.. versionadded:: 1.1.0
:attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
text_color_normal = ColorProperty([1, 1, 1, 1]) text_color_normal = ColorProperty([1, 1, 1, 1])
""" """
Text color of the label when it is not selected. Text color of the label when it is not selected.
@ -772,8 +857,6 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
class MDBottomNavigationBar( class MDBottomNavigationBar(
ThemableBehavior, ThemableBehavior, CommonElevationBehavior, MDFloatLayout
FakeRectangularElevationBehavior,
MDFloatLayout,
): ):
pass pass

View File

@ -34,7 +34,7 @@ Usage :class:`~MDListBottomSheet`
MDTopAppBar: MDTopAppBar:
title: "Example BottomSheet" title: "Example BottomSheet"
pos_hint: {"top": 1} pos_hint: {"top": 1}
elevation: 10 elevation: 4
MDRaisedButton: MDRaisedButton:
text: "Open list bottom sheet" text: "Open list bottom sheet"
@ -94,7 +94,7 @@ which will be used as an icon to the left of the item:
MDTopAppBar: MDTopAppBar:
title: 'Example BottomSheet' title: 'Example BottomSheet'
pos_hint: {"top": 1} pos_hint: {"top": 1}
elevation: 10 elevation: 4
MDRaisedButton: MDRaisedButton:
text: "Open grid bottom sheet" text: "Open grid bottom sheet"
@ -180,7 +180,7 @@ which will be used as an icon to the left of the item:
MDTopAppBar: MDTopAppBar:
title: 'Example BottomSheet' title: 'Example BottomSheet'
pos_hint: {"top": 1} pos_hint: {"top": 1}
elevation: 10 elevation: 4
MDRaisedButton: MDRaisedButton:
text: "Open custom bottom sheet" text: "Open custom bottom sheet"

View File

@ -93,6 +93,8 @@ from kivymd.uix.behaviors import DeclarativeBehavior
class MDBoxLayout(DeclarativeBehavior, BoxLayout, MDAdaptiveWidget): class MDBoxLayout(DeclarativeBehavior, BoxLayout, MDAdaptiveWidget):
""" """
Box layout class. For more information, see in the Box layout class.
For more information, see in the
:class:`~kivy.uix.boxlayout.BoxLayout` class documentation. :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
""" """

View File

@ -1,6 +1,7 @@
# NOQA F401 # NOQA F401
from .button import ( from .button import (
BaseButton, BaseButton,
ButtonContentsIconText,
MDFillRoundFlatButton, MDFillRoundFlatButton,
MDFillRoundFlatIconButton, MDFillRoundFlatIconButton,
MDFlatButton, MDFlatButton,

View File

@ -3,9 +3,9 @@
Clear Clear
Color: Color:
rgba: rgba:
(self._md_bg_color or [0.0, 0.0, 0.0, 0.0]) \ self._md_bg_color \
if not self.disabled else \ if not self.disabled else \
(self._md_bg_color_disabled or [0.0, 0.0, 0.0, 0.0]) self._md_bg_color_disabled
RoundedRectangle: RoundedRectangle:
size: self.size size: self.size
pos: self.pos pos: self.pos
@ -13,19 +13,17 @@
radius: [root._radius, ] radius: [root._radius, ]
Color: Color:
rgba: rgba:
root._line_color or [0.0, 0.0, 0.0, 0.0] \ root._line_color \
if not root.disabled else \ if not root.disabled else \
( \ (root._line_color_disabled or self._disabled_color)
root._line_color_disabled \
or self._disabled_color \
or [0.0, 0.0, 0.0, 0.0] \
)
Line: Line:
width: root.line_width width: root.line_width
rounded_rectangle: rounded_rectangle:
(self.x, self.y, self.width, self.height, \ ( \
self.x, self.y, self.width, self.height, \
root._radius, root._radius, root._radius, root._radius, \ root._radius, root._radius, root._radius, root._radius, \
self.height) self.height \
)
size_hint: None, None size_hint: None, None
anchor_x: root.halign anchor_x: root.halign
@ -33,21 +31,28 @@
_round_rad: [self._radius] * 4 _round_rad: [self._radius] * 4
<ButtonContentsText> <ButtonContentsText>
lbl_txt: lbl_txt lbl_txt: lbl_txt
width: width:
max(root._min_width, \ max( \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2]) root._min_width, \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
)
size_hint_min_x: size_hint_min_x:
max(root._min_width, \ max( \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2]) root._min_width, \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
)
height: height:
max(root._min_height, \ max( \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3]) root._min_height, \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
)
size_hint_min_y: size_hint_min_y:
max(root._min_height, \ max( \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3]) root._min_height, \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
)
MDLabel: MDLabel:
id: lbl_txt id: lbl_txt
@ -84,7 +89,10 @@
# This is only a temporary fix and does not fix the cause of the error. # This is only a temporary fix and does not fix the cause of the error.
(root._icon_color if root._icon_color else root.theme_cls.text_color) \ (root._icon_color if root._icon_color else root.theme_cls.text_color) \
if not root.disabled else \ if not root.disabled else \
root.theme_cls.disabled_hint_text_color root.theme_cls.disabled_hint_text_color \
if not root.disabled_color else \
root.disabled_color
on_icon: on_icon:
if self.icon not in md_icons.keys(): self.size_hint = (1, 1) if self.icon not in md_icons.keys(): self.size_hint = (1, 1)
theme_text_color: root._theme_icon_color theme_text_color: root._theme_icon_color
@ -131,7 +139,7 @@
id: box id: box
adaptive_size: True adaptive_size: True
padding: 0 padding: 0
spacing: "4dp" spacing: "8dp"
MDIcon: MDIcon:
id: lbl_ic id: lbl_ic
@ -193,48 +201,21 @@
radius: [self.height / 2] radius: [self.height / 2]
<BaseFloatingRootButton> <MDFloatingRootButton>
theme_text_color: "Custom" theme_text_color: "Custom"
md_bg_color: self.theme_cls.primary_color md_bg_color: self.theme_cls.primary_color
<MDFloatingLabel>
padding_x: "8dp"
padding_y: "8dp"
adaptive_size: True
theme_text_color: "Custom"
canvas.before: canvas.before:
PushMatrix
Rotate:
angle: self._angle
axis: (0, 0, 1)
origin: self.center
canvas.after:
PopMatrix
# FIXME: Use :class:`~kivymd.uix.boxlayout.MDBoxLayout` instead
# :class:`~kivy.uix.boxlayout.BoxLayout`.
<BaseFloatingLabel>
size_hint: None, None
padding: "8dp", "4dp", "8dp", "4dp"
height: label.texture_size[1] + self.padding[1] * 2
width: label.texture_size[0] + self.padding[0] * 2
elevation: 10
# TODO: Use `md_bg_color` and `radius` instead `canvasю
canvas:
Color: Color:
rgba: rgba: self.bg_color
self.theme_cls.primary_color \
if not root.bg_color else \
root.bg_color
RoundedRectangle: RoundedRectangle:
pos: self.pos
size: self.size size: self.size
radius: [5] pos: self.pos
radius: self.radius
Label:
id: label
markup: True
text: root.text
size_hint: None, None
size: self.texture_size
color:
root.theme_cls.text_color \
if not root.text_color else \
root.text_color

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,6 @@
md_bg_color: app.theme_cls.divider_color md_bg_color: app.theme_cls.divider_color
<MDCard>
canvas.before:
Color:
rgba: self.md_bg_color
RoundedRectangle:
size: self.size
pos: self.pos
radius: root.radius
source: root.background
<MDSeparator> <MDSeparator>
md_bg_color: md_bg_color:
self.theme_cls.divider_color \ self.theme_cls.divider_color \

View File

@ -26,26 +26,6 @@ Components/Card
MDCard MDCard
------ ------
.. warning:: Starting from the KivyMD 1.1.0 library version, it is necessary
to manually inherit the card class from one of the ``Elevation`` classes
from ``kivymd/uix/behaviors/elevation.py`` module to draw the card shadow.
.. code-block:: python
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
'''Implements a material design v3 card.'''
It actually allows for better control over the providers that implement the
rendering of the shadows.
.. note:: You can read more information about the classes that implement the
rendering of shadows on this
`documentation page <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/>`_.
An example of the implementation of a card in the style of material design version 3 An example of the implementation of a card in the style of material design version 3
------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------
@ -59,7 +39,6 @@ An example of the implementation of a card in the style of material design versi
from kivy.properties import StringProperty from kivy.properties import StringProperty
from kivymd.app import MDApp from kivymd.app import MDApp
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
KV = ''' KV = '''
@ -93,7 +72,7 @@ An example of the implementation of a card in the style of material design versi
''' '''
class MD3Card(MDCard, RoundedRectangularElevationBehavior): class MD3Card(MDCard):
'''Implements a material design v3 card.''' '''Implements a material design v3 card.'''
text = StringProperty() text = StringProperty()
@ -126,7 +105,6 @@ An example of the implementation of a card in the style of material design versi
.. code-block:: python .. code-block:: python
from kivymd.app import MDApp from kivymd.app import MDApp
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton from kivymd.uix.button import MDIconButton
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
@ -135,7 +113,7 @@ An example of the implementation of a card in the style of material design versi
from kivymd.uix.screen import MDScreen from kivymd.uix.screen import MDScreen
class MD3Card(MDCard, RoundedRectangularElevationBehavior): class MD3Card(MDCard):
'''Implements a material design v3 card.''' '''Implements a material design v3 card.'''
@ -170,7 +148,6 @@ An example of the implementation of a card in the style of material design versi
adaptive_size=True, adaptive_size=True,
color="grey", color="grey",
pos=("12dp", "12dp"), pos=("12dp", "12dp"),
bold=True,
), ),
), ),
line_color=(0.2, 0.2, 0.2, 0.8), line_color=(0.2, 0.2, 0.2, 0.8),
@ -255,10 +232,9 @@ End full code
MDBoxLayout: MDBoxLayout:
orientation: "vertical" orientation: "vertical"
spacing: "10dp"
MDTopAppBar: MDTopAppBar:
elevation: 10 elevation: 4
title: "MDCardSwipe" title: "MDCardSwipe"
MDScrollView: MDScrollView:
@ -286,7 +262,7 @@ End full code
'''Creates a list of cards.''' '''Creates a list of cards.'''
for i in range(20): for i in range(20):
self.screen.ids.md_list.add_widget( self.root.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}") SwipeToDeleteItem(text=f"One-line item {i}")
) )
@ -316,7 +292,7 @@ End full code
MDScreen( MDScreen(
MDBoxLayout( MDBoxLayout(
MDTopAppBar( MDTopAppBar(
elevation=10, elevation=4,
title="MDCardSwipe", title="MDCardSwipe",
), ),
MDScrollView( MDScrollView(
@ -328,7 +304,6 @@ End full code
), ),
id="box", id="box",
orientation="vertical", orientation="vertical",
spacing="10dp",
), ),
) )
) )
@ -426,7 +401,7 @@ You can use this event to remove items from a list:
.. code-block:: python .. code-block:: python
def on_swipe_complete(self, instance): def on_swipe_complete(self, instance):
self.screen.ids.md_list.remove_widget(instance) self.root.ids.md_list.remove_widget(instance)
.. tab:: Decralative python styles .. tab:: Decralative python styles
@ -496,10 +471,9 @@ End full code
MDBoxLayout: MDBoxLayout:
orientation: "vertical" orientation: "vertical"
spacing: "10dp"
MDTopAppBar: MDTopAppBar:
elevation: 10 elevation: 4
title: "MDCardSwipe" title: "MDCardSwipe"
MDScrollView: MDScrollView:
@ -560,7 +534,7 @@ End full code
MDScreen( MDScreen(
MDBoxLayout( MDBoxLayout(
MDTopAppBar( MDTopAppBar(
elevation=10, elevation=4,
title="MDCardSwipe", title="MDCardSwipe",
), ),
MDScrollView( MDScrollView(
@ -572,7 +546,6 @@ End full code
), ),
id="box", id="box",
orientation="vertical", orientation="vertical",
spacing="10dp",
), ),
) )
) )
@ -630,26 +603,21 @@ Focus behavior
from kivy.lang import Builder from kivy.lang import Builder
from kivymd.app import MDApp from kivymd.app import MDApp
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.card import MDCard
KV = ''' KV = '''
MDScreen: MDScreen:
ElevationCard: MDCard:
size_hint: .7, .4 size_hint: .7, .4
focus_behavior: True focus_behavior: True
pos_hint: {"center_x": .5, "center_y": .5} pos_hint: {"center_x": .5, "center_y": .5}
md_bg_color: "darkgrey" md_bg_color: "darkgrey"
unfocus_color: "darkgrey" unfocus_color: "darkgrey"
focus_color: "grey" focus_color: "grey"
elevation: 6
''' '''
class ElevationCard(MDCard, FakeRectangularElevationBehavior):
pass
class Example(MDApp): class Example(MDApp):
def build(self): def build(self):
self.theme_cls.theme_style = "Dark" self.theme_cls.theme_style = "Dark"
@ -663,27 +631,23 @@ Focus behavior
.. code-block:: python .. code-block:: python
from kivymd.app import MDApp from kivymd.app import MDApp
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
from kivymd.uix.screen import MDScreen from kivymd.uix.screen import MDScreen
class ElevationCard(MDCard, FakeRectangularElevationBehavior):
pass
class Example(MDApp): class Example(MDApp):
def build(self): def build(self):
self.theme_cls.theme_style = "Dark" self.theme_cls.theme_style = "Dark"
return ( return (
MDScreen( MDScreen(
ElevationCard( MDCard(
size_hint=(0.7, 0.4), size_hint=(0.7, 0.4),
focus_behavior=True, focus_behavior=True,
pos_hint={"center_x": 0.5, "center_y": 0.5}, pos_hint={"center_x": 0.5, "center_y": 0.5},
md_bg_color="darkgrey", md_bg_color="darkgrey",
unfocus_color="darkgrey", unfocus_color="darkgrey",
focus_color="grey", focus_color="grey",
elevation=6,
), ),
) )
) )
@ -691,7 +655,6 @@ Focus behavior
Example().run() Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif
:align: center :align: center
@ -732,12 +695,14 @@ from kivy.properties import (
VariableListProperty, VariableListProperty,
) )
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.utils import get_color_from_hex
from kivymd import uix_path from kivymd import uix_path
from kivymd.color_definitions import colors from kivymd.color_definitions import colors
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import (
BackgroundColorBehavior, BackgroundColorBehavior,
CommonElevationBehavior,
DeclarativeBehavior, DeclarativeBehavior,
RectangularRippleBehavior, RectangularRippleBehavior,
) )
@ -781,6 +746,7 @@ class MDCard(
ThemableBehavior, ThemableBehavior,
BackgroundColorBehavior, BackgroundColorBehavior,
RectangularRippleBehavior, RectangularRippleBehavior,
CommonElevationBehavior,
FocusBehavior, FocusBehavior,
BoxLayout, BoxLayout,
): ):
@ -800,14 +766,6 @@ class MDCard(
and defaults to `False`. and defaults to `False`.
""" """
elevation = NumericProperty(None, allownone=True)
"""
Elevation value.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to 1.
"""
radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)]) radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)])
""" """
Card radius by default. Card radius by default.
@ -831,15 +789,16 @@ class MDCard(
""" """
_bg_color_map = ( _bg_color_map = (
colors["Light"]["CardsDialogs"], get_color_from_hex(colors["Light"]["CardsDialogs"]),
colors["Dark"]["CardsDialogs"], get_color_from_hex(colors["Dark"]["CardsDialogs"]),
[1.0, 1.0, 1.0, 0.0], [1.0, 1.0, 1.0, 0.0],
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.theme_cls.bind(theme_style=self.update_md_bg_color) self.theme_cls.bind(
self.theme_cls.bind(material_style=self.set_style) material_style=self.set_style, theme_style=self.update_md_bg_color
)
Clock.schedule_once(self.set_style) Clock.schedule_once(self.set_style)
Clock.schedule_once( Clock.schedule_once(
lambda x: self.on_ripple_behavior(0, self.ripple_behavior) lambda x: self.on_ripple_behavior(0, self.ripple_behavior)
@ -848,7 +807,9 @@ class MDCard(
def update_md_bg_color(self, instance_card, theme_style: str) -> None: def update_md_bg_color(self, instance_card, theme_style: str) -> None:
if self.md_bg_color in self._bg_color_map: if self.md_bg_color in self._bg_color_map:
self.md_bg_color = colors[theme_style]["CardsDialogs"] self.md_bg_color = get_color_from_hex(
colors[theme_style]["CardsDialogs"]
)
def set_style(self, *args) -> None: def set_style(self, *args) -> None:
self.set_radius() self.set_radius()
@ -865,7 +826,7 @@ class MDCard(
if self.style == "outlined" or self.style == "filled": if self.style == "outlined" or self.style == "filled":
self.elevation = 0 self.elevation = 0
elif self.style == "elevated": elif self.style == "elevated":
self.elevation = 1 self.elevation = 2
def set_radius(self) -> None: def set_radius(self) -> None:
if ( if (

View File

@ -132,7 +132,7 @@ Use with elevation
icon_right: "close-circle-outline" icon_right: "close-circle-outline"
line_color: app.theme_cls.disabled_hint_text_color line_color: app.theme_cls.disabled_hint_text_color
md_bg_color: 1, 0, 0, .5 md_bg_color: 1, 0, 0, .5
elevation: 12 elevation: 4
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-with-elevation.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-with-elevation.png
:align: center :align: center
@ -304,7 +304,6 @@ __all__ = ("MDChip",)
import os import os
from kivy import Logger
from kivy.animation import Animation from kivy.animation import Animation
from kivy.lang import Builder from kivy.lang import Builder
from kivy.metrics import dp from kivy.metrics import dp
@ -314,14 +313,13 @@ from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path from kivymd import uix_path
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import (
FakeRectangularElevationBehavior, CommonElevationBehavior,
RectangularRippleBehavior, RectangularRippleBehavior,
ScaleBehavior,
TouchBehavior, TouchBehavior,
) )
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDIcon from kivymd.uix.label import MDIcon
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.templates import ScaleWidget
with open( with open(
os.path.join(uix_path, "chip", "chip.kv"), encoding="utf-8" os.path.join(uix_path, "chip", "chip.kv"), encoding="utf-8"
@ -330,12 +328,12 @@ with open(
class MDChip( class MDChip(
MDBoxLayout,
ThemableBehavior, ThemableBehavior,
RectangularRippleBehavior, RectangularRippleBehavior,
FakeRectangularElevationBehavior,
TouchBehavior,
ButtonBehavior, ButtonBehavior,
MDBoxLayout, CommonElevationBehavior,
TouchBehavior,
): ):
text = StringProperty() text = StringProperty()
""" """
@ -456,7 +454,7 @@ class MDChip(
self.active = False self.active = False
class MDScalableCheckIcon(MDIcon, ScaleWidget): class MDScalableCheckIcon(MDIcon, ScaleBehavior):
pos_hint = {"center_y": 0.5} pos_hint = {"center_y": 0.5}

View File

@ -1,5 +1,4 @@
#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE #:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE
#:import FakeRectangularElevationBehavior kivymd.uix.behaviors.FakeRectangularElevationBehavior
<CellRow> <CellRow>
@ -66,7 +65,7 @@
size_hint_y: None size_hint_y: None
height: self.minimum_height height: self.minimum_height
spacing: "4dp" spacing: "4dp"
tooltip_text: root.text tooltip_text: root.tooltip if root.tooltip else root.text
BoxLayout: BoxLayout:
id: box id: box
@ -175,7 +174,11 @@
font_size: "14sp" font_size: "14sp"
on_release: root.table_data.open_pagination_menu() on_release: root.table_data.open_pagination_menu()
text: text:
f"{root.table_data.rows_num if root.table_data.rows_num < len(root.table_data.row_data) else len(root.table_data.row_data)}" "{}".format( \
root.table_data.rows_num \
if root.table_data.rows_num < len(root.table_data.row_data) else \
len(root.table_data.row_data) \
)
Widget: Widget:
size_hint_x: None size_hint_x: None
@ -192,9 +195,11 @@
if root.theme_cls.theme_style == "Dark" else \ if root.theme_cls.theme_style == "Dark" else \
(0, 0, 0, 1) (0, 0, 0, 1)
text: text:
f"1-" \ "1-{} of {}".format( \
f"{root.table_data.rows_num if root.table_data.rows_num > len(root.table_data.row_data) else len(root.table_data.row_data)} " \ root.table_data.rows_num \
f"of {len(root.table_data.row_data)}" if root.table_data.rows_num > len(root.table_data.row_data) else \
len(root.table_data.row_data), len(root.table_data.row_data) \
)
MDIconButton: MDIconButton:
id: button_back id: button_back
@ -217,7 +222,7 @@
on_release: root.table_data.set_next_row_data_parts("forward") on_release: root.table_data.set_next_row_data_parts("forward")
<TableContainer@MDCard+FakeRectangularElevationBehavior> <TableContainer@MDCard>
<MDDataTable> <MDDataTable>

View File

@ -11,19 +11,6 @@ Components/DataTables
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png
:align: center :align: center
Warnings
---------
.. warning:: Data tables are still far from perfect. The class is in constant
change, because of optimizations and bug fixes. If you find a bug or have
an improvement you want to share, take some time and share your discoveries
with us over the main git repo.
Any help is well appreciated.
.. warning:: In versions prior to `Kivy 2.1.0-dev0` exists an error in which is
the table has only one row in the current page, the table will only render
one column instead of the whole row.
.. note:: `MDDataTable` allows developers to sort the data provided by column. .. note:: `MDDataTable` allows developers to sort the data provided by column.
This happens thanks to the use of an external function that you can bind This happens thanks to the use of an external function that you can bind
while you're defining the table columns. Be aware that the sorting function while you're defining the table columns. Be aware that the sorting function
@ -159,6 +146,15 @@ class CellHeader(MDTooltip, BoxLayout):
and defaults to `''`. and defaults to `''`.
""" """
tooltip = StringProperty()
"""
Tooltip containing descriptive text for the column.
If the tooltip is not provided, column `text` shall be used instead.
:attr:`tooltip` is a :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
# TODO: Added example. # TODO: Added example.
sort_action = ObjectProperty() sort_action = ObjectProperty()
""" """
@ -340,11 +336,19 @@ class TableHeader(ThemableBehavior, ScrollView):
CellHeader( CellHeader(
text=col_heading[0], text=col_heading[0],
sort_action=col_heading[2], sort_action=col_heading[2],
tooltip=col_heading[3],
width=self.cols_minimum[i], width=self.cols_minimum[i],
table_data=self.table_data, table_data=self.table_data,
is_sorted=(col_heading[0] == self.sorted_on), is_sorted=(col_heading[0] == self.sorted_on),
sorted_order=self.sorted_order, sorted_order=self.sorted_order,
) )
if len(col_heading) == 4
else CellHeader(
text=col_heading[0],
sort_action=col_heading[2],
width=self.cols_minimum[i],
table_data=self.table_data,
)
if len(col_heading) == 3 if len(col_heading) == 3
else CellHeader( else CellHeader(
text=col_heading[0], text=col_heading[0],
@ -356,6 +360,9 @@ class TableHeader(ThemableBehavior, ScrollView):
else: else:
# Sets the text in the first cell. # Sets the text in the first cell.
self.ids.first_cell.text = col_heading[0] self.ids.first_cell.text = col_heading[0]
self.ids.first_cell.tooltip = (
col_heading[3] if len(col_heading) == 4 else ""
)
self.ids.first_cell.ids.separator.height = 0 self.ids.first_cell.ids.separator.height = 0
self.ids.first_cell.width = self.cols_minimum[i] self.ids.first_cell.width = self.cols_minimum[i]
@ -765,6 +772,9 @@ class TablePagination(ThemableBehavior, MDBoxLayout):
class MDDataTable(ThemableBehavior, AnchorLayout): class MDDataTable(ThemableBehavior, AnchorLayout):
""" """
See :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation for
more information.
:Events: :Events:
:attr:`on_row_press` :attr:`on_row_press`
Called when a table row is clicked. Called when a table row is clicked.
@ -775,7 +785,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. code-block:: python .. code-block:: python
from kivy.metrics import dp from kivy.metrics import dp
from kivymd.app import MDApp from kivymd.app import MDApp
@ -914,38 +923,82 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
""" """
Data for header columns. Data for header columns.
.. code-block:: python .. tabs::
from kivy.metrics import dp .. tab:: Imperative python style
from kivymd.app import MDApp .. code-block:: python
from kivymd.uix.datatables import MDDataTable
from kivy.uix.anchorlayout import AnchorLayout from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivy.uix.anchorlayout import AnchorLayout
class Example(MDApp): class Example(MDApp):
def build(self): def build(self):
layout = AnchorLayout() self.theme_cls.theme_style = "Dark"
self.data_tables = MDDataTable( self.theme_cls.primary_palette = "Orange"
size_hint=(0.7, 0.6),
use_pagination=True, layout = AnchorLayout()
check=True, self.data_tables = MDDataTable(
# name column, width column, sorting function column(optional) size_hint=(0.7, 0.6),
column_data=[ use_pagination=True,
("No.", dp(30)), check=True,
("Status", dp(30)), # name column, width column, sorting function column(optional), custom tooltip
("Signal Name", dp(60)), column_data=[
("Severity", dp(30)), ("No.", dp(30), None, "Custom tooltip"),
("Stage", dp(30)), ("Status", dp(30)),
("Schedule", dp(30), lambda *args: print("Sorted using Schedule")), ("Signal Name", dp(60)),
("Team Lead", dp(30)), ("Severity", dp(30)),
], ("Stage", dp(30)),
) ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")),
layout.add_widget(self.data_tables) ("Team Lead", dp(30)),
return layout ],
)
layout.add_widget(self.data_tables)
return layout
Example().run() Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.anchorlayout import MDAnchorLayout
from kivymd.uix.datatables import MDDataTable
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return MDAnchorLayout(
MDDataTable(
size_hint=(0.7, 0.6),
use_pagination=True,
check=True,
# name column, width column, sorting function column(optional)
column_data=[
("No.", dp(30)),
("Status", dp(30)),
("Signal Name", dp(60)),
("Severity", dp(30)),
("Stage", dp(30)),
("Schedule", dp(30),
lambda *args: print("Sorted using Schedule")),
("Team Lead", dp(30)),
],
)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png
:align: center :align: center
@ -1060,6 +1113,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp): class Example(MDApp):
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout() layout = AnchorLayout()
data_tables = MDDataTable( data_tables = MDDataTable(
size_hint=(0.9, 0.6), size_hint=(0.9, 0.6),
@ -1187,7 +1243,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
""" """
Use or not use checkboxes for rows. Use or not use checkboxes for rows.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.png
:align: center :align: center
:attr:`check` is an :class:`~kivy.properties.BooleanProperty` :attr:`check` is an :class:`~kivy.properties.BooleanProperty`
@ -1209,6 +1265,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp): class Example(MDApp):
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout() layout = AnchorLayout()
data_tables = MDDataTable( data_tables = MDDataTable(
size_hint=(0.9, 0.6), size_hint=(0.9, 0.6),
@ -1238,19 +1297,19 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
and defaults to `False`. and defaults to `False`.
""" """
elevation = NumericProperty(8) elevation = NumericProperty(4)
""" """
Table elevation. Table elevation.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty` :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `8`. and defaults to `4`.
""" """
rows_num = NumericProperty(5) rows_num = NumericProperty(5)
""" """
The number of rows displayed on one page of the table. The number of rows displayed on one page of the table.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination-rows-num.png
:align: center :align: center
:attr:`rows_num` is an :class:`~kivy.properties.NumericProperty` :attr:`rows_num` is an :class:`~kivy.properties.NumericProperty`
@ -1266,7 +1325,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. rubric:: Center .. rubric:: Center
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-center.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-top.png
:align: center :align: center
.. rubric:: Auto .. rubric:: Auto
@ -1282,11 +1341,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
""" """
Menu height for selecting the number of displayed rows. Menu height for selecting the number of displayed rows.
.. rubric:: 140dp
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-140.png
:align: center
.. rubric:: 240dp .. rubric:: 240dp
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png
@ -1298,7 +1352,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color = ColorProperty([0, 0, 0, 0]) background_color = ColorProperty([0, 0, 0, 0])
""" """
Background color in the format (r, g, b, a). Background color in the format (r, g, b, a) or string format.
See :attr:`~kivy.uix.modalview.ModalView.background_color`. See :attr:`~kivy.uix.modalview.ModalView.background_color`.
Use markup strings Use markup strings
@ -1315,6 +1369,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp): class Example(MDApp):
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout() layout = AnchorLayout()
data_tables = MDDataTable( data_tables = MDDataTable(
size_hint=(0.9, 0.6), size_hint=(0.9, 0.6),
@ -1354,7 +1411,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_header = ColorProperty(None) background_color_header = ColorProperty(None)
""" """
Background color for :class:`~TableHeader` class. Background color in the format (r, g, b, a) or string format for
:class:`~TableHeader` class.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -1374,7 +1432,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_cell = ColorProperty(None) background_color_cell = ColorProperty(None)
""" """
Background color for :class:`~CellRow` class. Background color in the format (r, g, b, a) or string format for
:class:`~CellRow` class.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -1395,7 +1454,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_selected_cell = ColorProperty(None) background_color_selected_cell = ColorProperty(None)
""" """
Background selected color for :class:`~CellRow` class. Background selected color in the format (r, g, b, a) or string format for
:class:`~CellRow` class.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -1408,7 +1468,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
background_color_selected_cell="e4514f", background_color_selected_cell="e4514f",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.png
:align: center :align: center
:attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` and :attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` and
@ -1503,6 +1563,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
data_tables = None data_tables = None
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = MDFloatLayout() # root layout layout = MDFloatLayout() # root layout
# Creating control buttons. # Creating control buttons.
button_box = MDBoxLayout( button_box = MDBoxLayout(
@ -1604,6 +1667,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
data_tables = None data_tables = None
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = MDFloatLayout() layout = MDFloatLayout()
layout.add_widget( layout.add_widget(
MDRaisedButton( MDRaisedButton(

View File

@ -18,7 +18,11 @@
PopMatrix PopMatrix
<DialogContainer@MDCard+FakeRectangularElevationBehavior> <DialogContainer@MDCard>
shadow_color: 0.0, 0.0, 0.0, 0.0
elevation: 0
shadow_softness: 0
shadow_offset: 0, 0
<MDDialog> <MDDialog>
@ -28,7 +32,6 @@
orientation: "vertical" orientation: "vertical"
size_hint_y: None size_hint_y: None
height: self.minimum_height height: self.minimum_height
elevation: 24
padding: "24dp", "24dp", "8dp", "8dp" padding: "24dp", "24dp", "8dp", "8dp"
radius: root.radius radius: root.radius
md_bg_color: md_bg_color:

View File

@ -38,6 +38,8 @@ Usage
dialog = None dialog = None
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV) return Builder.load_string(KV)
def show_alert_dialog(self): def show_alert_dialog(self):
@ -87,6 +89,7 @@ from kivy.uix.modalview import ModalView
from kivymd import uix_path from kivymd import uix_path
from kivymd.material_resources import DEVICE_TYPE from kivymd.material_resources import DEVICE_TYPE
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.button import BaseButton from kivymd.uix.button import BaseButton
from kivymd.uix.card import MDSeparator from kivymd.uix.card import MDSeparator
from kivymd.uix.list import BaseListItem from kivymd.uix.list import BaseListItem
@ -97,7 +100,40 @@ with open(
Builder.load_string(kv_file.read()) Builder.load_string(kv_file.read())
class BaseDialog(ThemableBehavior, ModalView): class BaseDialog(ThemableBehavior, ModalView, CommonElevationBehavior):
elevation = NumericProperty(3)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
attribute for more information.
.. versionadded:: 1.1.0
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `3`.
"""
shadow_softness = NumericProperty(24)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
attribute for more information.
.. versionadded:: 1.1.0
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
and defaults to `24`.
"""
shadow_offset = ListProperty((0, 4))
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
attribute for more information.
.. versionadded:: 1.1.0
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
and defaults to `[0, 4]`.
"""
radius = ListProperty([dp(7), dp(7), dp(7), dp(7)]) radius = ListProperty([dp(7), dp(7), dp(7), dp(7)])
""" """
Dialog corners rounding value. Dialog corners rounding value.
@ -250,21 +286,22 @@ class MDDialog(BaseDialog):
class Example(MDApp): class Example(MDApp):
dialog = None dialog = None
def build(self): def build(self):
return Builder.load_string(KV) self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_simple_dialog(self): def show_simple_dialog(self):
if not self.dialog: if not self.dialog:
self.dialog = MDDialog( self.dialog = MDDialog(
title="Set backup account", title="Set backup account",
type="simple", type="simple",
items=[ items=[
Item(text="user01@gmail.com", source="user-1.png"), Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"),
Item(text="user02@gmail.com", source="user-2.png"), Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"),
Item(text="Add account", source="add-icon.png"), ],
], )
) self.dialog.open()
self.dialog.open()
Example().run() Example().run()
@ -317,6 +354,8 @@ class MDDialog(BaseDialog):
dialog = None dialog = None
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV) return Builder.load_string(KV)
def show_confirmation_dialog(self): def show_confirmation_dialog(self):
@ -385,71 +424,140 @@ class MDDialog(BaseDialog):
""" """
Custom content class. Custom content class.
.. code-block:: python .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivy.uix.boxlayout import BoxLayout
from kivymd.app import MDApp .. code-block:: python
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
KV = ''' from kivy.lang import Builder
<Content> from kivy.uix.boxlayout import BoxLayout
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
MDTextField: from kivymd.app import MDApp
hint_text: "City" from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
MDTextField: KV = '''
hint_text: "Street" <Content>
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
MDTextField:
hint_text: "City"
MDTextField:
hint_text: "Street"
MDFloatLayout: MDFloatLayout:
MDFlatButton: MDFlatButton:
text: "ALERT DIALOG" text: "ALERT DIALOG"
pos_hint: {'center_x': .5, 'center_y': .5} pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_confirmation_dialog() on_release: app.show_confirmation_dialog()
''' '''
class Content(BoxLayout): class Content(BoxLayout):
pass pass
class Example(MDApp): class Example(MDApp):
dialog = None dialog = None
def build(self): def build(self):
return Builder.load_string(KV) self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_confirmation_dialog(self): def show_confirmation_dialog(self):
if not self.dialog: if not self.dialog:
self.dialog = MDDialog( self.dialog = MDDialog(
title="Address:", title="Address:",
type="custom", type="custom",
content_cls=Content(), content_cls=Content(),
buttons=[ buttons=[
MDFlatButton( MDFlatButton(
text="CANCEL", text="CANCEL",
theme_text_color="Custom", theme_text_color="Custom",
text_color=self.theme_cls.primary_color, text_color=self.theme_cls.primary_color,
), ),
MDFlatButton( MDFlatButton(
text="OK", text="OK",
theme_text_color="Custom", theme_text_color="Custom",
text_color=self.theme_cls.primary_color, text_color=self.theme_cls.primary_color,
), ),
], ],
) )
self.dialog.open() self.dialog.open()
Example().run() Example().run()
.. tab:: Declarative Python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.textfield import MDTextField
class Example(MDApp):
dialog = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDFloatLayout(
MDFlatButton(
text="ALERT DIALOG",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
on_release=self.show_confirmation_dialog,
)
)
)
def show_confirmation_dialog(self, *args):
if not self.dialog:
self.dialog = MDDialog(
title="Address:",
type="custom",
content_cls=MDBoxLayout(
MDTextField(
hint_text="City",
),
MDTextField(
hint_text="Street",
),
orientation="vertical",
spacing="12dp",
size_hint_y=None,
height="120dp",
),
buttons=[
MDFlatButton(
text="CANCEL",
theme_text_color="Custom",
text_color=self.theme_cls.primary_color,
),
MDFlatButton(
text="OK",
theme_text_color="Custom",
text_color=self.theme_cls.primary_color,
),
],
)
self.dialog.open()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png
:align: center :align: center
@ -460,7 +568,7 @@ class MDDialog(BaseDialog):
md_bg_color = ColorProperty(None) md_bg_color = ColorProperty(None)
""" """
Background color in the format (r, g, b, a). Background color in the (r, g, b, a) or string format.
:attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`. and defaults to `None`.

View File

@ -1,7 +1,7 @@
<_Triangle>: <_Triangle>:
canvas: canvas:
Color: Color:
rgba: root.theme_cls.text_color rgba: app.theme_cls.text_color
Triangle: Triangle:
points: points:
[ \ [ \
@ -13,7 +13,8 @@
<MDDropDownItem> <MDDropDownItem>
orientation: "vertical" orientation: "vertical"
adaptive_size: True size_hint: None, None
size: self.minimum_size
spacing: "5dp" spacing: "5dp"
padding: "5dp", "5dp", "5dp", 0 padding: "5dp", "5dp", "5dp", 0

View File

@ -15,13 +15,13 @@ Usage
from kivymd.app import MDApp from kivymd.app import MDApp
KV = ''' KV = '''
Screen MDScreen
MDDropDownItem: MDDropDownItem:
id: drop_item id: drop_item
pos_hint: {'center_x': .5, 'center_y': .5} pos_hint: {'center_x': .5, 'center_y': .5}
text: 'Item' text: 'Item'
on_release: self.set_item("New Item") on_release: print("Press item")
''' '''
@ -48,12 +48,12 @@ import os
from kivy.lang import Builder from kivy.lang import Builder
from kivy.properties import NumericProperty, StringProperty from kivy.properties import NumericProperty, StringProperty
from kivy.uix.behaviors import ButtonBehavior from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from kivymd import uix_path from kivymd import uix_path
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import FakeRectangularElevationBehavior from kivymd.uix.behaviors import DeclarativeBehavior
from kivymd.uix.boxlayout import MDBoxLayout
with open( with open(
os.path.join(uix_path, "dropdownitem", "dropdownitem.kv"), encoding="utf-8" os.path.join(uix_path, "dropdownitem", "dropdownitem.kv"), encoding="utf-8"
@ -61,15 +61,12 @@ with open(
Builder.load_string(kv_file.read()) Builder.load_string(kv_file.read())
class _Triangle(ThemableBehavior, Widget): class _Triangle(Widget):
pass pass
class MDDropDownItem( class MDDropDownItem(
ThemableBehavior, DeclarativeBehavior, ThemableBehavior, ButtonBehavior, BoxLayout
FakeRectangularElevationBehavior,
ButtonBehavior,
MDBoxLayout,
): ):
text = StringProperty() text = StringProperty()
""" """

View File

@ -6,28 +6,29 @@
background_normal: "" background_normal: ""
background_down: "" background_down: ""
dir_or_file_name: "" dir_or_file_name: ""
icon_color: 0, 0, 0, 0
_selected: False _selected: False
events_callback: lambda x: None events_callback: lambda x: None
orientation: "vertical" orientation: "vertical"
ModifiedOneLineIconListItem: ModifiedOneLineIconListItem:
text: root.dir_or_file_name text: root.dir_or_file_name
on_release: root.events_callback(root.path, root)
bg_color: bg_color:
self.theme_cls.bg_darkest \ self.theme_cls.bg_darkest \
if root._selected else self.theme_cls.bg_normal if root._selected else \
on_release: root.events_callback(root.path, root) self.theme_cls.bg_normal
IconLeftWidget: IconLeftWidget:
icon: root.icon icon: root.icon
theme_text_color: "Custom" theme_icon_color: "Custom"
text_color: self.theme_cls.primary_color icon_color: root.icon_color
MDSeparator: MDSeparator:
<LabelContent@MDLabel> <LabelContent@MDLabel>
size_hint_y: None adaptive_height: True
height: self.texture_size[1]
shorten: True shorten: True
shorten_from: "center" shorten_from: "center"
halign: "center" halign: "center"
@ -61,23 +62,6 @@
text: root.name text: root.name
<FloatButton>
anchor_x: "right"
anchor_y: "bottom"
size_hint_y: None
height: dp(56)
padding: dp(10)
MDFloatingActionButton:
size_hint: None, None
size:dp(56), dp(56)
icon: root.icon
opposite_colors: True
elevation: 8
on_release: root.callback()
md_bg_color: root.md_bg_color
<MDFileManager> <MDFileManager>
md_bg_color: root.theme_cls.bg_normal md_bg_color: root.theme_cls.bg_normal
@ -90,7 +74,11 @@
title: root.current_path title: root.current_path
right_action_items: [["close-box", lambda x: root.exit_manager(1)]] right_action_items: [["close-box", lambda x: root.exit_manager(1)]]
left_action_items: [["chevron-left", lambda x: root.back()]] left_action_items: [["chevron-left", lambda x: root.back()]]
elevation: 10 elevation: 3
md_bg_color:
app.theme_cls.primary_color \
if not root.background_color_toolbar else \
root.background_color_toolbar
RecycleView: RecycleView:
id: rv id: rv

View File

@ -9,7 +9,7 @@ Usage
.. code-block:: python .. code-block:: python
path = '/' # path to the directory that will be opened in the file manager path = os.path.expanduser("~") # path to the directory that will be opened in the file manager
file_manager = MDFileManager( file_manager = MDFileManager(
exit_manager=self.exit_manager, # function called when the user reaches directory tree root exit_manager=self.exit_manager, # function called when the user reaches directory tree root
select_path=self.select_path, # function called when selecting a file/directory select_path=self.select_path, # function called when selecting a file/directory
@ -19,7 +19,7 @@ Usage
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png
:align: center :align: center
.. warning:: Be careful! To use the `/` path on Android devices, you need .. warning:: Be careful! To use the `'/'` path on Android devices, you need
special permissions. Therefore, you are likely to get an error. special permissions. Therefore, you are likely to get an error.
Or with ``preview`` mode: Or with ``preview`` mode:
@ -32,7 +32,7 @@ Or with ``preview`` mode:
preview=True, preview=True,
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-previous.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-preview.png
:align: center :align: center
.. warning:: The `preview` mode is intended only for viewing images and will .. warning:: The `preview` mode is intended only for viewing images and will
@ -43,6 +43,8 @@ Example
.. code-block:: python .. code-block:: python
import os
from kivy.core.window import Window from kivy.core.window import Window
from kivy.lang import Builder from kivy.lang import Builder
@ -53,19 +55,19 @@ Example
KV = ''' KV = '''
MDBoxLayout: MDBoxLayout:
orientation: 'vertical' orientation: "vertical"
MDTopAppBar: MDTopAppBar:
title: "MDFileManager" title: "MDFileManager"
left_action_items: [['menu', lambda x: None]] left_action_items: [["menu", lambda x: None]]
elevation: 10 elevation: 3
MDFloatLayout: MDFloatLayout:
MDRoundFlatIconButton: MDRoundFlatIconButton:
text: "Open manager" text: "Open manager"
icon: "folder" icon: "folder"
pos_hint: {'center_x': .5, 'center_y': .6} pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.file_manager_open() on_release: app.file_manager_open()
''' '''
@ -76,23 +78,23 @@ Example
Window.bind(on_keyboard=self.events) Window.bind(on_keyboard=self.events)
self.manager_open = False self.manager_open = False
self.file_manager = MDFileManager( self.file_manager = MDFileManager(
exit_manager=self.exit_manager, exit_manager=self.exit_manager, select_path=self.select_path
select_path=self.select_path,
preview=True,
) )
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV) return Builder.load_string(KV)
def file_manager_open(self): def file_manager_open(self):
self.file_manager.show('/') # output manager to the screen self.file_manager.show(os.path.expanduser("~")) # output manager to the screen
self.manager_open = True self.manager_open = True
def select_path(self, path): def select_path(self, path: str):
'''It will be called when you click on the file name '''
It will be called when you click on the file name
or the catalog selection button. or the catalog selection button.
:type path: str;
:param path: path to the selected directory or file; :param path: path to the selected directory or file;
''' '''
@ -126,6 +128,9 @@ Not tested on `iOS`.
def file_manager_open(self): def file_manager_open(self):
self.file_manager.show_disks() self.file_manager.show_disks()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-show-disks.png
:align: center
""" """
__all__ = ("MDFileManager",) __all__ = ("MDFileManager",)
@ -136,6 +141,7 @@ import re
from typing import List, Tuple, Union from typing import List, Tuple, Union
from kivy import platform from kivy import platform
from kivy.clock import Clock
from kivy.factory import Factory from kivy.factory import Factory
from kivy.lang import Builder from kivy.lang import Builder
from kivy.metrics import dp from kivy.metrics import dp
@ -148,7 +154,6 @@ from kivy.properties import (
OptionProperty, OptionProperty,
StringProperty, StringProperty,
) )
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.modalview import ModalView from kivy.uix.modalview import ModalView
@ -156,6 +161,7 @@ from kivymd import images_path, uix_path
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import CircularRippleBehavior from kivymd.uix.behaviors import CircularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFloatingActionButton
from kivymd.uix.fitimage import FitImage from kivymd.uix.fitimage import FitImage
from kivymd.uix.list import BaseListItem from kivymd.uix.list import BaseListItem
from kivymd.uix.relativelayout import MDRelativeLayout from kivymd.uix.relativelayout import MDRelativeLayout
@ -167,9 +173,7 @@ with open(
class BodyManager(MDBoxLayout): class BodyManager(MDBoxLayout):
""" """Base class for folders and files icons."""
Base class for folders and files icons.
"""
class BodyManagerWithPreview(MDBoxLayout): class BodyManagerWithPreview(MDBoxLayout):
@ -182,47 +186,146 @@ class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage):
"""Folder icons/thumbnails images in ``preview`` mode.""" """Folder icons/thumbnails images in ``preview`` mode."""
class FloatButton(ThemableBehavior, AnchorLayout):
callback = ObjectProperty()
md_bg_color = ColorProperty([1, 1, 1, 1])
icon = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.bind(primary_palette=self.set_md_bg_color)
def set_md_bg_color(self, *args):
self.md_bg_color = self.theme_cls.primary_color
class ModifiedOneLineIconListItem(BaseListItem): class ModifiedOneLineIconListItem(BaseListItem):
_txt_left_pad = NumericProperty("72dp") _txt_left_pad = NumericProperty("72dp")
_txt_top_pad = NumericProperty("16dp") _txt_top_pad = NumericProperty("16dp")
_txt_bot_pad = NumericProperty("15dp") _txt_bot_pad = NumericProperty("15dp")
_num_lines = 1 _num_lines = 1
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(**kwargs) super().__init__(*args, **kwargs)
self.height = dp(48) self.height = dp(48)
class MDFileManager(ThemableBehavior, MDRelativeLayout): class MDFileManager(MDRelativeLayout, ThemableBehavior):
icon = StringProperty("check")
""" """
The icon that will be used on the directory selection button. Implements a modal dialog with a file manager.
For more information, see in the
:class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation.
:Events:
`on_pre_open`:
Called before the MDFileManager is opened.
`on_open`:
Called when the MDFileManager is opened.
`on_pre_dismiss`:
Called before the MDFileManager is closed.
`on_dismiss`:
Called when the MDFileManager is closed.
"""
icon = StringProperty("check", deprecated=True)
"""
Icon that will be used on the directory selection button.
.. deprecated:: 1.1.0
Use :attr:`icon_selection_button` instead.
:attr:`icon` is an :class:`~kivy.properties.StringProperty` :attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `check`. and defaults to `check`.
""" """
icon_selection_button = StringProperty("check")
"""
Icon that will be used on the directory selection button.
.. versionadded:: 1.1.0
.. code-block:: python
MDFileManager(
...
icon_selection_button="pencil",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-selection-button.png
:align: center
:attr:`icon_selection_button` is an :class:`~kivy.properties.StringProperty`
and defaults to `check`.
"""
background_color_selection_button = ColorProperty(None)
"""
Background color of the current directory/path selection button.
.. versionadded:: 1.1.0
.. code-block:: python
MDFileManager(
...
background_color_selection_button="brown",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-background-color-selection-button.png
:align: center
:attr:`background_color_selection_button` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
background_color_toolbar = ColorProperty(None)
"""
Background color of the file manager toolbar.
.. versionadded:: 1.1.0
.. code-block:: python
MDFileManager(
...
background_color_toolbar="brown",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-background-color-toolbar.png
:align: center
:attr:`background_color_toolbar` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
icon_folder = StringProperty(f"{images_path}folder.png") icon_folder = StringProperty(f"{images_path}folder.png")
""" """
The icon that will be used for folder icons when using ``preview = True``. Icon that will be used for folder icons when using ``preview = True``.
.. code-block:: python
MDFileManager(
...
preview=True,
icon_folder="path/to/icon.png",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-folder.png
:align: center
:attr:`icon` is an :class:`~kivy.properties.StringProperty` :attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `check`. and defaults to `check`.
""" """
icon_color = ColorProperty(None)
"""
Color of the folder icon when the :attr:`preview` property is set to False.
.. versionadded:: 1.1.0
.. code-block:: python
MDFileManager(
...
preview=False,
icon_color="brown",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager-icon-color.png
:align: center
:attr:`icon_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
exit_manager = ObjectProperty(lambda x: None) exit_manager = ObjectProperty(lambda x: None)
""" """
Function called when the user reaches directory tree root. Function called when the user reaches directory tree root.
@ -259,12 +362,12 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
and defaults to `all`. and defaults to `all`.
""" """
current_path = StringProperty(os.getcwd()) current_path = StringProperty(os.path.expanduser("~"))
""" """
Current directory. Current directory.
:attr:`current_path` is an :class:`~kivy.properties.StringProperty` :attr:`current_path` is an :class:`~kivy.properties.StringProperty`
and defaults to `/`. and defaults to `os.path.expanduser("~")`.
""" """
use_access = BooleanProperty(True) use_access = BooleanProperty(True)
@ -295,9 +398,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"name", options=["nothing", "name", "date", "size", "type"] "name", options=["nothing", "name", "date", "size", "type"]
) )
""" """
It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files
By default, sort by name. by option. By default, sort by name. Available options are:
Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`. `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.
:attr:`sort_by` is an :class:`~kivy.properties.OptionProperty` :attr:`sort_by` is an :class:`~kivy.properties.OptionProperty`
and defaults to `name`. and defaults to `name`.
@ -325,29 +428,33 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
""" """
Contains the list of files that are currently selected. Contains the list of files that are currently selected.
:attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty`
defaults to `[]`. and defaults to `[]`.
"""
selection_button = ObjectProperty()
"""
The instance of the directory/path selection button.
.. versionadded:: 1.1.0
:attr:`selection_button` is a read-only :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
""" """
_window_manager = None _window_manager = None
_window_manager_open = False _window_manager_open = False
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(**kwargs) super().__init__(*args, **kwargs)
self.register_event_type("on_pre_open")
self.register_event_type("on_open")
self.register_event_type("on_pre_dismiss")
self.register_event_type("on_dismiss")
toolbar_label = self.ids.toolbar.children[1].children[0] toolbar_label = self.ids.toolbar.children[1].children[0]
toolbar_label.font_style = "Subtitle1" toolbar_label.font_style = "Subtitle1"
if ( Clock.schedule_once(self._create_selection_button)
self.selector == "any"
or self.selector == "multi"
or self.selector == "folder"
):
self.add_widget(
FloatButton(
callback=self.select_directory_on_press_button,
md_bg_color=self.theme_cls.primary_color,
icon=self.icon,
)
)
if self.preview: if self.preview:
self.ext = [".png", ".jpg", ".jpeg"] self.ext = [".png", ".jpg", ".jpeg"]
@ -400,15 +507,7 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
} }
) )
self.ids.rv.data = manager_list self.ids.rv.data = manager_list
self._show()
if not self._window_manager:
self._window_manager = ModalView(
size_hint=self.size_hint, auto_dismiss=False
)
self._window_manager.add_widget(self)
if not self._window_manager_open:
self._window_manager.open()
self._window_manager_open = True
def show(self, path: str) -> None: def show(self, path: str) -> None:
""" """
@ -474,6 +573,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"icon": icon, "icon": icon,
"dir_or_file_name": name, "dir_or_file_name": name,
"events_callback": self.select_dir_or_file, "events_callback": self.select_dir_or_file,
"icon_color": self.theme_cls.primary_color
if not self.icon_color
else self.icon_color,
"_selected": False, "_selected": False,
} }
) )
@ -488,19 +590,14 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"icon": "file-outline", "icon": "file-outline",
"dir_or_file_name": os.path.split(name)[1], "dir_or_file_name": os.path.split(name)[1],
"events_callback": self.select_dir_or_file, "events_callback": self.select_dir_or_file,
"icon_color": self.theme_cls.primary_color
if not self.icon_color
else self.icon_color,
"_selected": False, "_selected": False,
} }
) )
self.ids.rv.data = manager_list self.ids.rv.data = manager_list
self._show()
if not self._window_manager:
self._window_manager = ModalView(
size_hint=self.size_hint, auto_dismiss=False
)
self._window_manager.add_widget(self)
if not self._window_manager_open:
self._window_manager.open()
self._window_manager_open = True
def get_access_string(self, path: str) -> str: def get_access_string(self, path: str) -> str:
access_string = "" access_string = ""
@ -557,7 +654,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
def close(self) -> None: def close(self) -> None:
"""Closes the file manager window.""" """Closes the file manager window."""
self.dispatch("on_pre_dismiss")
self._window_manager.dismiss() self._window_manager.dismiss()
self.dispatch("on_dismiss")
self._window_manager_open = False self._window_manager_open = False
def select_dir_or_file( def select_dir_or_file(
@ -609,6 +708,84 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
if self.selector == "folder" or self.selector == "any": if self.selector == "folder" or self.selector == "any":
self.select_path(self.current_path) self.select_path(self.current_path)
def on_icon(self, instance_file_manager, icon_name: str) -> None:
"""Called when the :attr:`icon` property is changed."""
self.icon_selection_button = icon_name
def on_background_color_toolbar(
self, instance_file_manager, color: Union[str, list]
) -> None:
"""
Called when the :attr:`background_color_toolbar` property is changed.
"""
def on_background_color_toolbar(*args):
self.ids.toolbar.md_bg_color = color
Clock.schedule_once(on_background_color_toolbar)
def on_pre_open(self, *args):
"""
Default pre-open event handler.
.. versionadded:: 1.1.0
"""
def on_open(self, *args):
"""
Default open event handler.
.. versionadded:: 1.1.0
"""
def on_pre_dismiss(self, *args):
"""
Default pre-dismiss event handler.
.. versionadded:: 1.1.0
"""
def on_dismiss(self, *args):
"""
Default dismiss event handler.
.. versionadded:: 1.1.0
"""
def _show(self):
if not self._window_manager:
self._window_manager = ModalView(
size_hint=self.size_hint, auto_dismiss=False
)
self.size_hint = (1, 1)
self._window_manager.add_widget(self)
if not self._window_manager_open:
self._window_manager.open()
self._window_manager_open = True
self.dispatch("on_pre_open")
self.dispatch("on_open")
def _create_selection_button(self, *args):
if (
self.selector == "any"
or self.selector == "multi"
or self.selector == "folder"
):
self.selection_button = MDFloatingActionButton(
on_release=self.select_directory_on_press_button,
md_bg_color=self.theme_cls.primary_color
if not self.background_color_selection_button
else self.background_color_selection_button,
icon=self.icon_selection_button,
pos_hint={"right": 0.99},
y=dp(12),
elevation=0,
)
self.add_widget(self.selection_button)
def __sort_files(self, files): def __sort_files(self, files):
def sort_by_name(files): def sort_by_name(files):
files.sort(key=locale.strxfrm) files.sort(key=locale.strxfrm)

View File

@ -132,11 +132,11 @@ from kivy.properties import BooleanProperty, ObjectProperty
from kivy.uix.image import AsyncImage from kivy.uix.image import AsyncImage
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from kivymd.uix.behaviors import StencilBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.templates import StencilWidget
class FitImage(MDBoxLayout, StencilWidget): class FitImage(MDBoxLayout, StencilBehavior):
source = ObjectProperty() source = ObjectProperty()
""" """
Filename/source of your image. Filename/source of your image.

View File

@ -90,4 +90,7 @@ from kivymd.uix.behaviors import DeclarativeBehavior
class MDGridLayout(DeclarativeBehavior, GridLayout, MDAdaptiveWidget): class MDGridLayout(DeclarativeBehavior, GridLayout, MDAdaptiveWidget):
pass """
Grid layout class. For more information, see in the
:class:`~kivy.uix.gridlayout.GridLayout` class documentation.
"""

View File

@ -63,7 +63,7 @@ Base example
x: 24 x: 24
FitImage: FitImage:
source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png" source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None size_hint: None, None
size: hero_from.size size: hero_from.size
@ -72,7 +72,7 @@ Base example
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "36dp" y: "36dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen B" root.current = "screen B"
MDScreen: MDScreen:
@ -82,6 +82,7 @@ Base example
MDHeroTo: MDHeroTo:
id: hero_to id: hero_to
tag: "hero"
size_hint: None, None size_hint: None, None
size: "220dp", "220dp" size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5} pos_hint: {"center_x": .5, "center_y": .5}
@ -91,7 +92,7 @@ Base example
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "36dp" y: "36dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen A" root.current = "screen A"
''' '''
@ -113,6 +114,7 @@ Note that the child of the :class:`~MDHeroFrom` widget must have the size of the
MDHeroFrom: MDHeroFrom:
id: hero_from id: hero_from
tag: "hero"
FitImage: FitImage:
size_hint: None, None size_hint: None, None
@ -127,7 +129,7 @@ container in which the hero is located:
MDRaisedButton: MDRaisedButton:
text: "Move Hero To Screen B" text: "Move Hero To Screen B"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen 2" root.current = "screen 2"
If you need to switch to a screen that does not contain heroes, set the If you need to switch to a screen that does not contain heroes, set the
@ -138,7 +140,7 @@ If you need to switch to a screen that does not contain heroes, set the
MDRaisedButton: MDRaisedButton:
text: "Go To Another Screen" text: "Go To Another Screen"
on_release: on_release:
root.current_hero = "" root.current_heroes = []
root.current = "another screen" root.current = "another screen"
Example Example
@ -166,7 +168,7 @@ Example
x: 24 x: 24
FitImage: FitImage:
source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png" source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None size_hint: None, None
size: hero_from.size size: hero_from.size
@ -175,7 +177,7 @@ Example
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "36dp" y: "36dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen B" root.current = "screen B"
MDScreen: MDScreen:
@ -185,6 +187,7 @@ Example
MDHeroTo: MDHeroTo:
id: hero_to id: hero_to
tag: "hero"
size_hint: None, None size_hint: None, None
size: "220dp", "220dp" size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5} pos_hint: {"center_x": .5, "center_y": .5}
@ -194,7 +197,7 @@ Example
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "52dp" y: "52dp"
on_release: on_release:
root.current_hero = "" root.current_heroes = []
root.current = "screen C" root.current = "screen C"
MDRaisedButton: MDRaisedButton:
@ -202,7 +205,7 @@ Example
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "8dp" y: "8dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen A" root.current = "screen A"
MDScreen: MDScreen:
@ -283,7 +286,7 @@ background color of the hero during the flight between the screens:
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "36dp" y: "36dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen B" root.current = "screen B"
MDScreen: MDScreen:
@ -293,6 +296,7 @@ background color of the hero during the flight between the screens:
MDHeroTo: MDHeroTo:
id: hero_to id: hero_to
tag: "hero"
size_hint: None, None size_hint: None, None
size: "220dp", "220dp" size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5} pos_hint: {"center_x": .5, "center_y": .5}
@ -302,7 +306,7 @@ background color of the hero during the flight between the screens:
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "36dp" y: "36dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = ["hero"]
root.current = "screen A" root.current = "screen A"
''' '''
@ -370,7 +374,7 @@ Usage with ScrollView
radius: 24 radius: 24
box_radius: 0, 0, 24, 24 box_radius: 0, 0, 24, 24
box_color: 0, 0, 0, .5 box_color: 0, 0, 0, .5
source: "image.jpg" source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None size_hint: None, None
size: root.size size: root.size
mipmap: True mipmap: True
@ -399,7 +403,7 @@ Usage with ScrollView
MDScreen: MDScreen:
name: "screen B" name: "screen B"
hero_to: hero_to heroes_to: [hero_to]
MDHeroTo: MDHeroTo:
id: hero_to id: hero_to
@ -412,7 +416,7 @@ Usage with ScrollView
pos_hint: {"center_x": .5} pos_hint: {"center_x": .5}
y: "36dp" y: "36dp"
on_release: on_release:
root.current_hero = "hero" root.current_heroes = [hero_to.tag]
root.current = "screen A" root.current = "screen A"
''' '''
@ -441,7 +445,8 @@ Usage with ScrollView
def on_release(self): def on_release(self):
def switch_screen(*args): def switch_screen(*args):
self.manager.current_hero = self.tag self.manager.current_heroes = [self.tag]
self.manager.ids.hero_to.tag = self.tag
self.manager.current = "screen B" self.manager.current = "screen B"
Clock.schedule_once(switch_screen, 0.2) Clock.schedule_once(switch_screen, 0.2)
@ -465,6 +470,93 @@ Usage with ScrollView
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage-with-scrollview.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage-with-scrollview.gif
:align: center :align: center
Using multiple heroes at the same time
--------------------------------------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreenManager:
MDScreen:
name: "screen A"
md_bg_color: "lightblue"
MDHeroFrom:
id: hero_kivymd
tag: "kivymd"
size_hint: None, None
size: "200dp", "200dp"
pos_hint: {"top": .98}
x: 24
FitImage:
source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None
size: hero_kivymd.size
MDHeroFrom:
id: hero_kivy
tag: "kivy"
size_hint: None, None
size: "200dp", "200dp"
pos_hint: {"top": .98}
x: 324
FitImage:
source: "data/logo/kivy-icon-512.png"
size_hint: None, None
size: hero_kivy.size
MDRaisedButton:
text: "Move Hero To Screen B"
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_heroes = ["kivymd", "kivy"]
root.current = "screen B"
MDScreen:
name: "screen B"
heroes_to: hero_to_kivymd, hero_to_kivy
md_bg_color: "cadetblue"
MDHeroTo:
id: hero_to_kivy
tag: "kivy"
size_hint: None, None
pos_hint: {"center_x": .5, "center_y": .5}
MDHeroTo:
id: hero_to_kivymd
tag: "kivymd"
size_hint: None, None
pos_hint: {"right": 1, "top": 1}
MDRaisedButton:
text: "Move Hero To Screen A"
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_heroes = ["kivy", "kivymd"]
root.current = "screen A"
'''
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-multiple-heroes.gif
:align: center
""" """
from kivy.properties import StringProperty from kivy.properties import StringProperty
@ -476,6 +568,9 @@ class MDHeroFrom(MDBoxLayout):
""" """
The container from which the hero begins his flight. The container from which the hero begins his flight.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
:Events: :Events:
`on_transform_in` `on_transform_in`
when the hero flies from screen **A** to screen **B**. when the hero flies from screen **A** to screen **B**.
@ -487,7 +582,7 @@ class MDHeroFrom(MDBoxLayout):
""" """
Tag ID for heroes. Tag ID for heroes.
:attr:`shift_right` is an :class:`~kivy.properties.StringProperty` :attr:`tag` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`. and defaults to `''`.
""" """
@ -504,4 +599,17 @@ class MDHeroFrom(MDBoxLayout):
class MDHeroTo(MDBoxLayout): class MDHeroTo(MDBoxLayout):
"""The container in which the hero comes.""" """
The container in which the hero comes.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
"""
tag = StringProperty(allownone=True)
"""
Tag ID for heroes.
:attr:`tag` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""

View File

@ -65,12 +65,13 @@ Implementation
:align: center :align: center
""" """
__all__ = "MDSmartTile" __all__ = [
"MDSmartTile",
]
import os import os
from kivy.lang import Builder from kivy.lang import Builder
from kivy.logger import Logger
from kivy.properties import ( from kivy.properties import (
BooleanProperty, BooleanProperty,
ColorProperty, ColorProperty,

View File

@ -17,8 +17,14 @@
rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0) rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0)
Rectangle: Rectangle:
source: self.source if self.source else None source: self.source if self.source else None
pos: self.pos pos:
size: self.size self.pos \
if not self.source else \
(self.x - self._size[0] / 2, self.y)
size:
self._size \
if self.source else \
self.size
font_style: "Icon" font_style: "Icon"
text: u"{}".format(md_icons[root.icon]) if root.icon in md_icons else "blank" text: u"{}".format(md_icons[root.icon]) if root.icon in md_icons else "blank"

View File

@ -222,14 +222,18 @@ __all__ = ("MDLabel", "MDIcon")
import os import os
from typing import Union from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.lang import Builder from kivy.lang import Builder
from kivy.metrics import sp from kivy.metrics import sp
from kivy.properties import ( from kivy.properties import (
AliasProperty, AliasProperty,
BooleanProperty, BooleanProperty,
ColorProperty, ColorProperty,
ListProperty,
NumericProperty, NumericProperty,
ObjectProperty,
OptionProperty, OptionProperty,
StringProperty, StringProperty,
) )
@ -322,6 +326,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
parent_background = ColorProperty(None) parent_background = ColorProperty(None)
can_capitalize = BooleanProperty(True) can_capitalize = BooleanProperty(True)
canvas_bg = ObjectProperty()
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@ -349,6 +354,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
font_info = self.theme_cls.font_styles[self.font_style] font_info = self.theme_cls.font_styles[self.font_style]
self.font_name = font_info[0] self.font_name = font_info[0]
self.font_size = sp(font_info[1]) self.font_size = sp(font_info[1])
if font_info[2] and self.can_capitalize: if font_info[2] and self.can_capitalize:
self._capitalizing = True self._capitalizing = True
else: else:
@ -374,29 +380,68 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
# generic None value it's not yet been set # generic None value it's not yet been set
self._text_color_str = "" self._text_color_str = ""
if theme_text_color == "Custom" and self.text_color: if theme_text_color == "Custom" and self.text_color:
self.color = self.text_color color = self.text_color
elif ( elif (
theme_text_color == "ContrastParentBackground" theme_text_color == "ContrastParentBackground"
and self.parent_background and self.parent_background
): ):
self.color = get_contrast_text_color(self.parent_background) color = get_contrast_text_color(self.parent_background)
else: else:
self.color = [0, 0, 0, 1] color = [0, 0, 0, 1]
def on_text_color(self, instance_label, color: list) -> None: if self.theme_cls.theme_style_switch_animation:
Animation(
color=color,
d=self.theme_cls.theme_style_switch_animation_duration,
t="linear",
).start(self)
else:
self.color = color
def on_text_color(self, instance_label, color: Union[list, str]) -> None:
if self.theme_text_color == "Custom": if self.theme_text_color == "Custom":
self.color = self.text_color if self.theme_cls.theme_style_switch_animation:
Animation(
color=self.text_color,
d=self.theme_cls.theme_style_switch_animation_duration,
t="linear",
).start(self)
else:
self.color = self.text_color
def on_opposite_colors(self, *args) -> None: def on_opposite_colors(self, *args) -> None:
self.on_theme_text_color(self, self.theme_text_color) self.on_theme_text_color(self, self.theme_text_color)
def on_md_bg_color(self, instance_label, color: Union[list, str]) -> None:
self.canvas.remove_group("Background_instruction")
with self.canvas.before:
Color(rgba=color)
self.canvas_bg = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_canvas_bg_pos)
def on_size(self, instance_label, size: list) -> None:
if self.canvas_bg:
self.canvas_bg.size = size
def update_canvas_bg_pos(self, instance_label, pos: list) -> None:
if self.canvas_bg:
self.canvas_bg.pos = pos
def _do_update_theme_color(self, *args): def _do_update_theme_color(self, *args):
if self._text_color_str: if self._text_color_str:
self.color = getattr(self.theme_cls, self._text_color_str)
if not self.disabled: if not self.disabled:
self.color = getattr(self.theme_cls, self._text_color_str) color = getattr(self.theme_cls, self._text_color_str)
else: else:
self.color = getattr(self.theme_cls, "disabled_hint_text_color") color = getattr(self.theme_cls, "disabled_hint_text_color")
if self.theme_cls.theme_style_switch_animation:
Animation(
color=color,
d=self.theme_cls.theme_style_switch_animation_duration,
t="linear",
).start(self)
else:
self.color = color
class MDIcon(MDFloatLayout, MDLabel): class MDIcon(MDFloatLayout, MDLabel):
@ -456,11 +501,16 @@ class MDIcon(MDFloatLayout, MDLabel):
and defaults to `None`. and defaults to `None`.
""" """
def __init__(self, **kwargs): _size = ListProperty((0, 0))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Clock.schedule_once(self.adjust_size)
def adjust_size(self, *args) -> None:
from kivymd.uix.selectioncontrol import MDCheckbox from kivymd.uix.selectioncontrol import MDCheckbox
super().__init__(**kwargs)
if not isinstance(self, MDCheckbox): if not isinstance(self, MDCheckbox):
self.size_hint = (None, None) self.size_hint = (None, None)
self.size = self.texture_size self._size = self.texture_size[1], self.texture_size[1]
self.adaptive_size = True self.adaptive_size = True

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#:import STD_INC kivymd.material_resources.STANDARD_INCREMENT #:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
<RightContent> <RightContent>
@ -14,7 +14,7 @@
<MDMenu> <MDMenu>
size_hint: None, None size_hint: None, None
width: root.width_mult * STD_INC width: root.width_mult * STANDARD_INCREMENT
bar_width: 0 bar_width: 0
key_viewclass: "viewclass" key_viewclass: "viewclass"
key_size: "height" key_size: "height"
@ -28,7 +28,7 @@
orientation: "vertical" orientation: "vertical"
<MenuContainer@MDCard+FakeRectangularElevationBehavior> <MenuContainer@MDCard>
<MDDropdownMenu> <MDDropdownMenu>

View File

@ -781,7 +781,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout):
and defaults to `'[dp(7)]'`. and defaults to `'[dp(7)]'`.
""" """
elevation = NumericProperty(10) elevation = NumericProperty(4)
""" """
Elevation value of menu dialog. Elevation value of menu dialog.
@ -790,7 +790,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout):
.. code-block:: python .. code-block:: python
self.menu = MDDropdownMenu( self.menu = MDDropdownMenu(
elevation=16, elevation=4,
..., ...,
) )
@ -798,7 +798,7 @@ class MDDropdownMenu(ThemableBehavior, FloatLayout):
:align: center :align: center
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty` :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `10`. and defaults to `4`.
""" """
_start_coords = [] _start_coords = []

View File

@ -61,7 +61,7 @@ A simple example
MDTopAppBar: MDTopAppBar:
title: "Navigation Drawer" title: "Navigation Drawer"
elevation: 10 elevation: 4
pos_hint: {"top": 1} pos_hint: {"top": 1}
md_bg_color: "#e7e4c0" md_bg_color: "#e7e4c0"
specific_text_color: "#4a4939" specific_text_color: "#4a4939"
@ -115,7 +115,7 @@ A simple example
MDScreen( MDScreen(
MDTopAppBar( MDTopAppBar(
title="Navigation Drawer", title="Navigation Drawer",
elevation=10, elevation=4,
pos_hint={"top": 1}, pos_hint={"top": 1},
md_bg_color="#e7e4c0", md_bg_color="#e7e4c0",
specific_text_color="#4a4939", specific_text_color="#4a4939",
@ -188,7 +188,7 @@ Standard content for the navigation bar
MDTopAppBar: MDTopAppBar:
title: "Navigation Drawer" title: "Navigation Drawer"
elevation: 10 elevation: 4
pos_hint: {"top": 1} pos_hint: {"top": 1}
md_bg_color: "#e7e4c0" md_bg_color: "#e7e4c0"
specific_text_color: "#4a4939" specific_text_color: "#4a4939"
@ -296,7 +296,7 @@ Standard content for the navigation bar
MDScreen( MDScreen(
MDTopAppBar( MDTopAppBar(
title="Navigation Drawer", title="Navigation Drawer",
elevation=10, elevation=4,
pos_hint={"top": 1}, pos_hint={"top": 1},
md_bg_color="#e7e4c0", md_bg_color="#e7e4c0",
specific_text_color="#4a4939", specific_text_color="#4a4939",
@ -396,7 +396,7 @@ Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar``
MDTopAppBar: MDTopAppBar:
pos_hint: {"top": 1} pos_hint: {"top": 1}
elevation: 10 elevation: 4
title: "MDNavigationDrawer" title: "MDNavigationDrawer"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
@ -465,7 +465,7 @@ Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar``
MDScreen( MDScreen(
MDTopAppBar( MDTopAppBar(
pos_hint={"top": 1}, pos_hint={"top": 1},
elevation=10, elevation=4,
title="MDNavigationDrawer", title="MDNavigationDrawer",
left_action_items=[["menu", lambda x: self.nav_drawer_open()]], left_action_items=[["menu", lambda x: self.nav_drawer_open()]],
), ),
@ -551,14 +551,9 @@ from kivy.properties import (
StringProperty, StringProperty,
VariableListProperty, VariableListProperty,
) )
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager from kivy.uix.screenmanager import ScreenManager
from kivymd import uix_path from kivymd import uix_path
from kivymd.uix.behaviors import (
DeclarativeBehavior,
FakeRectangularElevationBehavior,
)
from kivymd.uix.behaviors.focus_behavior import FocusBehavior from kivymd.uix.behaviors.focus_behavior import FocusBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
@ -1029,7 +1024,7 @@ class MDNavigationDrawerMenu(MDScrollView):
widget.text_color = widget._text_color widget.text_color = widget._text_color
class MDNavigationDrawer(MDCard, FakeRectangularElevationBehavior): class MDNavigationDrawer(MDCard):
type = OptionProperty("modal", options=("standard", "modal")) type = OptionProperty("modal", options=("standard", "modal"))
""" """
Type of drawer. Modal type will be on top of screen. Standard type will be Type of drawer. Modal type will be on top of screen. Standard type will be

View File

@ -29,45 +29,86 @@ Usage
MDNavigationRailItem: MDNavigationRailItem:
.. code-block:: python .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivymd.app import MDApp .. code-block:: python
KV = ''' from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDBoxLayout:
MDNavigationRail:
MDNavigationRailItem:
text: "Python"
icon: "language-python"
MDNavigationRailItem:
text: "JavaScript"
icon: "language-javascript"
MDNavigationRailItem:
text: "CPP"
icon: "language-cpp"
MDNavigationRailItem:
text: "Git"
icon: "git"
MDScreen:
'''
MDBoxLayout: class Example(MDApp):
def build(self):
MDNavigationRail: return Builder.load_string(KV)
MDNavigationRailItem:
text: "Python"
icon: "language-python"
MDNavigationRailItem:
text: "JavaScript"
icon: "language-javascript"
MDNavigationRailItem:
text: "CPP"
icon: "language-cpp"
MDNavigationRailItem:
text: "Git"
icon: "git"
MDScreen:
'''
class Example(MDApp): Example().run()
def build(self):
return Builder.load_string(KV) .. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem
Example().run() class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDBoxLayout(
MDNavigationRail(
MDNavigationRailItem(
text="Python",
icon="language-python",
),
MDNavigationRailItem(
text="JavaScript",
icon="language-javascript",
),
MDNavigationRailItem(
text="CPP",
icon="language-cpp",
),
MDNavigationRailItem(
text="Git",
icon="git",
),
)
)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-usage.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-usage.png
:align: center :align: center
@ -75,202 +116,412 @@ Usage
Example Example
======= =======
.. code-block:: python .. tabs::
from kivy.clock import Clock .. tab:: Declarative KV and imperative python styles
from kivy.lang import Builder
from kivymd.app import MDApp .. code-block:: python
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFillRoundFlatIconButton
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
KV = ''' from kivy.clock import Clock
#:import FadeTransition kivy.uix.screenmanager.FadeTransition from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFillRoundFlatIconButton
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
KV = '''
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<ExtendedButton> <ExtendedButton>
elevation: 3 elevation: 3.5
-height: "56dp" shadow_radius: 12
shadow_softness: 4
-height: "56dp"
<DrawerClickableItem@MDNavigationDrawerItem> <DrawerClickableItem@MDNavigationDrawerItem>
focus_color: "#e7e4c0" focus_color: "#e7e4c0"
unfocus_color: "#fffcf4" unfocus_color: "#fffcf4"
MDScreen: MDScreen:
MDNavigationLayout: MDNavigationLayout:
ScreenManager: ScreenManager:
MDScreen: MDScreen:
MDBoxLayout: MDBoxLayout:
orientation: "vertical" orientation: "vertical"
MDBoxLayout:
adaptive_height: True
md_bg_color: "#fffcf4"
padding: "12dp"
MDLabel:
text: "12:00"
adaptive_height: True
pos_hint: {"center_y": .5}
MDBoxLayout:
MDNavigationRail:
id: navigation_rail
md_bg_color: "#fffcf4"
selected_color_background: "#e7e4c0"
ripple_color_item: "#e7e4c0"
on_item_release: app.switch_screen(*args)
MDNavigationRailMenuButton:
on_release: nav_drawer.set_state("open")
MDNavigationRailFabButton:
md_bg_color: "#b0f0d6"
MDNavigationRailItem:
text: "Python"
icon: "language-python"
MDNavigationRailItem:
text: "JavaScript"
icon: "language-javascript"
MDNavigationRailItem:
text: "CPP"
icon: "language-cpp"
MDNavigationRailItem:
text: "Swift"
icon: "language-swift"
ScreenManager:
id: screen_manager
transition:
FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark)
MDNavigationDrawer:
id: nav_drawer
radius: (0, 16, 16, 0)
md_bg_color: "#fffcf4"
elevation: 4
width: "240dp"
MDNavigationDrawerMenu:
MDBoxLayout: MDBoxLayout:
orientation: "vertical"
adaptive_height: True adaptive_height: True
md_bg_color: "#fffcf4" spacing: "12dp"
padding: "12dp" padding: "3dp", 0, 0, "12dp"
MDLabel: MDIconButton:
text: "12:00" icon: "menu"
adaptive_height: True
pos_hint: {"center_y": .5}
MDBoxLayout: ExtendedButton:
text: "Compose"
icon: "pencil"
MDNavigationRail: DrawerClickableItem:
id: navigation_rail text: "Python"
md_bg_color: "#fffcf4" icon: "language-python"
selected_color_background: "#e7e4c0"
ripple_color_item: "#e7e4c0"
on_item_release: app.switch_screen(*args)
MDNavigationRailMenuButton: DrawerClickableItem:
on_release: nav_drawer.set_state("open") text: "JavaScript"
icon: "language-javascript"
MDNavigationRailFabButton: DrawerClickableItem:
md_bg_color: "#b0f0d6" text: "CPP"
icon: "language-cpp"
MDNavigationRailItem: DrawerClickableItem:
text: "Python" text: "Swift"
icon: "language-python" icon: "language-swift"
MDNavigationRailItem:
text: "JavaScript"
icon: "language-javascript"
MDNavigationRailItem:
text: "CPP"
icon: "language-cpp"
MDNavigationRailItem:
text: "Swift"
icon: "language-swift"
ScreenManager:
id: screen_manager
transition:
FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark)
MDNavigationDrawer:
id: nav_drawer
radius: (0, 16, 16, 0)
md_bg_color: "#fffcf4"
elevation: 12
width: "240dp"
MDNavigationDrawerMenu:
MDBoxLayout:
orientation: "vertical"
adaptive_height: True
spacing: "12dp"
padding: 0, 0, 0, "12dp"
MDIconButton:
icon: "menu"
ExtendedButton:
text: "Compose"
icon: "pencil"
DrawerClickableItem:
text: "Python"
icon: "language-python"
DrawerClickableItem:
text: "JavaScript"
icon: "language-javascript"
DrawerClickableItem:
text: "CPP"
icon: "language-cpp"
DrawerClickableItem:
text: "Swift"
icon: "language-swift"
'''
class ExtendedButton(
RoundedRectangularElevationBehavior, MDFillRoundFlatIconButton
):
'''
Implements a button of type
`Extended FAB <https://m3.material.io/components/extended-fab/overview>`_.
.. rubric::
Extended FABs help people take primary actions.
They're wider than FABs to accommodate a text label and larger target
area.
This type of buttons is not yet implemented in the standard widget set
of the KivyMD library, so we will implement it ourselves in this class.
'''
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.padding = "16dp"
Clock.schedule_once(self.set_spacing)
def set_spacing(self, interval):
self.ids.box.spacing = "12dp"
def set_radius(self, *args):
if self.rounded_button:
self._radius = self.radius = self.height / 4
class Example(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def switch_screen(
self, instance_navigation_rail, instance_navigation_rail_item
):
'''
Called when tapping on rail menu items. Switches application screens.
''' '''
self.root.ids.screen_manager.current = (
instance_navigation_rail_item.icon.split("-")[1].lower() class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior):
'''
Implements a button of type
`Extended FAB <https://m3.material.io/components/extended-fab/overview>`_.
.. rubric::
Extended FABs help people take primary actions.
They're wider than FABs to accommodate a text label and larger target
area.
This type of buttons is not yet implemented in the standard widget set
of the KivyMD library, so we will implement it ourselves in this class.
'''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.padding = "16dp"
Clock.schedule_once(self.set_spacing)
def set_spacing(self, interval):
self.ids.box.spacing = "12dp"
def set_radius(self, *args):
if self.rounded_button:
self._radius = self.radius = self.height / 4
class Example(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def switch_screen(
self, instance_navigation_rail, instance_navigation_rail_item
):
'''
Called when tapping on rail menu items. Switches application screens.
'''
self.root.ids.screen_manager.current = (
instance_navigation_rail_item.icon.split("-")[1].lower()
)
def on_start(self):
'''Creates application screens.'''
navigation_rail_items = self.root.ids.navigation_rail.get_items()[:]
navigation_rail_items.reverse()
for widget in navigation_rail_items:
name_screen = widget.icon.split("-")[1].lower()
screen = MDScreen(
name=name_screen,
md_bg_color="#edd769",
radius=[18, 0, 0, 0],
)
box = MDBoxLayout(padding="12dp")
label = MDLabel(
text=name_screen.capitalize(),
font_style="H1",
halign="right",
adaptive_height=True,
shorten=True,
)
box.add_widget(label)
screen.add_widget(box)
self.root.ids.screen_manager.add_widget(screen)
Example().run()
.. tab:: Declarative python style
.. code-block:: python
from kivy.clock import Clock
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFillRoundFlatIconButton, MDIconButton
from kivymd.uix.label import MDLabel
from kivymd.uix.navigationdrawer import (
MDNavigationDrawerItem,
MDNavigationLayout,
MDNavigationDrawer,
MDNavigationDrawerMenu,
) )
from kivymd.uix.navigationrail import (
def on_start(self): MDNavigationRail,
'''Creates application screens.''' MDNavigationRailMenuButton,
MDNavigationRailFabButton,
navigation_rail_items = self.root.ids.navigation_rail.get_items()[:] MDNavigationRailItem,
navigation_rail_items.reverse() )
from kivymd.uix.screen import MDScreen
for widget in navigation_rail_items: from kivymd.uix.screenmanager import MDScreenManager
name_screen = widget.icon.split("-")[1].lower()
screen = MDScreen(
name=name_screen,
md_bg_color="#edd769",
radius=[18, 0, 0, 0],
)
box = MDBoxLayout(padding="12dp")
label = MDLabel(
text=name_screen.capitalize(),
font_style="H1",
halign="right",
adaptive_height=True,
shorten=True,
)
box.add_widget(label)
screen.add_widget(box)
self.root.ids.screen_manager.add_widget(screen)
Example().run() class DrawerClickableItem(MDNavigationDrawerItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.focus_color = "#e7e4c0"
self.unfocus_color = self.theme_cls.bg_light
self.radius = 24
class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.padding = "16dp"
self.elevation = 3.5
self.shadow_radius = 12
self.shadow_softness = 4
self.height = dp(56)
Clock.schedule_once(self.set_spacing)
def set_spacing(self, interval):
self.ids.box.spacing = "12dp"
def set_radius(self, *args):
if self.rounded_button:
self._radius = self.radius = self.height / 4
class Example(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
self.theme_cls.primary_palette = "Orange"
return MDScreen(
MDNavigationLayout(
MDScreenManager(
MDScreen(
MDBoxLayout(
MDBoxLayout(
MDLabel(
text="12:00",
adaptive_height=True,
pos_hint={"center_y": 0.5},
),
adaptive_height=True,
md_bg_color="#fffcf4",
padding="12dp",
),
MDBoxLayout(
MDNavigationRail(
MDNavigationRailMenuButton(
on_release=self.open_nav_drawer,
),
MDNavigationRailFabButton(
md_bg_color="#b0f0d6",
),
MDNavigationRailItem(
text="Python",
icon="language-python",
),
MDNavigationRailItem(
text="JavaScript",
icon="language-javascript",
),
MDNavigationRailItem(
text="CPP",
icon="language-cpp",
),
MDNavigationRailItem(
text="Swift",
icon="language-swift",
),
id="navigation_rail",
md_bg_color="#fffcf4",
selected_color_background="#e7e4c0",
ripple_color_item="#e7e4c0",
),
MDScreenManager(
id="screen_manager_content",
),
id="root_box",
),
id="box_rail",
orientation="vertical",
),
id="box",
),
id="screen",
),
id="screen_manager",
),
MDNavigationDrawer(
MDNavigationDrawerMenu(
MDBoxLayout(
MDIconButton(
icon="menu",
),
ExtendedButton(
text="Compose",
icon="pencil",
),
orientation="vertical",
adaptive_height=True,
spacing="12dp",
padding=("3dp", 0, 0, "12dp"),
),
DrawerClickableItem(
text="Python",
icon="language-python",
),
DrawerClickableItem(
text="JavaScript",
icon="language-javascript",
),
DrawerClickableItem(
text="CPP",
icon="language-cpp",
),
DrawerClickableItem(
text="Swift",
icon="language-swift",
),
),
id="nav_drawer",
radius=(0, 16, 16, 0),
elevation=4,
width="240dp",
),
)
def switch_screen(self, *args, screen_manager_content=None):
'''
Called when tapping on rail menu items. Switches application screens.
'''
instance_navigation_rail, instance_navigation_rail_item = args
screen_manager_content.current = (
instance_navigation_rail_item.icon.split("-")[1].lower()
)
def open_nav_drawer(self, *args):
self.root.ids.nav_drawer.set_state("open")
def on_start(self):
'''Creates application screens.'''
screen_manager = self.root.ids.screen_manager
root_box = screen_manager.ids.screen.ids.box.ids.box_rail.ids.root_box
navigation_rail = root_box.ids.navigation_rail
screen_manager_content = root_box.ids.screen_manager_content
navigation_rail_items = navigation_rail.get_items()[:]
navigation_rail_items.reverse()
navigation_rail.bind(
on_item_release=lambda *args: self.switch_screen(
*args, screen_manager_content=screen_manager_content
)
)
for widget in navigation_rail_items:
name_screen = widget.icon.split("-")[1].lower()
screen_manager_content.add_widget(
MDScreen(
MDBoxLayout(
MDLabel(
text=name_screen.capitalize(),
font_style="H1",
halign="right",
adaptive_height=True,
shorten=True,
),
padding="12dp",
),
name=name_screen,
md_bg_color="#edd769",
radius=[18, 0, 0, 0],
),
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-example.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-example.gif
:align: center :align: center
@ -306,12 +557,11 @@ from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path from kivymd import uix_path
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import FakeRectangularElevationBehavior from kivymd.uix.behaviors import ScaleBehavior
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFloatingActionButton, MDIconButton from kivymd.uix.button import MDFloatingActionButton, MDIconButton
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.templates import ScaleWidget
from kivymd.uix.widget import MDWidget from kivymd.uix.widget import MDWidget
with open( with open(
@ -333,7 +583,7 @@ class PanelItems(MDBoxLayout):
"""Box for menu items.""" """Box for menu items."""
class RippleWidget(MDWidget, ScaleWidget): class RippleWidget(MDWidget, ScaleBehavior):
""" """
Implements a background color for a menu item - Implements a background color for a menu item -
(:class:`~MDNavigationRailItem`). (:class:`~MDNavigationRailItem`).
@ -562,7 +812,7 @@ class MDNavigationRailItem(ThemableBehavior, ButtonBehavior, MDBoxLayout):
self.navigation_rail.dispatch("on_item_release", self) self.navigation_rail.dispatch("on_item_release", self)
class MDNavigationRail(MDCard, FakeRectangularElevationBehavior): class MDNavigationRail(MDCard):
""" """
:Events: :Events:
:attr:`on_item_press` :attr:`on_item_press`

View File

@ -307,34 +307,6 @@
else (dp(32), dp(32)) else (dp(32), dp(32))
disabled: True disabled: True
canvas:
Color:
rgba:
( \
( \
self.theme_cls.primary_color if not root.owner.selector_color \
else root.owner.selector_color \
) \
if root.is_selected and not self.disabled \
else (0, 0, 0, 0) \
) \
if self.owner.mode != "range" else \
( \
( \
self.theme_cls.primary_color if not root.owner.selector_color \
else root.owner.selector_color \
) \
if root.is_selected and not self.disabled \
and (self.owner.mode == "range" and self.owner._start_range_date) \
else (0, 0, 0, 0) \
)
Ellipse:
size:
(dp(42), dp(42)) \
if root.theme_cls.device_orientation == "portrait" \
else (dp(32), dp(32))
pos: self.pos
# Fill marking the available dates of the range, if using the `range` mode # Fill marking the available dates of the range, if using the `range` mode
# or use `min_date/max_date`. # or use `min_date/max_date`.
canvas.before: canvas.before:
@ -355,12 +327,14 @@
if root.theme_cls.device_orientation == "portrait" \ if root.theme_cls.device_orientation == "portrait" \
else \ else \
(dp(32), dp(28)) \ (dp(32), dp(28)) \
if self.index in [6, 13, 20, 27, 30] or self.owner._date_range \ if self.index in [6, 13, 20, 27, 34] or self.owner._date_range \
and self.text and self.owner._date_range[-1] == date( \ and self.text and self.owner._date_range[-1] == date( \
self.current_year, \ self.current_year, \
self.current_month, \ self.current_month, \
int(self.text) \ int(self.text) \
) \ ) \
or self.text and int(self.text) == \
calendar.monthrange(self.current_year, self.current_month)[1] \
else (dp(46), dp(28)) else (dp(46), dp(28))
pos: pos:
(self.x - dp(1.5), self.y + dp(5)) \ (self.x - dp(1.5), self.y + dp(5)) \
@ -395,29 +369,15 @@
else [0, 0, 0, 0]) \ else [0, 0, 0, 0]) \
) )
# Circle marking the beginning and end of the date range if the "range" # Selection circle.
# mode is used.
Color: Color:
rgba: rgba:
[0, 0, 0, 0] if not self.owner._date_range else \
(
( \ ( \
self.theme_cls.primary_color if not root.owner.selector_color \ self.theme_cls.primary_color if not root.owner.selector_color \
else root.owner.selector_color \ else root.owner.selector_color \
) \ ) \
if self.text and self.owner._date_range[0] == date( \ if root.is_selected and not self.disabled \
self.current_year, \ else (0, 0, 0, 0)
self.current_month, \
int(self.text) \
) \
or \
self.text and self.owner._date_range[-1] == date( \
self.current_year, \
self.current_month, \
int(self.text) \
) \
else (0, 0, 0, 0) \
)
Ellipse: Ellipse:
size: size:
(dp(42), dp(42)) \ (dp(42), dp(42)) \

View File

@ -11,65 +11,110 @@ Components/DatePicker
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png
:align: center :align: center
.. warning:: The widget is under testing. Therefore, we would be grateful if
you would let us know about the bugs found.
.. rubric:: Usage .. rubric:: Usage
.. code-block:: python .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivymd.app import MDApp .. code-block:: python
from kivymd.uix.pickers import MDDatePicker
KV = ''' from kivy.lang import Builder
MDFloatLayout:
MDTopAppBar: from kivymd.app import MDApp
title: "MDDatePicker" from kivymd.uix.pickers import MDDatePicker
pos_hint: {"top": 1}
elevation: 10
MDRaisedButton: KV = '''
text: "Open date picker" MDFloatLayout:
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_date_picker()
'''
MDRaisedButton:
class Test(MDApp): text: "Open date picker"
def build(self): pos_hint: {'center_x': .5, 'center_y': .5}
return Builder.load_string(KV) on_release: app.show_date_picker()
def on_save(self, instance, value, date_range):
'''
Events called when the "OK" dialog box button is clicked.
:type instance: <kivymd.uix.picker.MDDatePicker object>;
:param value: selected date;
:type value: <class 'datetime.date'>;
:param date_range: list of 'datetime.date' objects in the selected range;
:type date_range: <class 'list'>;
''' '''
print(instance, value, date_range)
def on_cancel(self, instance, value): class Test(MDApp):
'''Events called when the "CANCEL" dialog box button is clicked.''' def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_date_picker(self): def on_save(self, instance, value, date_range):
date_dialog = MDDatePicker() '''
date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) Events called when the "OK" dialog box button is clicked.
date_dialog.open()
:type instance: <kivymd.uix.picker.MDDatePicker object>;
:param value: selected date;
:type value: <class 'datetime.date'>;
:param date_range: list of 'datetime.date' objects in the selected range;
:type date_range: <class 'list'>;
'''
print(instance, value, date_range)
def on_cancel(self, instance, value):
'''Events called when the "CANCEL" dialog box button is clicked.'''
def show_date_picker(self):
date_dialog = MDDatePicker()
date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
date_dialog.open()
Test().run() Test().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.pickers import MDDatePicker
from kivymd.uix.screen import MDScreen
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.gif class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDRaisedButton(
text="Open data picker",
pos_hint={'center_x': .5, 'center_y': .5},
on_release=self.show_date_picker,
)
)
)
def on_save(self, instance, value, date_range):
'''
Events called when the "OK" dialog box button is clicked.
:type instance: <kivymd.uix.picker.MDDatePicker object>;
:param value: selected date;
:type value: <class 'datetime.date'>;
:param date_range: list of 'datetime.date' objects in the selected range;
:type date_range: <class 'list'>;
'''
print(instance, value, date_range)
def on_cancel(self, instance, value):
'''Events called when the "CANCEL" dialog box button is clicked.'''
def show_date_picker(self, *args):
date_dialog = MDDatePicker()
date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
date_dialog.open()
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.png
:align: center :align: center
Open date dialog with the specified date Open date dialog with the specified date
@ -81,7 +126,7 @@ Open date dialog with the specified date
date_dialog = MDDatePicker(year=1983, month=4, day=12) date_dialog = MDDatePicker(year=1983, month=4, day=12)
date_dialog.open() date_dialog.open()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/previous-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/specified-date.png
:align: center :align: center
Interval date Interval date
@ -94,12 +139,16 @@ that are not included in this range will have the status `disabled`.
def show_date_picker(self): def show_date_picker(self):
date_dialog = MDDatePicker( date_dialog = MDDatePicker(
min_date=datetime.date(2021, 2, 15), min_date=datetime.date.today(),
max_date=datetime.date(2021, 3, 27), max_date=datetime.date(
datetime.date.today().year,
datetime.date.today().month,
datetime.date.today().day + 2,
),
) )
date_dialog.open() date_dialog.open()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.png
:align: center :align: center
The range of available dates can be changed in the picker dialog: The range of available dates can be changed in the picker dialog:
@ -122,7 +171,7 @@ You can set the range of years using the :attr:`~kivymd.uix.picker.MDDatePicker.
.. code-block:: python .. code-block:: python
def show_date_picker(self): def show_date_picker(self):
date_dialog = MDDatePicker(min_year=2021, max_year=2030) date_dialog = MDDatePicker(min_year=2022, max_year=2030)
date_dialog.open() date_dialog.open()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/min-max-year-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/min-max-year-date.png
@ -141,18 +190,21 @@ Set and select a date range
:align: center :align: center
""" """
from __future__ import annotations
__all__ = ("MDDatePicker", "BaseDialogPicker", "DatePickerInputField") __all__ = ("MDDatePicker", "BaseDialogPicker", "DatePickerInputField")
import calendar import calendar
import datetime import datetime
import math
import os import os
import time import time
from datetime import date from datetime import date
from itertools import zip_longest
from typing import Union from typing import Union
from kivy import Logger from kivy import Logger
from kivy.animation import Animation from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder from kivy.lang import Builder
from kivy.metrics import dp from kivy.metrics import dp
from kivy.properties import ( from kivy.properties import (
@ -175,7 +227,7 @@ from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.toast import toast from kivymd.toast import toast
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import (
CircularRippleBehavior, CircularRippleBehavior,
FakeRectangularElevationBehavior, CommonElevationBehavior,
SpecificBackgroundColorBehavior, SpecificBackgroundColorBehavior,
) )
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
@ -194,7 +246,7 @@ with open(
class BaseDialogPicker( class BaseDialogPicker(
BaseDialog, BaseDialog,
FakeRectangularElevationBehavior, CommonElevationBehavior,
SpecificBackgroundColorBehavior, SpecificBackgroundColorBehavior,
): ):
""" """
@ -255,11 +307,11 @@ class BaseDialogPicker(
primary_color = ColorProperty(None) primary_color = ColorProperty(None)
""" """
Background color of toolbar in (r, g, b, a) format. Background color of toolbar in (r, g, b, a) or string format.
.. code-block:: python .. code-block:: python
MDDatePicker(primary_color=get_color_from_hex("#72225b")) MDDatePicker(primary_color="brown")
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-color-date.png
:align: center :align: center
@ -270,13 +322,13 @@ class BaseDialogPicker(
accent_color = ColorProperty(None) accent_color = ColorProperty(None)
""" """
Background color of calendar/clock face in (r, g, b, a) format. Background color of calendar/clock face in (r, g, b, a) or string format.
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-color-date.png
@ -288,14 +340,15 @@ class BaseDialogPicker(
selector_color = ColorProperty(None) selector_color = ColorProperty(None)
""" """
Background color of the selected day of the month or hour in (r, g, b, a) format. Background color of the selected day of the month or hour in (r, g, b, a)
or string format.
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selector-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selector-color-date.png
@ -307,15 +360,15 @@ class BaseDialogPicker(
text_toolbar_color = ColorProperty(None) text_toolbar_color = ColorProperty(None)
""" """
Color of labels for text on a toolbar in (r, g, b, a) format. Color of labels for text on a toolbar in (r, g, b, a) or string format.
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-toolbar-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-toolbar-color-date.png
@ -327,16 +380,16 @@ class BaseDialogPicker(
text_color = ColorProperty(None) text_color = ColorProperty(None)
""" """
Color of text labels in calendar/clock face in (r, g, b, a) format. Color of text labels in calendar/clock face in (r, g, b, a) or string format.
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
text_color=("#ffffff"), text_color="orange",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-color-date.png
@ -348,17 +401,18 @@ class BaseDialogPicker(
text_current_color = ColorProperty(None) text_current_color = ColorProperty(None)
""" """
Color of the text of the current day of the month/hour in (r, g, b, a) format. Color of the text of the current day of the month/hour in (r, g, b, a)
or string format.
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
text_color=("#ffffff"), text_color="orange",
text_current_color=get_color_from_hex("#e93f39"), text_current_color="white",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-current-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-current-color-date.png
@ -375,13 +429,13 @@ class BaseDialogPicker(
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
text_color=("#ffffff"), text_color="orange",
text_current_color=get_color_from_hex("#e93f39"), text_current_color="white",
text_button_color=(1, 1, 1, .5), text_button_color="lightgrey",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button-color-date.png
@ -391,52 +445,124 @@ class BaseDialogPicker(
and defaults to `None`. and defaults to `None`.
""" """
input_field_background_color = ColorProperty(None) input_field_background_color_normal = ColorProperty(None)
""" """
Background color of input fields in (r, g, b, a) format. Background color normal of input fields in (r, g, b, a) or string format.
.. versionadded:: 1.1.0
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
text_color=("#ffffff"), text_color="orange",
text_current_color=get_color_from_hex("#e93f39"), text_current_color="white",
input_field_background_color=(1, 1, 1, 0.2), text_button_color="lightgrey",
input_field_background_color_normal="coral",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png
:align: center :align: center
:attr:`input_field_background_color` is an :class:`~kivy.properties.ColorProperty` :attr:`input_field_background_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`. and defaults to `None`.
""" """
input_field_background_color_focus = ColorProperty(None)
"""
Background color normal of input fields in (r, g, b, a) or string format.
.. versionadded:: 1.1.0
.. code-block:: python
MDDatePicker(
primary_color="brown",
accent_color="darkred",
selector_color="red",
text_toolbar_color="lightgrey",
text_color="orange",
text_current_color="white",
text_button_color="lightgrey",
input_field_background_color_normal="coral",
input_field_background_color_focus="red",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-focus-date.png
:align: center
:attr:`input_field_background_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
input_field_background_color = ColorProperty(None)
"""
.. deprecated:: 1.1.0
Use :attr:`input_field_background_color_normal` instead.
"""
input_field_text_color = ColorProperty(None) input_field_text_color = ColorProperty(None)
""" """
Text color of input fields in (r, g, b, a) format. .. deprecated:: 1.1.0
Use :attr:`input_field_text_color_normal` instead.
"""
Background color of input fields. input_field_text_color_normal = ColorProperty(None)
"""
Text color normal of input fields in (r, g, b, a) or string format.
.. versionadded:: 1.1.0
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
text_color=("#ffffff"), text_color="orange",
text_current_color=get_color_from_hex("#e93f39"), text_current_color="white",
input_field_background_color=(1, 1, 1, 0.2), text_button_color="lightgrey",
input_field_text_color=(1, 1, 1, 1), input_field_background_color_normal="brown",
input_field_background_color_focus="red",
input_field_text_color_normal="white",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png
:align: center :align: center
:attr:`input_field_text_color` is an :class:`~kivy.properties.ColorProperty` :attr:`input_field_text_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
input_field_text_color_focus = ColorProperty(None)
"""
Text color focus of input fields in (r, g, b, a) or string format.
.. versionadded:: 1.1.0
.. code-block:: python
MDDatePicker(
primary_color="brown",
accent_color="darkred",
selector_color="red",
text_toolbar_color="lightgrey",
text_color="orange",
text_current_color="white",
text_button_color="lightgrey",
input_field_background_color_normal="brown",
input_field_background_color_focus="red",
input_field_text_color_normal="white",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png
:align: center
:attr:`input_field_text_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`. and defaults to `None`.
""" """
@ -447,16 +573,18 @@ class BaseDialogPicker(
.. code-block:: python .. code-block:: python
MDDatePicker( MDDatePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="darkred",
selector_color=get_color_from_hex("#e93f39"), selector_color="red",
text_toolbar_color=get_color_from_hex("#cccccc"), text_toolbar_color="lightgrey",
text_color=("#ffffff"), text_color="orange",
text_current_color=get_color_from_hex("#e93f39"), text_current_color="white",
input_field_background_color=(1, 1, 1, 0.2), text_button_color="lightgrey",
input_field_text_color=(1, 1, 1, 1), input_field_background_color_normal="brown",
font_name="Weather.ttf", input_field_background_color_focus="red",
input_field_text_color_normal="white",
input_field_text_color_focus="lightgrey",
font_name="nasalization.ttf",
) )
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-date.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-date.png
@ -471,6 +599,20 @@ class BaseDialogPicker(
self.register_event_type("on_save") self.register_event_type("on_save")
self.register_event_type("on_cancel") self.register_event_type("on_cancel")
def on_input_field_background_color(
self, instance, value: str | list | tuple
) -> None:
"""For supported of current API."""
self.input_field_background_color_normal = value
def on_input_field_text_color(
self, instance, value: str | list | tuple
) -> None:
"""For supported of current API."""
self.input_field_text_color_normal = value
def on_save(self, *args) -> None: def on_save(self, *args) -> None:
"""Events called when the "OK" dialog box button is clicked.""" """Events called when the "OK" dialog box button is clicked."""
@ -606,13 +748,18 @@ class DatePickerDaySelectableItem(
self.owner.set_selected_widget(self) self.owner.set_selected_widget(self)
def on_touch_down(self, touch):
# If year_layout is active don't dispatch on_touch_down events,
# so date items don't consume touch.
if not self.owner.ids._year_layout.disabled:
return
super().on_touch_down(touch)
class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel): class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
"""Implements an item for a pick list of the year.""" """Implements an item for a pick list of the year."""
index = None index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
selected_color = ColorProperty([0, 0, 0, 0]) selected_color = ColorProperty([0, 0, 0, 0])
owner = ObjectProperty() owner = ObjectProperty()
@ -623,7 +770,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
def on_touch_down(self, touch): def on_touch_down(self, touch):
if super().on_touch_down(touch): if super().on_touch_down(touch):
return True return True
if self.collide_point(*touch.pos) and self.selectable: if self.collide_point(*touch.pos):
self.owner.year = int(self.text) self.owner.year = int(self.text)
# self.owner.sel_year = self.owner.year # self.owner.sel_year = self.owner.year
self.owner.ids.label_full_date.text = self.owner.set_text_full_date( self.owner.ids.label_full_date.text = self.owner.set_text_full_date(
@ -635,7 +782,6 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
return self.parent.select_with_touch(self.index, touch) return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, table_data, index, is_selected): def apply_selection(self, table_data, index, is_selected):
self.selected = is_selected
if is_selected: if is_selected:
self.selected_color = ( self.selected_color = (
self.owner.selector_color self.owner.selector_color
@ -661,7 +807,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
class MDDatePicker(BaseDialogPicker): class MDDatePicker(BaseDialogPicker):
text_weekday_color = ColorProperty(None) text_weekday_color = ColorProperty(None)
""" """
Text color of weekday names in (r, g, b, a) format. Text color of weekday names in (r, g, b, a) or string format.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-text-weekday-color.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-text-weekday-color.png
:align: center :align: center
@ -813,7 +959,6 @@ class MDDatePicker(BaseDialogPicker):
_enter_data_field_two = None _enter_data_field_two = None
_enter_data_field_container = None _enter_data_field_container = None
_date_range = [] _date_range = []
_sel_day_widget = ObjectProperty()
_scale_calendar_layout = NumericProperty(1) _scale_calendar_layout = NumericProperty(1)
_scale_year_layout = NumericProperty(0) _scale_year_layout = NumericProperty(0)
_shift_dialog_height = NumericProperty(0) _shift_dialog_height = NumericProperty(0)
@ -838,11 +983,6 @@ class MDDatePicker(BaseDialogPicker):
self.month = self.sel_month self.month = self.sel_month
self.year = self.sel_year self.year = self.sel_year
self.day = self.sel_day self.day = self.sel_day
self._current_selected_date = (
self.sel_day,
self.sel_month,
self.sel_year,
)
super().__init__(**kwargs) super().__init__(**kwargs)
self.theme_cls.bind(device_orientation=self.on_device_orientation) self.theme_cls.bind(device_orientation=self.on_device_orientation)
@ -861,16 +1001,6 @@ class MDDatePicker(BaseDialogPicker):
self.generate_list_widgets_days() self.generate_list_widgets_days()
self.update_calendar(self.sel_year, self.sel_month) self.update_calendar(self.sel_year, self.sel_month)
if (
not self.max_date
and not self.min_date
and not self._date_range
and self.mode != "range"
):
# Mark the current day.
self.set_month_day(self.sel_day)
self._sel_day_widget.dispatch("on_release")
def on_device_orientation( def on_device_orientation(
self, instance_theme_manager: ThemeManager, orientation_value: str self, instance_theme_manager: ThemeManager, orientation_value: str
) -> None: ) -> None:
@ -924,18 +1054,14 @@ class MDDatePicker(BaseDialogPicker):
Animation(opacity=1, d=0.15).start(self.ids.chevron_left) Animation(opacity=1, d=0.15).start(self.ids.chevron_left)
Animation(opacity=1, d=0.15).start(self.ids.chevron_right) Animation(opacity=1, d=0.15).start(self.ids.chevron_right)
Animation(_scale_year_layout=0, d=0.15).start(self) Animation(_scale_year_layout=0, d=0.15).start(self)
Animation( Animation(_scale_calendar_layout=1, d=0.15).start(self)
_shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15
).start(self)
self._calendar_layout.clear_widgets() # Move selection to the same day and month of the selected year.
self.generate_list_widgets_days() self.sel_year = self.year
last_day = calendar.monthrange(self.year, self.sel_month)[1]
self.sel_day = min(self.sel_day, last_day)
self.update_calendar(self.year, self.month) self.update_calendar(self.year, self.month)
if self.mode != "range":
self.set_month_day(self.day)
self._sel_day_widget.dispatch("on_release")
def transformation_to_dialog_select_year(self) -> None: def transformation_to_dialog_select_year(self) -> None:
def disabled_chevron_buttons(*args): def disabled_chevron_buttons(*args):
self.ids.chevron_left.disabled = True self.ids.chevron_left.disabled = True
@ -943,15 +1069,20 @@ class MDDatePicker(BaseDialogPicker):
self._select_year_dialog_open = True self._select_year_dialog_open = True
self.ids._year_layout.disabled = False self.ids._year_layout.disabled = False
self._scale_calendar_layout = 0
Animation(opacity=0, d=0.15).start(self.ids.chevron_left) Animation(opacity=0, d=0.15).start(self.ids.chevron_left)
Animation(opacity=0, d=0.15).start(self.ids.chevron_right) Animation(opacity=0, d=0.15).start(self.ids.chevron_right)
Animation(_scale_calendar_layout=0, d=0.15).start(self)
anim = Animation(_scale_year_layout=1, d=0.15) anim = Animation(_scale_year_layout=1, d=0.15)
anim.bind(on_complete=disabled_chevron_buttons) anim.bind(on_complete=disabled_chevron_buttons)
anim.start(self) anim.start(self)
self.ids.triangle.icon = "menu-up" self.ids.triangle.icon = "menu-up"
self.generate_list_widgets_years() self.generate_list_widgets_years()
self.set_position_to_current_year() self.set_position_to_current_year()
if self.min_year <= self.year < self.max_year:
index = self.year - self.min_year
self.ids._year_layout.children[0].select_node(index)
else:
self.ids._year_layout.children[0].clear_selection()
def transformation_to_dialog_input_date(self) -> None: def transformation_to_dialog_input_date(self) -> None:
def set_date_to_input_field(): def set_date_to_input_field():
@ -1063,13 +1194,10 @@ class MDDatePicker(BaseDialogPicker):
if not self.min_date and not self.max_date: if not self.min_date and not self.max_date:
list_date = self._enter_data_field.get_list_date() list_date = self._enter_data_field.get_list_date()
if len(list_date) == 3 and len(list_date[2]) == 4: if len(list_date) == 3 and len(list_date[2]) == 4:
# self._sel_day_widget.is_selected = False self.sel_day = int(list_date[0])
self.update_calendar(int(list_date[2]), int(list_date[1])) self.sel_month = int(list_date[1])
self.set_month_day(int(list_date[0])) self.sel_year = int(list_date[2])
# self._sel_day_widget.dispatch("on_release") self.update_calendar(self.sel_year, self.sel_month)
if self.mode != "range":
self._sel_day_widget.is_selected = False
self._sel_day_widget.dispatch("on_release")
elif self.min_date and self.max_date: elif self.min_date and self.max_date:
list_min_date = self._enter_data_field.get_list_date() list_min_date = self._enter_data_field.get_list_date()
list_max_date = self._enter_data_field_two.get_list_date() list_max_date = self._enter_data_field_two.get_list_date()
@ -1107,8 +1235,6 @@ class MDDatePicker(BaseDialogPicker):
def update_calendar_for_date_range(self) -> None: def update_calendar_for_date_range(self) -> None:
# self.compare_date_range() # self.compare_date_range()
self._date_range = self.get_date_range() self._date_range = self.get_date_range()
self._calendar_layout.clear_widgets()
self.generate_list_widgets_days()
self.update_calendar(self.year, self.month) self.update_calendar(self.year, self.month)
def update_text_full_date(self, list_date) -> None: def update_text_full_date(self, list_date) -> None:
@ -1140,79 +1266,72 @@ class MDDatePicker(BaseDialogPicker):
) )
def update_calendar(self, year, month) -> None: def update_calendar(self, year, month) -> None:
try: self.year, self.month = year, month
dates = [x for x in self.calendar.itermonthdates(year, month)] if self.mode == "picker":
except ValueError as e: selected_date = date(self.sel_year, self.sel_month, self.sel_day)
if str(e) == "year is out of range": selected_dates = {selected_date}
pass
else: else:
self.year = year selected_dates = {self._start_range_date, self._end_range_date}
self.month = month dates = self.calendar.itermonthdates(year, month)
for idx in range(len(self._calendar_list)): for widget, widget_date in zip_longest(self._calendar_list, dates):
self._calendar_list[idx].current_month = int(self.month) # Only widgets whose dates are in the displayed month are visible.
self._calendar_list[idx].current_year = int(self.year) visible = (
widget_date is not None
# Dates of the month not in the range 1-31. and widget_date.month == month
if idx >= len(dates) or dates[idx].month != month: and widget_date.year == year
# self._calendar_list[idx].disabled = True )
self._calendar_list[idx].text = "" widget.text = str(widget_date.day) if visible else ""
# Dates of the month in the range 1-31. widget.current_year = year
else: widget.current_month = month
self._calendar_list[idx].disabled = False widget.is_today = visible and widget_date == self.today
self._calendar_list[idx].text = str(dates[idx].day) widget.is_selected = visible and widget_date in selected_dates
self._calendar_list[idx].is_today = dates[idx] == self.today # I don't understand why, but this line is important. Without this
# The marked date widget has a True value in the `is_selected` # line, some widgets that we are trying to disable remain enabled.
# attribute. In the KV file it is checked if the date widget widget.disabled = False
# (DatePickerDaySelectableItem) has the `is_selected = False` widget.disabled = (
# attribute value, then the date widget is not highlighted. not visible
if ( or self.mode == "range"
0 and self._date_range
if not self._calendar_list[idx].text and widget_date not in self._date_range
else int(self._calendar_list[idx].text), )
self._calendar_list[idx].current_month,
self._calendar_list[idx].current_year,
) == self._current_selected_date:
self._calendar_list[idx].is_selected = True
else:
self._calendar_list[idx].is_selected = False
# Dates outside the set range - disabled.
if (
self.mode == "picker"
and self._date_range
and self._calendar_list[idx].text
) or (
self.mode == "range"
and self._start_range_date
and self._end_range_date
and self._calendar_list[idx].text
):
if (
date(
self._calendar_list[idx].current_year,
self._calendar_list[idx].current_month,
int(self._calendar_list[idx].text),
)
not in self._date_range
):
self._calendar_list[idx].disabled = True
def get_field(self) -> MDTextField: def get_field(self) -> MDTextField:
"""Creates and returns a text field object used to enter dates.""" """Creates and returns a text field object used to enter dates."""
if issubclass(self.input_field_cls, MDTextField): if issubclass(self.input_field_cls, MDTextField):
text_color_focus = (
self.input_field_text_color_focus
if self.input_field_text_color_focus
else self.theme_cls.primary_color
)
text_color_normal = (
self.input_field_text_color_normal
if self.input_field_text_color_normal
else self.theme_cls.disabled_hint_text_color
)
fill_color_focus = (
self.input_field_background_color_focus
if self.input_field_background_color_focus
else self.theme_cls.bg_dark
)
fill_color_normal = (
self.input_field_background_color_normal
if self.input_field_background_color_normal
else self.theme_cls.bg_darkest
)
field = self.input_field_cls( field = self.input_field_cls(
owner=self, owner=self,
helper_text=self.helper_text, helper_text=self.helper_text,
line_color_normal=self.theme_cls.divider_color, fill_color_normal=fill_color_normal,
fill_color_focus=fill_color_focus,
hint_text_color_normal=text_color_normal,
hint_text_color_focus=text_color_focus,
text_color_normal=text_color_normal,
text_color_focus=text_color_focus,
line_color_focus=text_color_focus,
line_color_normal=text_color_normal,
) )
field.color_mode = "custom"
field.line_color_focus = (
self.theme_cls.primary_color
if not self.input_field_text_color
else self.input_field_text_color
)
field.current_hint_text_color = field.line_color_focus
field._current_hint_text_color = field.line_color_focus
return field return field
else: else:
raise TypeError( raise TypeError(
@ -1239,10 +1358,7 @@ class MDDatePicker(BaseDialogPicker):
"set_text_full_date:\n\t" f"Month [{month}] out of range." "set_text_full_date:\n\t" f"Month [{month}] out of range."
) )
if int(day) > calendar.monthrange(int(year), (month))[1]: if int(day) > calendar.monthrange(int(year), (month))[1]:
raise ValueError( return ""
"set_text_full_date:\n\t"
f"Day [{day}] out of range for the month {month}"
)
date = datetime.date(int(year), int(month), int(day)) date = datetime.date(int(year), int(month), int(day))
separator = ( separator = (
"\n" "\n"
@ -1345,46 +1461,55 @@ class MDDatePicker(BaseDialogPicker):
) )
def set_selected_widget(self, widget) -> None: def set_selected_widget(self, widget) -> None:
if self._sel_day_widget: self.sel_year = self.year
self._sel_day_widget.is_selected = False self.sel_month = self.month
widget.is_selected = True
self.sel_month = int(self.month)
self.sel_year = int(self.year)
self.sel_day = int(widget.text) self.sel_day = int(widget.text)
self._current_selected_date = ( self.update_calendar(self.sel_year, self.sel_month)
self.sel_day,
self.sel_month,
self.sel_year,
)
self._sel_day_widget = widget
def set_month_day(self, day) -> None: def set_month_day(self, day) -> None:
for idx in range(len(self._calendar_list)): # This method is no longer used. The code bellow repeats the behavior
if str(day) == str(self._calendar_list[idx].text): # that was previously required of it for backward compatibility
self._sel_day_widget = self._calendar_list[idx] # reasons.
self.sel_day = int(self._calendar_list[idx].text) self.sel_day = day
if self._sel_day_widget: self.update_calendar(self.sel_year, self.sel_month)
self._sel_day_widget.is_selected = False
self._sel_day_widget = self._calendar_list[idx]
def set_position_to_current_year(self) -> None: def set_position_to_current_year(self) -> None:
# TODO: Add the feature to set the position of the list of years year_layout = self.ids._year_layout
# for the current year. This is not currently possible because the # When this method is called for the first time, RecycleView has not
# ``RecycleView`` class does not support this functionality. # yet added widgets to the year list, so we use the default height.
# There is a solution to this problem widget_height = year_layout.children[0].default_size[1]
# - https://github.com/Bakterija/log_fruit/blob/dev/src/app_modules/widgets/app_recycleview/recycleview.py. cols_amount = year_layout.children[0].cols
# But I have not been able to get it to work. rows_amount = math.ceil((self.max_year - self.min_year) / cols_amount)
pass row_index = (self.year - self.min_year) // cols_amount
# To find the middle of the current year widget, we add the height of
# the rows under this widget with half the widget height.
widget_center_y = (rows_amount - row_index - 1 + 0.5) * widget_height
viewport_height = year_layout.height
year_list_height = rows_amount * widget_height
# If there are too few years in the list to fill the entire viewport,
# RecycleView displays additional empty space outside the list.
# We have to move the viewport up so that this space is displayed
# under the years list. Also, this guard condition protects against
# the division by zero error below.
if viewport_height >= year_list_height:
year_layout.scroll_y = 1
return
viewport_bottom = widget_center_y - 0.5 * viewport_height
# We set scroll_y property to the ratio of the actual lifting height
# of the viewport to the maximum possible, and clamp this ratio in the
# range from 0 to 1 so that the viewport still is in a valid position
# if it is impossible to show the widget in the middle.
scroll_y = viewport_bottom / (year_list_height - viewport_height)
year_layout.scroll_y = min(1, max(0, scroll_y))
def generate_list_widgets_years(self) -> None: def generate_list_widgets_years(self) -> None:
self.ids._year_layout.data = []
for i, number_year in enumerate(range(self.min_year, self.max_year)): for i, number_year in enumerate(range(self.min_year, self.max_year)):
self.ids._year_layout.data.append( self.ids._year_layout.data.append(
{ {
"owner": self, "owner": self,
"text": str(number_year), "text": str(number_year),
"index": i, "index": i,
"selectable": True,
"viewclass": "DatePickerYearSelectableItem", "viewclass": "DatePickerYearSelectableItem",
} }
) )
@ -1416,26 +1541,9 @@ class MDDatePicker(BaseDialogPicker):
Called when "chevron-left" and "chevron-right" buttons are pressed. Called when "chevron-left" and "chevron-right" buttons are pressed.
Switches the calendar to the previous/next month. Switches the calendar to the previous/next month.
""" """
month_delta = 1 if operation == "next" else -1
operation = 1 if operation == "next" else -1 year = self.year + (self.month - 1 + month_delta) // 12
month = ( month = (self.month - 1 + month_delta) % 12 + 1
12 if year <= 0:
if self.month + operation == 0 year, month = 1, 1
else 1
if self.month + operation == 13
else self.month + operation
)
year = (
self.year - 1
if self.month + operation == 0
else self.year + 1
if self.month + operation == 13
else self.year
)
self.update_calendar(year, month) self.update_calendar(year, month)
if self.sel_day:
x = calendar.monthrange(year, month)[1]
if x < self.sel_day:
self.sel_day = (
x if year <= self.sel_year and month <= self.sel_year else 1
)

View File

@ -16,35 +16,73 @@ Components/TimePicker
.. rubric:: Usage .. rubric:: Usage
.. code-block:: .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivymd.app import MDApp .. code-block:: python
from kivymd.uix.pickers import MDTimePicker
KV = ''' from kivy.lang import Builder
MDFloatLayout:
MDRaisedButton: from kivymd.app import MDApp
text: "Open time picker" from kivymd.uix.pickers import MDTimePicker
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_time_picker() KV = '''
''' MDFloatLayout:
MDRaisedButton:
text: "Open time picker"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_time_picker()
'''
class Test(MDApp): class Test(MDApp):
def build(self): def build(self):
return Builder.load_string(KV) self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_time_picker(self): def show_time_picker(self):
'''Open time picker dialog.''' '''Open time picker dialog.'''
time_dialog = MDTimePicker() time_dialog = MDTimePicker()
time_dialog.open() time_dialog.open()
Test().run() Test().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.pickers import MDTimePicker
from kivymd.uix.screen import MDScreen
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDRaisedButton(
text="Open time picker",
pos_hint={'center_x': .5, 'center_y': .5},
on_release=self.show_time_picker,
)
)
)
def show_time_picker(self, *args):
'''Open time picker dialog.'''
MDTimePicker().open()
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDTimePicker.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDTimePicker.png
:align: center :align: center
@ -91,14 +129,14 @@ Use the :attr:`~MDTimePicker.set_time` method of the
.. code-block:: python .. code-block:: python
time_dialog = MDTimePicker( MDTimePicker(
primary_color=get_color_from_hex("#72225b"), primary_color="brown",
accent_color=get_color_from_hex("#5d1a4a"), accent_color="red",
text_button_color=(1, 1, 1, 1), text_button_color="white",
) ).open()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png
:align: center :align: center
""" """
__all__ = ("MDTimePicker",) __all__ = ("MDTimePicker",)
@ -194,8 +232,8 @@ class TimeInputTextField(MDTextField):
hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$" hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$"
minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$" minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$"
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(**kwargs) super().__init__(*args, **kwargs)
Clock.schedule_once(self.set_text) Clock.schedule_once(self.set_text)
self.register_event_type("on_select") self.register_event_type("on_select")
self.bind(text_color_focus=self.setter("hint_text_color_normal")) self.bind(text_color_focus=self.setter("hint_text_color_normal"))
@ -217,17 +255,20 @@ class TimeInputTextField(MDTextField):
to somehow make them aligned. to somehow make them aligned.
""" """
if not self.text: def set_text(*args):
self.text = " " if not self.text:
self.text = " "
self._refresh_text(self.text) self._refresh_text(self.text)
max_size = max(self._lines_rects, key=lambda r: r.size[0]).size max_size = max(self._lines_rects, key=lambda r: r.size[0]).size
dx = (self.width - max_size[0]) / 2.0 dx = (self.width - max_size[0]) / 2.0
dy = (self.height - max_size[1]) / 2.0 dy = (self.height - max_size[1]) / 2.0
self.padding = [dx, dy, dx, dy] self.padding = [dx, dy, dx, dy]
if len(self.text) > 1: if len(self.text) > 1:
self.text = self.text.replace(" ", "") self.text = self.text.replace(" ", "")
Clock.schedule_once(set_text)
def on_focus(self, *args) -> None: def on_focus(self, *args) -> None:
super().on_focus(*args) super().on_focus(*args)

View File

@ -36,7 +36,7 @@ Example
title: app.title title: app.title
md_bg_color: app.theme_cls.primary_color md_bg_color: app.theme_cls.primary_color
background_palette: 'Primary' background_palette: 'Primary'
elevation: 10 elevation: 4
left_action_items: [['menu', lambda x: x]] left_action_items: [['menu', lambda x: x]]
MDScrollViewRefreshLayout: MDScrollViewRefreshLayout:

View File

@ -29,7 +29,7 @@ MDScreen
md_bg_color: app.theme_cls.primary_color md_bg_color: app.theme_cls.primary_color
""" """
from kivy.properties import ObjectProperty from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.screenmanager import Screen from kivy.uix.screenmanager import Screen
from kivymd.uix import MDAdaptiveWidget from kivymd.uix import MDAdaptiveWidget
@ -44,20 +44,34 @@ class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
see in the :class:`~kivy.uix.screenmanager.Screen` class documentation. see in the :class:`~kivy.uix.screenmanager.Screen` class documentation.
""" """
hero_to = ObjectProperty() hero_to = ObjectProperty(deprecated=True)
""" """
Must be a :class:`~kivymd.uix.hero.MDHeroTo` class. Must be a :class:`~kivymd.uix.hero.MDHeroTo` class.
See the documentation of the See the documentation of the
`MDHeroTo <https://kivymd.readthedocs.io/en/latest/components/hero/>`_ `MDHeroTo <https://kivymd.readthedocs.io/en/latest/components/hero/>`_
widget for more detailed information. widget for more detailed information.
.. versionchanged:: 1.0.0 .. deprecated:: 1.0.0
Use attr:`heroes_to` attribute instead.
:attr:`hero_to` is an :class:`~kivy.properties.ObjectProperty` :attr:`hero_to` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`. and defaults to `None`.
""" """
def on_hero_to(self, screen, widget) -> None: heroes_to = ListProperty()
"""
Must be a list of :class:`~kivymd.uix.hero.MDHeroTo` class.
.. versionadded:: 1.0.0
:attr:`heroes_to` is an :class:`~kivy.properties.LiatProperty`
and defaults to `[]`.
"""
def on_hero_to(self, screen, widget: MDHeroTo) -> None:
"""Called when the value of the :attr:`hero_to` attribute changes."""
if not isinstance(widget, MDHeroTo) or not issubclass( if not isinstance(widget, MDHeroTo) or not issubclass(
widget.__class__, MDHeroTo widget.__class__, MDHeroTo
): ):
@ -65,3 +79,4 @@ class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
f"The `{widget}` widget must be an `kivymd.uix.hero.MDHeroTo` " f"The `{widget}` widget must be an `kivymd.uix.hero.MDHeroTo` "
f"class or inherited from this class" f"class or inherited from this class"
) )
self.heroes_to = [widget]

View File

@ -8,8 +8,23 @@ Components/ScreenManager
If you want to use Hero animations you need to use If you want to use Hero animations you need to use
:class:`~kivymd.uix.screenmanager.MDScreenManager` not :class:`~kivymd.uix.screenmanager.MDScreenManager` not
:class:`~kivy.uix.screenmanager.ScreenManager` class. :class:`~kivy.uix.screenmanager.ScreenManager` class.
Transition
----------
:class:`~kivymd.uix.screenmanager.MDScreenManager` class supports the following
transitions:
- :class:`~kivymd.uix.transition.MDFadeSlideTransition`
- :class:`~kivymd.uix.transition.MDSlideTransition`
- :class:`~kivymd.uix.transition.MDSwapTransition`
You need to use the :class:`~kivymd.uix.screenmanager.MDScreenManager` class
when you want to use hero animations on your screens. If you don't need hero
animation use the :class:`~kivy.uix.screenmanager.ScreenManager` class.
""" """
from kivy import Logger
from kivy.clock import Clock from kivy.clock import Clock
from kivy.properties import ListProperty, StringProperty from kivy.properties import ListProperty, StringProperty
from kivy.uix.screenmanager import ScreenManager from kivy.uix.screenmanager import ScreenManager
@ -21,17 +36,22 @@ from kivymd.uix.hero import MDHeroFrom
class MDScreenManager(DeclarativeBehavior, ScreenManager): class MDScreenManager(DeclarativeBehavior, ScreenManager):
""" """
Screen manager. This is the main class that will control your Screen manager. This is the main class that will control your
:class:`~kivymd.uix.screen.MDScreen` stack and memory. For more :class:`~kivymd.uix.screen.MDScreen` stack and memory.
For more
information, see in the :class:`~kivy.uix.screenmanager.ScreenManager` information, see in the :class:`~kivy.uix.screenmanager.ScreenManager`
class documentation. class documentation.
""" """
current_hero = StringProperty(None) current_hero = StringProperty(None, deprecated=True)
""" """
The name of the current tag for the :class:`~kivymd.uix.hero.MDHeroFrom` The name of the current tag for the :class:`~kivymd.uix.hero.MDHeroFrom`
and :class:`~kivymd.uix.hero.MDHeroTo` objects that will be animated when and :class:`~kivymd.uix.hero.MDHeroTo` objects that will be animated when
animating the transition between screens. animating the transition between screens.
.. deprecated:: 1.1.0
Use :attr:`current_heroes` attribute instead.
See the `Hero <https://kivymd.readthedocs.io/en/latest/components/hero/>`_ See the `Hero <https://kivymd.readthedocs.io/en/latest/components/hero/>`_
module documentation for more information about creating and using Hero module documentation for more information about creating and using Hero
animations. animations.
@ -40,6 +60,17 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager):
and defaults to `None`. and defaults to `None`.
""" """
current_heroes = ListProperty()
"""
A list of names (tags) of heroes that need to be animated when moving
to the next screen.
.. versionadded:: 1.1.0
:attr:`current_heroes` is an :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
# Collection of `MDHeroFrom` objects on all screens of the current # Collection of `MDHeroFrom` objects on all screens of the current
# screen manager. # screen manager.
_heroes_data = ListProperty() _heroes_data = ListProperty()
@ -58,28 +89,48 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager):
self.transition = MDSlideTransition() self.transition = MDSlideTransition()
def get_hero_from_widget(self) -> None: def get_hero_from_widget(self) -> list:
""" """
Get an :class:`~kivymd.uix.hero.MDHeroTo` object with the Get a list of :class:`~kivymd.uix.hero.MDHeroFrom` objects according
:attr:`~current_hero` tag. to the tag names specified in the :attr:`~current_heroes` list.
""" """
hero_from_widget = None hero_from_widget = []
for hero_widget in self._heroes_data: for name_hero in self.current_heroes:
if isinstance(hero_widget, MDHeroFrom) or issubclass( for hero_widget in self._heroes_data:
hero_widget.__class__, MDHeroFrom if isinstance(hero_widget, MDHeroFrom) or issubclass(
): hero_widget.__class__, MDHeroFrom
if hero_widget.tag == self.current_hero: ):
hero_from_widget = hero_widget if hero_widget.tag == name_hero:
break hero_from_widget.append(hero_widget)
return hero_from_widget return hero_from_widget
def on_current_hero(self, instance, value: str) -> None:
"""
Called when the value of the :attr:`current_hero` attribute changes.
"""
Logger.warning(
"KivyMD: "
"`kivymd/uix/screenmanager.MDScreenManager.current_hero` "
"attribute is deprecated. "
"Use `kivymd/uix/screenmanager.MDScreenManager.current_heroes` "
"attribute instead."
)
if value:
self.current_heroes = [value]
else:
self.current_heroes = []
def add_widget(self, widget, *args, **kwargs): def add_widget(self, widget, *args, **kwargs):
super().add_widget(widget, *args, **kwargs) super().add_widget(widget, *args, **kwargs)
Clock.schedule_once(lambda x: self._create_heroes_data(widget)) Clock.schedule_once(lambda x: self._create_heroes_data(widget))
# TODO: Add a method to delete an object from the arrt:`_heroes_data`
# collection when deleting an object using the `remove_widget` method.
def _create_heroes_data(self, widget): def _create_heroes_data(self, widget):
def find_hero_widget(child_widget): def find_hero_widget(child_widget):
widget_hero = None widget_hero = None

View File

@ -15,8 +15,11 @@
pos_hint: {"center_y": .5} pos_hint: {"center_y": .5}
x: root._segment_switch_x x: root._segment_switch_x
md_bg_color: root.segment_color md_bg_color: root.segment_color
elevation: 6 elevation: 2
_radius: root.radius[0] - 4 _radius: root.radius[0] - 4
width:
segment_panel.width / segment_panel.children_number \
- segment_panel.spacing
SegmentPanel: SegmentPanel:
id: segment_panel id: segment_panel

View File

@ -10,58 +10,77 @@ Components/SegmentedControl
Usage Usage
===== =====
.. code-block:: python .. tabs::
from kivy.lang import Builder .. tab:: Declarative KV style
from kivymd.app import MDApp .. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = ''' KV = '''
MDScreen: MDScreen:
MDSegmentedControl: MDSegmentedControl:
pos_hint: {"center_x": .5, "center_y": .5} pos_hint: {"center_x": .5, "center_y": .5}
MDSegmentedControlItem: MDSegmentedControlItem:
text: "Male" text: "Male"
MDSegmentedControlItem: MDSegmentedControlItem:
text: "Female" text: "Female"
MDSegmentedControlItem: MDSegmentedControlItem:
text: "All" text: "All"
''' '''
class Test(MDApp): class Example(MDApp):
def build(self): def build(self):
return Builder.load_string(KV) self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
Test().run() Example().run()
Or only in python code: .. tab:: Declarative python style
.. code-block:: python .. code-block:: python
from kivymd.app import MDApp from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen from kivymd.uix.screen import MDScreen
from kivymd.uix.segmentedcontrol import MDSegmentedControl, MDSegmentedControlItem from kivymd.uix.segmentedcontrol import (
MDSegmentedControl, MDSegmentedControlItem
)
class Test(MDApp): class Example(MDApp):
def build(self): def build(self):
screen = MDScreen() self.theme_cls.theme_style = "Dark"
segment_control = MDSegmentedControl(pos_hint={"center_x": .5, "center_y": .5}) self.theme_cls.primary_palette = "Orange"
segment_control.add_widget(MDSegmentedControlItem(text="Male")) return (
segment_control.add_widget(MDSegmentedControlItem(text="Female")) MDScreen(
segment_control.add_widget(MDSegmentedControlItem(text="All")) MDSegmentedControl(
screen.add_widget(segment_control) MDSegmentedControlItem(
return screen text="Male"
),
MDSegmentedControlItem(
text="Female"
),
MDSegmentedControlItem(
text="All"
),
pos_hint={"center_x": 0.5, "center_y": 0.5}
)
)
)
Test().run() Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-usage.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-usage.gif
:align: center :align: center
@ -117,12 +136,22 @@ with open(
class MDSegmentedControlItem(MDLabel): class MDSegmentedControlItem(MDLabel):
"""Implements a label to place on the :class:`~SegmentPanel` panel.""" """
Implements a label to place on the :class:`~SegmentPanel` panel.
See :class:`~kivymd.uix.label.MDLabel` class documentation for more
information.
"""
# TODO: Add an attribute for the color of the active segment label. # TODO: Add an attribute for the color of the active segment label.
class MDSegmentedControl(MDRelativeLayout, ThemableBehavior): class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
""" """
Implements a segmented control panel.
Relative layout class. For more information, see in the
:class:`~kivy.uix.relativelayout.RelativeLayout` class documentation.
:Events: :Events:
`on_active` `on_active`
Called when the segment is activated. Called when the segment is activated.
@ -135,7 +164,7 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv .. code-block:: kv
MDSegmentedControl: MDSegmentedControl:
md_bg_color: "#451938" md_bg_color: "brown"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-md-bg-color.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-md-bg-color.png
:align: center :align: center
@ -151,8 +180,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv .. code-block:: kv
MDSegmentedControl: MDSegmentedControl:
md_bg_color: "#451938" md_bg_color: "brown"
segment_color: "#e4514f" segment_color: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-color.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-color.png
:align: center :align: center
@ -160,8 +189,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv .. code-block:: kv
MDSegmentedControl: MDSegmentedControl:
md_bg_color: "#451938" md_bg_color: "brown"
segment_color: "#e4514f" segment_color: "red"
MDSegmentedControlItem: MDSegmentedControlItem:
text: "[color=fff]Male[/color]" text: "[color=fff]Male[/color]"
@ -196,9 +225,9 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv .. code-block:: kv
MDSegmentedControl: MDSegmentedControl:
md_bg_color: "#451938" md_bg_color: "brown"
segment_color: "#e4514f" segment_color: "red"
separator_color: 1, 1, 1, 1 separator_color: "white"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-separator-color.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-separator-color.png
:align: center :align: center
@ -255,9 +284,6 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
Clock.schedule_once(self.set_default_colors) Clock.schedule_once(self.set_default_colors)
Clock.schedule_once(self._remove_last_separator) Clock.schedule_once(self._remove_last_separator)
# FIXME: Sometimes this interval is not enough to get the width
# of the segment label textures.
Clock.schedule_once(self._set_width_segment_switch, 2.2)
def set_default_colors(self, *args) -> None: def set_default_colors(self, *args) -> None:
""" """
@ -313,6 +339,10 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
self.ids.segment_panel.add_widget(widget) self.ids.segment_panel.add_widget(widget)
separator = MDSeparator(orientation="vertical") separator = MDSeparator(orientation="vertical")
self.ids.segment_panel.add_widget(separator) self.ids.segment_panel.add_widget(separator)
if not self.ids.segment_panel._started:
self.ids.segment_panel._started = True
else:
self.ids.segment_panel.children_number += 1
Clock.schedule_once( Clock.schedule_once(
lambda x: self.update_separator_color(separator) lambda x: self.update_separator_color(separator)
) )
@ -326,15 +356,6 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
self.current_active_segment = widget self.current_active_segment = widget
self.dispatch("on_active", widget) self.dispatch("on_active", widget)
def _set_width_segment_switch(self, *args):
"""
Sets the width of the switch. I think this is not done quite correctly.
"""
self.ids.segment_switch.width = self.ids.segment_panel.children[
0
].width + dp(12)
def _remove_last_separator(self, *args): def _remove_last_separator(self, *args):
self.ids.segment_panel.remove_widget(self.ids.segment_panel.children[0]) self.ids.segment_panel.remove_widget(self.ids.segment_panel.children[0])
@ -350,3 +371,7 @@ class SegmentPanel(MDBoxLayout):
Implements a panel for placing items - :class:`~MDSegmentedControlItem` Implements a panel for placing items - :class:`~MDSegmentedControlItem`
for the :class:`~MDSegmentedControl` class. for the :class:`~MDSegmentedControl` class.
""" """
children_number = NumericProperty(1)
_started = BooleanProperty(defaultvalue=False)

View File

@ -101,7 +101,7 @@
size_hint: None, None size_hint: None, None
size: dp(24), dp(24) size: dp(24), dp(24)
elevation: elevation:
(8 if root.active else 5) \ (2.5 if root.active else 1) \
if app.theme_cls.material_style != "M3" else \ if app.theme_cls.material_style != "M3" else \
0 0
pos: pos:

View File

@ -192,15 +192,10 @@ from kivy.properties import (
) )
from kivy.uix.behaviors import ToggleButtonBehavior from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.floatlayout import FloatLayout from kivy.uix.floatlayout import FloatLayout
from kivy.utils import get_color_from_hex
from kivymd import uix_path from kivymd import uix_path
from kivymd.color_definitions import colors
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
CircularRippleBehavior,
FakeCircularElevationBehavior,
)
from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.label import MDIcon from kivymd.uix.label import MDIcon
@ -361,8 +356,10 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
disabled=self.update_color, disabled=self.update_color,
state=self.update_color, state=self.update_color,
) )
self.theme_cls.bind(primary_color=self.update_primary_color) self.theme_cls.bind(
self.theme_cls.bind(theme_style=self.update_primary_color) theme_style=self.update_primary_color,
primary_color=self.update_primary_color,
)
self.update_icon() self.update_icon()
self.update_color() self.update_color()
@ -423,7 +420,7 @@ class ThumbIcon(MDIcon):
class Thumb( class Thumb(
FakeCircularElevationBehavior, CommonElevationBehavior,
CircularRippleBehavior, CircularRippleBehavior,
MDFloatLayout, MDFloatLayout,
): ):

View File

@ -3,7 +3,7 @@
#:import colors kivymd.color_definitions.colors #:import colors kivymd.color_definitions.colors
<HintBoxContainer@MDCard+FakeRectangularElevationBehavior> <HintBoxContainer@MDCard>
<MDSlider> <MDSlider>
@ -131,16 +131,20 @@
) \ ) \
) \ ) \
) )
elevation: 0 if root._is_off else (4 if root.active else 2) elevation: 0 if root._is_off else (3 if root.active else 1)
HintBoxContainer: HintBoxContainer:
id: hint_box id: hint_box
size_hint: None, None size_hint: None, None
md_bg_color: root.hint_bg_color md_bg_color: root.hint_bg_color if root.hint_bg_color else [0, 0, 0, 0]
elevation: 0 elevation: 1.5
opacity: 1 if root.active else 0 opacity: 1 if root.active else 0
radius: root.hint_radius radius: root.hint_radius
padding: "6dp", "6dp", "6dp", "8dp" padding: "6dp", "6dp", "6dp", "8dp"
shadow_color:
([0, 0, 0, 0.6] if root.hint_bg_color else [0, 0, 0, 0]) \
if root.active else \
[0, 0, 0, 0]
size: size:
lbl_value.width + self.padding[0] * 2, \ lbl_value.width + self.padding[0] * 2, \
lbl_value.height + self.padding[0] lbl_value.height + self.padding[0]

View File

@ -82,7 +82,7 @@ class MDSlider(ThemableBehavior, Slider):
and defaults to `True`. and defaults to `True`.
""" """
hint_bg_color = ColorProperty([0, 0, 0, 0]) hint_bg_color = ColorProperty(None)
""" """
Hint rectangle color in (r.g.b.a) format. Hint rectangle color in (r.g.b.a) format.

View File

@ -38,8 +38,8 @@ Example
from kivy.lang.builder import Builder from kivy.lang.builder import Builder
from kivymd.app import MDApp
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
KV = ''' KV = '''
<CardItem> <CardItem>
@ -47,7 +47,6 @@ Example
height: "86dp" height: "86dp"
padding: "4dp" padding: "4dp"
radius: 12 radius: 12
elevation: 4
FitImage: FitImage:
source: "avatar.jpg" source: "avatar.jpg"
@ -95,8 +94,10 @@ Example
''' '''
class CardItem(MDCard, RoundedRectangularElevationBehavior): class CardItem(MDCard):
pass def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.elevation = 3
class Example(MDApp): class Example(MDApp):
@ -192,7 +193,6 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
from kivymd.uix.toolbar import MDTopAppBar from kivymd.uix.toolbar import MDTopAppBar
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
KV = ''' KV = '''
#:import SliverToolbar __main__.SliverToolbar #:import SliverToolbar __main__.SliverToolbar
@ -203,7 +203,6 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
height: "86dp" height: "86dp"
padding: "4dp" padding: "4dp"
radius: 12 radius: 12
elevation: 4
FitImage: FitImage:
source: "avatar.jpg" source: "avatar.jpg"
@ -252,13 +251,16 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
''' '''
class CardItem(MDCard, RoundedRectangularElevationBehavior): class CardItem(MDCard):
pass def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.elevation = 3
class SliverToolbar(MDTopAppBar): class SliverToolbar(MDTopAppBar):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.shadow_color = (0, 0, 0, 0)
self.type_height = "medium" self.type_height = "medium"
self.headline_text = "Headline medium" self.headline_text = "Headline medium"
self.left_action_items = [["arrow-left", lambda x: x]] self.left_action_items = [["arrow-left", lambda x: x]]
@ -422,6 +424,7 @@ class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
# Adding a custom MDTopAppBar object. # Adding a custom MDTopAppBar object.
if issubclass(instance_toolbar_cls.__class__, MDTopAppBar): if issubclass(instance_toolbar_cls.__class__, MDTopAppBar):
instance_toolbar_cls.pos_hint = {"top": 1} instance_toolbar_cls.pos_hint = {"top": 1}
instance_toolbar_cls.elevation = 0
self.ids.float_box.add_widget(instance_toolbar_cls) self.ids.float_box.add_widget(instance_toolbar_cls)
else: else:
raise MDSliverAppbarException( raise MDSliverAppbarException(

View File

@ -8,7 +8,7 @@
padding: "10dp", "10dp", "10dp", "10dp" padding: "10dp", "10dp", "10dp", "10dp"
md_bg_color: "323232" if not root.bg_color else root.bg_color md_bg_color: "323232" if not root.bg_color else root.bg_color
radius: root.radius radius: root.radius
elevation: 11 if root.padding else 0 elevation: 4 if root.padding else 0
canvas: canvas:
Color: Color:

View File

@ -284,7 +284,6 @@ from kivy.properties import (
) )
from kivymd import uix_path from kivymd import uix_path
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.button import BaseButton from kivymd.uix.button import BaseButton
from kivymd.uix.card import MDCard from kivymd.uix.card import MDCard
@ -294,7 +293,7 @@ with open(
Builder.load_string(kv_file.read()) Builder.load_string(kv_file.read())
class BaseSnackbar(MDCard, FakeRectangularElevationBehavior): class BaseSnackbar(MDCard):
""" """
:Events: :Events:
:attr:`on_open` :attr:`on_open`

View File

@ -38,7 +38,7 @@ Example
MDTopAppBar: MDTopAppBar:
id: toolbar id: toolbar
title: "MDSwiper" title: "MDSwiper"
elevation: 10 elevation: 4
pos_hint: {"top": 1} pos_hint: {"top": 1}
MDSwiper: MDSwiper:
@ -142,7 +142,7 @@ Example
MDTopAppBar: MDTopAppBar:
id: toolbar id: toolbar
title: "MDSwiper" title: "MDSwiper"
elevation: 10 elevation: 4
pos_hint: {"top": 1} pos_hint: {"top": 1}
MDSwiper: MDSwiper:
@ -203,7 +203,6 @@ from kivy.animation import Animation
from kivy.clock import Clock from kivy.clock import Clock
from kivy.core.window import Window from kivy.core.window import Window
from kivy.effects.dampedscroll import DampedScrollEffect from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.event import EventDispatcher
from kivy.lang.builder import Builder from kivy.lang.builder import Builder
from kivy.properties import ( from kivy.properties import (
BooleanProperty, BooleanProperty,
@ -212,7 +211,6 @@ from kivy.properties import (
StringProperty, StringProperty,
) )
from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.utils import platform from kivy.utils import platform
from kivymd import uix_path from kivymd import uix_path
@ -294,7 +292,7 @@ class MDSwiperItem(MDBoxLayout):
anim.start(self) anim.start(self)
class MDSwiper(MDScrollView, EventDispatcher): class MDSwiper(MDScrollView):
items_spacing = NumericProperty("20dp") items_spacing = NumericProperty("20dp")
""" """
The space between each :class:`MDSwiperItem`. The space between each :class:`MDSwiperItem`.

View File

@ -79,6 +79,10 @@
layout: layout layout: layout
size_hint: 1, None size_hint: 1, None
elevation: root.elevation elevation: root.elevation
radius: root.radius
shadow_offset: root.shadow_offset
shadow_color: root.shadow_color
shadow_softness: root.shadow_softness
height: root.tab_bar_height height: root.tab_bar_height
md_bg_color: md_bg_color:
self.theme_cls.primary_color \ self.theme_cls.primary_color \

View File

@ -944,7 +944,6 @@ from kivy.properties import (
from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ToggleButtonBehavior from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.scrollview import ScrollView from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.utils import boundary from kivy.utils import boundary
from kivymd import uix_path from kivymd import uix_path
@ -953,11 +952,11 @@ from kivymd.icon_definitions import md_icons
from kivymd.theming import ThemableBehavior, ThemeManager from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import (
DeclarativeBehavior, DeclarativeBehavior,
FakeRectangularElevationBehavior,
RectangularRippleBehavior, RectangularRippleBehavior,
SpecificBackgroundColorBehavior, SpecificBackgroundColorBehavior,
) )
from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.carousel import MDCarousel from kivymd.uix.carousel import MDCarousel
from kivymd.uix.label import MDLabel from kivymd.uix.label import MDLabel
@ -1024,7 +1023,7 @@ class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel):
Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0) Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0)
class MDTabsBase(Widget): class MDTabsBase:
""" """
This class allow you to create a tab. This class allow you to create a tab.
You must create a new class that inherits from MDTabsBase. You must create a new class that inherits from MDTabsBase.
@ -1130,9 +1129,9 @@ class MDTabsBase(Widget):
This property will affect the Tab's Title Label widget. This property will affect the Tab's Title Label widget.
""" """
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
self.tab_label = MDTabsLabel(tab=self) self.tab_label = MDTabsLabel(tab=self)
super().__init__(**kwargs) super().__init__(*args, **kwargs)
self.bind( self.bind(
icon=self._update_text, icon=self._update_text,
title=self._update_text, title=self._update_text,
@ -1273,9 +1272,7 @@ class MDTabsScrollView(ScrollView):
_update(self.effect_y, scroll_y) _update(self.effect_y, scroll_y)
class MDTabsBar( class MDTabsBar(MDCard):
ThemableBehavior, FakeRectangularElevationBehavior, MDBoxLayout
):
""" """
This class is just a boxlayout that contains the scroll view for tabs. This class is just a boxlayout that contains the scroll view for tabs.
It is also responsible for resizing the tab shortcut when necessary. It is also responsible for resizing the tab shortcut when necessary.
@ -1551,13 +1548,43 @@ class MDTabs(
and defaults to `None`. and defaults to `None`.
""" """
shadow_softness = NumericProperty(12)
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_softness`
attribute.
.. versionadded:: 1.1.0
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
and defaults to `12`.
"""
shadow_color = ColorProperty([0, 0, 0, 0.6])
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_color`
attribute.
.. versionadded:: 1.1.0
:attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.6]`.
"""
shadow_offset = ListProperty((0, 0))
"""
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_offset`
attribute.
.. versionadded:: 1.1.0
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
and defaults to `[0, 0]`.
"""
elevation = NumericProperty(0) elevation = NumericProperty(0)
""" """
Tab value elevation. See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation`
attribute.
.. seealso::
`Behaviors/Elevation <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/index.html>`_
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty` :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`. and defaults to `0`.

View File

@ -487,6 +487,8 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
_outer_radius = NumericProperty(0) _outer_radius = NumericProperty(0)
_target_radius = NumericProperty(0) _target_radius = NumericProperty(0)
__elevation = 0
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.ripple_max_dist = dp(90) self.ripple_max_dist = dp(90)
self.on_outer_radius(self, self.outer_radius) self.on_outer_radius(self, self.outer_radius)
@ -514,6 +516,89 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
if not self.outer_circle_color: if not self.outer_circle_color:
self.outer_circle_color = self.theme_cls.primary_color[:-1] self.outer_circle_color = self.theme_cls.primary_color[:-1]
def start(self, *args):
"""Starts widget opening animation."""
self._initialize()
self._animate_outer()
self.state = "open"
self.core_title_text.opacity = 1
self.core_description_text.opacity = 1
self.dispatch("on_open")
elevation = getattr(self.widget, "elevation", None)
if elevation:
self.__elevation = elevation
self.widget.elevation = 0
def stop(self, *args):
"""Starts widget close animation."""
# It needs a better implementation.
if self.anim_ripple is not None:
self.anim_ripple.unbind(on_complete=self._repeat_ripple)
self.core_title_text.opacity = 0
self.core_description_text.opacity = 0
anim = Animation(
d=0.15,
t="in_cubic",
**dict(
zip(
["_outer_radius", "_target_radius", "target_ripple_radius"],
[0, 0, 0],
)
),
)
anim.bind(on_complete=self._after_stop)
anim.start(self.widget)
def on_open(self, *args):
"""Called at the time of the start of the widget opening animation."""
def on_close(self, *args):
"""Called at the time of the start of the widget closed animation."""
def on_draw_shadow(self, instance, value):
Logger.warning(
"The shadow adding method will be implemented in future versions"
)
def on_description_text(self, instance, value):
self.core_description_text.text = value
def on_description_text_size(self, instance, value):
self.core_description_text.font_size = value
def on_description_text_bold(self, instance, value):
self.core_description_text.bold = value
def on_title_text(self, instance, value):
self.core_title_text.text = value
def on_title_text_size(self, instance, value):
self.core_title_text.font_size = value
def on_title_text_bold(self, instance, value):
self.core_title_text.bold = value
def on_outer_radius(self, instance, value):
self._outer_radius = self.outer_radius * 2
def on_target_radius(self, instance, value):
self._target_radius = self.target_radius * 2
def on_target_touch(self):
if self.stop_on_target_touch:
self.stop()
def on_outer_touch(self):
if self.stop_on_outer_touch:
self.stop()
def on_outside_click(self):
if self.cancelable:
self.stop()
def _initialize(self): def _initialize(self):
setattr(self.widget, "_outer_radius", 0) setattr(self.widget, "_outer_radius", 0)
setattr(self.widget, "_target_radius", 0) setattr(self.widget, "_target_radius", 0)
@ -527,7 +612,7 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
def _draw_canvas(self): def _draw_canvas(self):
_pos = self._ttv_pos() _pos = self._ttv_pos()
self.widget.canvas.before.clear() self.widget.canvas.before.remove_group("ttv_group")
with self.widget.canvas.before: with self.widget.canvas.before:
# Outer circle. # Outer circle.
@ -588,34 +673,14 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
group="ttv_group", group="ttv_group",
) )
def stop(self, *args):
"""Starts widget close animation."""
# It needs a better implementation.
if self.anim_ripple is not None:
self.anim_ripple.unbind(on_complete=self._repeat_ripple)
self.core_title_text.opacity = 0
self.core_description_text.opacity = 0
anim = Animation(
d=0.15,
t="in_cubic",
**dict(
zip(
["_outer_radius", "_target_radius", "target_ripple_radius"],
[0, 0, 0],
)
),
)
anim.bind(on_complete=self._after_stop)
anim.start(self.widget)
def _after_stop(self, *args): def _after_stop(self, *args):
self.widget.canvas.before.remove_group("ttv_group") self.widget.canvas.before.remove_group("ttv_group")
args[0].stop_all(self.widget) args[0].stop_all(self.widget)
elev = getattr(self.widget, "elevation", None)
if elev: elevation = getattr(self.widget, "elevation", None)
self._fix_elev() if elevation:
self.widget.elevation = self.__elevation
self.dispatch("on_close") self.dispatch("on_close")
# Don't forget to unbind the function or it'll mess # Don't forget to unbind the function or it'll mess
@ -639,16 +704,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
) )
Color(a=1) Color(a=1)
def start(self, *args):
"""Starts widget opening animation."""
self._initialize()
self._animate_outer()
self.state = "open"
self.core_title_text.opacity = 1
self.core_description_text.opacity = 1
self.dispatch("on_open")
def _animate_outer(self): def _animate_outer(self):
anim = Animation( anim = Animation(
d=0.2, d=0.2,
@ -684,53 +739,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
setattr(self.widget, "target_ripple_alpha", 1) setattr(self.widget, "target_ripple_alpha", 1)
self._animate_ripple() self._animate_ripple()
def on_open(self, *args):
"""Called at the time of the start of the widget opening animation."""
def on_close(self, *args):
"""Called at the time of the start of the widget closed animation."""
def on_draw_shadow(self, instance, value):
Logger.warning(
"The shadow adding method will be implemented in future versions"
)
def on_description_text(self, instance, value):
self.core_description_text.text = value
def on_description_text_size(self, instance, value):
self.core_description_text.font_size = value
def on_description_text_bold(self, instance, value):
self.core_description_text.bold = value
def on_title_text(self, instance, value):
self.core_title_text.text = value
def on_title_text_size(self, instance, value):
self.core_title_text.font_size = value
def on_title_text_bold(self, instance, value):
self.core_title_text.bold = value
def on_outer_radius(self, instance, value):
self._outer_radius = self.outer_radius * 2
def on_target_radius(self, instance, value):
self._target_radius = self.target_radius * 2
def on_target_touch(self):
if self.stop_on_target_touch:
self.stop()
def on_outer_touch(self):
if self.stop_on_outer_touch:
self.stop()
def on_outside_click(self):
if self.cancelable:
self.stop()
def _some_func(self, wid, touch): def _some_func(self, wid, touch):
""" """
This function decides which one to dispatch based on the touch This function decides which one to dispatch based on the touch

View File

@ -1,9 +0,0 @@
<RotateWidget>
canvas.before:
PushMatrix
Rotate:
angle: self.rotate_value_angle
axis: tuple(self.rotate_value_axis)
origin: self.center
canvas.after:
PopMatrix

View File

@ -2,127 +2,31 @@
Templates/RotateWidget Templates/RotateWidget
====================== ======================
.. versionadded:: 1.0.0 .. deprecated:: 1.0.0
Base class for controlling the rotate of the widget. .. note:: `RotateWidget` class has been deprecated. Please use
`RotateBahavior <https://kivymd.readthedocs.io/en/latest/behaviors/rotate/>`_
.. note:: See `kivy.graphics.Rotate class instead.
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Rotate>`_
for more information.
Kivy
----
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.app import App
from kivy.properties import NumericProperty
from kivy.uix.button import Button
KV = '''
Screen:
RotateButton:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_rotate(self)
canvas.before:
PushMatrix
Rotate:
angle: self.rotate_value_angle
axis: 0, 0, 1
origin: self.center
canvas.after:
PopMatrix
'''
class RotateButton(Button):
rotate_value_angle = NumericProperty(0)
class Test(App):
def build(self):
return Builder.load_string(KV)
def change_rotate(self, instance_button: Button) -> None:
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
Test().run()
KivyMD
------
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.templates import RotateWidget
KV = '''
MDScreen:
RotateButton:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_rotate(self)
elevation:0
'''
class RotateButton(MDRaisedButton, RotateWidget):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def change_rotate(self, instance_button: MDRaisedButton) -> None:
Animation(rotate_value_angle=45, d=0.3).start(instance_button)
Test().run()
""" """
__all__ = ("RotateWidget",) __all__ = ("RotateWidget",)
import os from kivy import Logger
from kivy.lang import Builder from kivymd.uix.behaviors import RotateBehavior
from kivy.properties import ListProperty, NumericProperty
from kivymd import uix_path
with open(
os.path.join(uix_path, "templates", "rotatewidget", "rotatewidget.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class RotateWidget: class RotateWidget(RotateBehavior):
"""Base class for controlling the rotate of the widget."""
rotate_value_angle = NumericProperty(0)
""" """
Property for getting/setting the angle of the rotation. .. deprecated:: 1.1.0
Use :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior`
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty` class instead.
and defaults to `0`.
""" """
rotate_value_axis = ListProperty((0, 0, 1)) def __init__(self, **kwargs):
""" super().__init__(**kwargs)
Property for getting/setting the axis of the rotation. Logger.warning(
"KivyMD: "
:attr:`rotate_value_axis` is an :class:`~kivy.properties.NumericProperty` "The `RotateWidget` class has been deprecated. "
and defaults to `(0, 0, 1)`. "Use the `RotateBehavior` class instead."
""" )

View File

@ -1,10 +0,0 @@
<ScaleWidget>
canvas.before:
PushMatrix
Scale:
x: self.scale_value_x
y: self.scale_value_y
z: self.scale_value_x
origin: self.center
canvas.after:
PopMatrix

View File

@ -2,149 +2,33 @@
Templates/ScaleWidget Templates/ScaleWidget
===================== =====================
.. versionadded:: 1.0.0 .. deprecated:: 1.1.0
Base class for controlling the scale of the widget. Base class for controlling the scale of the widget.
.. note:: See `kivy.graphics.Scale .. note:: `ScaleWidget` class has been deprecated. Please use
<https://kivy.org/doc/stable/api-kivy.graphics.html#kivy.graphics.Scale>`_ `ScaleBehavior <https://kivymd.readthedocs.io/en/latest/behaviors/scale/>`_
for more information. class instead.
Kivy
----
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.button import Button
from kivy.app import App
KV = '''
Screen:
ScaleButton:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_scale(self)
canvas.before:
PushMatrix
Scale:
x: self.scale_value_x
y: self.scale_value_y
z: self.scale_value_x
origin: self.center
canvas.after:
PopMatrix
'''
class ScaleButton(Button):
scale_value_x = NumericProperty(1)
scale_value_y = NumericProperty(1)
scale_value_z = NumericProperty(1)
class Test(App):
def build(self):
return Builder.load_string(KV)
def change_scale(self, instance_button: Button) -> None:
Animation(
scale_value_x=0.5,
scale_value_y=0.5,
scale_value_z=0.5,
d=0.3,
).start(instance_button)
Test().run()
KivyMD
------
.. code-block:: python
from kivy.animation import Animation
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.templates import ScaleWidget
KV = '''
MDScreen:
ScaleButton:
size_hint: .5, .5
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.change_scale(self)
elevation:0
'''
class ScaleButton(MDRaisedButton, ScaleWidget):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def change_scale(self, instance_button: MDRaisedButton) -> None:
Animation(
scale_value_x=0.5,
scale_value_y=0.5,
scale_value_z=0.5,
d=0.3,
).start(instance_button)
Test().run()
""" """
__all__ = ("ScaleWidget",) __all__ = ("ScaleWidget",)
import os from kivy import Logger
from kivy.lang import Builder from kivymd.uix.behaviors import ScaleBehavior
from kivy.properties import NumericProperty
from kivymd import uix_path
with open(
os.path.join(uix_path, "templates", "scalewidget", "scalewidget.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class ScaleWidget: class ScaleWidget(ScaleBehavior):
"""Base class for controlling the scale of the widget."""
scale_value_x = NumericProperty(1)
""" """
X-axis value. .. deprecated:: 1.1.0
Use :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior`
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty` class instead.
and defaults to `1`.
""" """
scale_value_y = NumericProperty(1) def __init__(self, **kwargs):
""" super().__init__(**kwargs)
Y-axis value. Logger.warning(
"KivyMD: "
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty` "The `ScaleWidget` class has been deprecated. "
and defaults to `1`. "Use the `ScaleBehavior` class instead."
""" )
scale_value_z = NumericProperty(1)
"""
Z-axis value.
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""

View File

@ -1,19 +0,0 @@
<StencilWidget>
canvas.before:
StencilPush
RoundedRectangle:
pos: root.pos
size: root.size
# FIXME: Sometimes the radius has the value [], which get a
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
radius: root.radius if root.radius else [0, 0, 0, 0]
StencilUse
canvas.after:
StencilUnUse
RoundedRectangle:
pos: root.pos
size: root.size
# FIXME: Sometimes the radius has the value [], which get a
# `GraphicException: Invalid radius value, must be list of tuples/numerics` error
radius: root.radius if root.radius else [0, 0, 0, 0]
StencilPop

View File

@ -2,115 +2,33 @@
Templates/StencilWidget Templates/StencilWidget
======================= =======================
.. versionadded:: 1.0.0 .. deprecated:: 1.1.0
Base class for controlling the stencil instructions of the widget. Base class for controlling the stencil instructions of the widget.
.. note:: See `Stencil instructions .. note:: `StencilWidget` class has been deprecated. Please use
<https://kivy.org/doc/stable/api-kivy.graphics.stencil_instructions.html>`_ `StencilBehavior <https://kivymd.readthedocs.io/en/latest/behaviors/stencil/>`_
for more information. class instead.
Kivy
----
.. code-block:: python
from kivy.lang import Builder
from kivy.app import App
KV = '''
Carousel:
Button:
size_hint: .9, .8
pos_hint: {"center_x": .5, "center_y": .5}
canvas.before:
StencilPush
RoundedRectangle:
pos: root.pos
size: root.size
StencilUse
canvas.after:
StencilUnUse
RoundedRectangle:
pos: root.pos
size: root.size
StencilPop
'''
class Test(App):
def build(self):
return Builder.load_string(KV)
Test().run()
KivyMD
------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.templates import StencilWidget
from kivymd.uix.fitimage import FitImage
KV = '''
MDCarousel:
StencilImage:
size_hint: .9, .8
pos_hint: {"center_x": .5, "center_y": .5}
source: "image.png"
'''
class StencilImage(FitImage, StencilWidget):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
""" """
__all__ = ("StencilWidget",) __all__ = ("StencilWidget",)
import os from kivy import Logger
from kivy.lang import Builder from kivymd.uix.behaviors import StencilBehavior
from kivy.properties import VariableListProperty
from kivymd import uix_path
with open(
os.path.join(uix_path, "templates", "stencilwidget", "stencilwidget.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class StencilWidget: class StencilWidget(StencilBehavior):
"""Base class for controlling the stencil instructions of the widget"""
radius = VariableListProperty([0], length=4)
""" """
Canvas radius. .. deprecated:: 1.1.0
Use :class:`~kivymd.uix.behaviors.scale_behavior.StencilBehavior`
.. versionadded:: 1.0.0 class instead.
.. code-block:: python
# Top left corner slice.
MDWidget:
radius: [25, 0, 0, 0]
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[0, 0, 0, 0]`.
""" """
def __init__(self, **kwargs):
super().__init__(**kwargs)
Logger.warning(
"KivyMD: "
"The `StencilWidget` class has been deprecated. "
"Use the `StencilBehavior` class instead."
)

View File

@ -1,4 +1,7 @@
<MDTextField> <MDTextField>
input_filter: self.field_filter
do_backspace: self.do_backspace
canvas.before: canvas.before:
Clear Clear

View File

@ -14,7 +14,6 @@ Components/TextField
`KivyMD` provides the following field classes for use: `KivyMD` provides the following field classes for use:
- MDTextField_ - MDTextField_
- MDTextFieldRound_
- MDTextFieldRect_ - MDTextFieldRect_
.. Note:: :class:`~MDTextField` inherited from .. Note:: :class:`~MDTextField` inherited from
@ -79,15 +78,15 @@ parameter to `True`:
from kivymd.app import MDApp from kivymd.app import MDApp
KV = ''' KV = '''
BoxLayout: MDScreen:
padding: "10dp"
MDTextField: MDTextField:
id: text_field_error id: text_field_error
hint_text: "Helper text on error (press 'Enter')" hint_text: "Helper text on error (press 'Enter')"
helper_text: "There will always be a mistake" helper_text: "There will always be a mistake"
helper_text_mode: "on_error" helper_text_mode: "on_error"
pos_hint: {"center_y": .5} pos_hint: {"center_x": .5, "center_y": .5}
size_hint_x: .5
''' '''
@ -97,6 +96,8 @@ parameter to `True`:
self.screen = Builder.load_string(KV) self.screen = Builder.load_string(KV)
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.screen.ids.text_field_error.bind( self.screen.ids.text_field_error.bind(
on_text_validate=self.set_error_message, on_text_validate=self.set_error_message,
on_focus=self.set_error_message, on_focus=self.set_error_message,
@ -119,6 +120,7 @@ Helper text mode `'on_error'` (with required)
MDTextField: MDTextField:
hint_text: "required = True" hint_text: "required = True"
text: "required = True"
required: True required: True
helper_text_mode: "on_error" helper_text_mode: "on_error"
helper_text: "Enter text" helper_text: "Enter text"
@ -186,7 +188,7 @@ Round mode
max_text_length: 15 max_text_length: 15
helper_text: "Massage" helper_text: "Massage"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.gif
:align: center :align: center
.. MDTextFieldRect: .. MDTextFieldRect:
@ -203,6 +205,7 @@ MDTextFieldRect
MDTextFieldRect: MDTextFieldRect:
size_hint: 1, None size_hint: 1, None
height: "30dp" height: "30dp"
background_color: app.theme_cls.bg_normal
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif
:align: center :align: center
@ -278,18 +281,17 @@ __all__ = ("MDTextField", "MDTextFieldRect")
import os import os
import re import re
from datetime import date
from typing import Union from typing import Union
from kivy.animation import Animation from kivy.animation import Animation
from kivy.clock import Clock from kivy.clock import Clock
from kivy.lang import Builder from kivy.lang import Builder
from kivy.logger import Logger
from kivy.metrics import dp, sp from kivy.metrics import dp, sp
from kivy.properties import ( from kivy.properties import (
AliasProperty, AliasProperty,
BooleanProperty, BooleanProperty,
ColorProperty, ColorProperty,
DictProperty,
ListProperty, ListProperty,
NumericProperty, NumericProperty,
ObjectProperty, ObjectProperty,
@ -311,6 +313,220 @@ with open(
Builder.load_string(kv_file.read()) Builder.load_string(kv_file.read())
# TODO: Add a class to work with the phone number mask.
class AutoFormatTelephoneNumber:
"""
Implements automatic formatting of the text entered in the text field
according to the mask, for example '+38 (###) ### ## ##'.
"""
def __init__(self):
self._backspace = False
def isnumeric(self, value):
try:
int(value)
return True
except ValueError:
return False
def do_backspace(self, *args):
if self.validator and self.validator == "phone":
self._backspace = True
text = self.text
text = text[:-1]
self.text = text
self._backspace = False
def field_filter(self, value, boolean):
if self.validator and self.validator == "phone":
if len(self.text) == 14:
return
if self.isnumeric(value):
return value
return value
def format(self, value):
if value != "" and not value.isspace() and not self._backspace:
if len(value) <= 1 and self.focus:
self.text = value
self._check_cursor()
elif len(value) == 4:
start = self.text[:-1]
end = self.text[-1]
self.text = "%s) %s" % (start, end)
self._check_cursor()
elif len(value) == 8:
self.text += "-"
self._check_cursor()
elif len(value) in [12, 16]:
start = self.text[:-1]
end = self.text[-1]
self.text = "%s-%s" % (start, end)
self._check_cursor()
def _check_cursor(self):
def set_pos_cursor(pos_corsor, interval=0.5):
self.cursor = (pos_corsor, 0)
if self.focus:
Clock.schedule_once(lambda x: set_pos_cursor(len(self.text)), 0.1)
class Validator:
"""Container class for various validation methods."""
datetime_date = ObjectProperty()
"""
The last valid date as a <class 'datetime.date'> object.
:attr:`datetime_date` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
date_interval = ListProperty([None, None])
"""
The date interval that is valid for input.
Can be entered as <class 'datetime.date'> objects or a string format.
Both values or just one value can be entered.
In string format, must follow the current date_format.
Example: Given date_format -> "mm/dd/yyyy"
Input examples -> "12/31/1900", "12/31/2100" or "12/31/1900", None.
:attr:`date_interval` is an :class:`~kivy.properties.ListProperty`
and defaults to `[None, None]`.
"""
date_format = OptionProperty(
None,
options=[
"dd/mm/yyyy",
"mm/dd/yyyy",
"yyyy/mm/dd",
],
)
"""
Format of date strings that will be entered.
Available options are: `'dd/mm/yyyy'`, `'mm/dd/yyyy'`, `'yyyy/mm/dd'`.
:attr:`date_format` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None`.
"""
def is_email_valid(self, text: str) -> bool:
if not re.match(r"[^@]+@[^@]+\.[^@]+", text):
return True
return False
def is_time_valid(self, text: str) -> bool:
if re.match(r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$", text) or re.match(
r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$", text
):
return False
return True
def is_date_valid(self, text: str) -> bool:
if not self.date_format:
raise Exception("TextInput date_format was not defined.")
# Regex strings.
dd = "[0][1-9]|[1-2][0-9]|[3][0-1]"
mm = "[0][1-9]|[1][0-2]"
yyyy = "[0-9][0-9][0-9][0-9]"
fmt = self.date_format.split("/")
largs = locals()
# Access the local variables dict in the correct format based on
# date_format split. Example: "mm/dd/yyyy" -> ["mm", "dd", "yyyy"]
# largs[fmt[0]] would be largs["mm"] so the month regex string.
if re.match(
f"^({largs[fmt[0]]})/({largs[fmt[1]]})/({largs[fmt[2]]})$", text
):
input_split = text.split("/")
largs[fmt[0]] = input_split[0]
largs[fmt[1]] = input_split[1]
largs[fmt[2]] = input_split[2]
# Organize input into correct slots and try to convert
# to datetime object. This way February exceptions are
# tested. Also tests with the date_interval are simpler
# using datetime objects.
try:
datetime = date(
int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"])
)
except ValueError:
return True
if self.date_interval:
if (
self.date_interval[0]
and not self.date_interval[0] <= datetime
or self.date_interval[1]
and not datetime <= self.date_interval[1]
):
return True
self.datetime_date = datetime
return False
return True
def on_date_interval(self, *args) -> None:
"""Default event handler for date_interval input."""
def on_date_interval():
if not self.date_format:
raise Exception("TextInput date_format was not defined.")
fmt = self.date_format.split("/")
largs = {}
# Convert string inputs into datetime.date objects and store
# them back into self.date_interval.
try:
if self.date_interval[0] and not isinstance(
self.date_interval[0], date
):
split = self.date_interval[0].split("/")
largs[fmt[0]] = split[0]
largs[fmt[1]] = split[1]
largs[fmt[2]] = split[2]
self.date_interval[0] = date(
int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"])
)
if self.date_interval[1] and not isinstance(
self.date_interval[1], date
):
split = self.date_interval[1].split("/")
largs[fmt[0]] = split[0]
largs[fmt[1]] = split[1]
largs[fmt[2]] = split[2]
self.date_interval[1] = date(
int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"])
)
except Exception:
raise Exception(
r"TextInput date_interval was defined incorrectly, it must "
r"be composed of <class 'datetime.date'> objects or strings"
r" following current date_format."
)
# Test if the interval is valid.
if isinstance(self.date_interval[0], date) and isinstance(
self.date_interval[1], date
):
if self.date_interval[0] >= self.date_interval[1]:
raise Exception(
"TextInput date_interval last date must be greater"
" than the first date or set to None."
)
Clock.schedule_once(lambda x: on_date_interval())
class MDTextFieldRect(ThemableBehavior, TextInput): class MDTextFieldRect(ThemableBehavior, TextInput):
line_anim = BooleanProperty(True) line_anim = BooleanProperty(True)
""" """
@ -383,7 +599,13 @@ class TextfieldLabel(ThemableBehavior, Label):
self.font_size = sp(self.theme_cls.font_styles[self.font_style][1]) self.font_size = sp(self.theme_cls.font_styles[self.font_style][1])
class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput): class MDTextField(
DeclarativeBehavior,
ThemableBehavior,
TextInput,
Validator,
AutoFormatTelephoneNumber,
):
helper_text = StringProperty() helper_text = StringProperty()
""" """
Text for ``helper_text`` mode. Text for ``helper_text`` mode.
@ -430,17 +652,185 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
and defaults to `'line'`. and defaults to `'line'`.
""" """
phone_mask = StringProperty("")
validator = OptionProperty(None, options=["date", "email", "time", "phone"])
"""
The type of text field for entering Email, time, etc.
Automatically sets the type of the text field as "error" if the user input
does not match any of the set validation types.
Available options are: `'date'`, `'email'`, `'time'`.
When using `'date'`, :attr:`date_format` must be defined.
.. versionadded:: 1.1.0
.. code-block:: python
MDTextField:
hint_text: "Email"
helper_text: "user@gmail.com"
validator: "email"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator.png
:align: center
.. tabs::
.. tab:: Declarative KV style
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDBoxLayout:
orientation: "vertical"
spacing: "20dp"
adaptive_height: True
size_hint_x: .8
pos_hint: {"center_x": .5, "center_y": .5}
MDTextField:
hint_text: "Date dd/mm/yyyy without limits"
helper_text: "Enter a valid dd/mm/yyyy date"
validator: "date"
date_format: "dd/mm/yyyy"
MDTextField:
hint_text: "Date mm/dd/yyyy without limits"
helper_text: "Enter a valid mm/dd/yyyy date"
validator: "date"
date_format: "mm/dd/yyyy"
MDTextField:
hint_text: "Date yyyy/mm/dd without limits"
helper_text: "Enter a valid yyyy/mm/dd date"
validator: "date"
date_format: "yyyy/mm/dd"
MDTextField:
hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval"
helper_text: "Enter a valid dd/mm/yyyy date"
validator: "date"
date_format: "dd/mm/yyyy"
date_interval: "01/01/1900", "01/01/2100"
MDTextField:
hint_text: "Date dd/mm/yyyy in [01/01/1900, None] interval"
helper_text: "Enter a valid dd/mm/yyyy date"
validator: "date"
date_format: "dd/mm/yyyy"
date_interval: "01/01/1900", None
MDTextField:
hint_text: "Date dd/mm/yyyy in [None, 01/01/2100] interval"
helper_text: "Enter a valid dd/mm/yyyy date"
validator: "date"
date_format: "dd/mm/yyyy"
date_interval: None, "01/01/2100"
'''
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
Test().run()
.. tab:: Declarative python style
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen
from kivymd.uix.textfield import MDTextField
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDBoxLayout(
MDTextField(
hint_text="Date dd/mm/yyyy without limits",
helper_text="Enter a valid dd/mm/yyyy date",
validator="date",
date_format="dd/mm/yyyy",
),
MDTextField(
hint_text="Date mm/dd/yyyy without limits",
helper_text="Enter a valid mm/dd/yyyy date",
validator="date",
date_format="mm/dd/yyyy",
),
MDTextField(
hint_text="Date yyyy/mm/dd without limits",
helper_text="Enter a valid yyyy/mm/dd date",
validator="date",
date_format="yyyy/mm/dd",
),
MDTextField(
hint_text="Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval",
helper_text="Enter a valid dd/mm/yyyy date",
validator="date",
date_format="dd/mm/yyyy",
date_interval=["01/01/1900", "01/01/2100"],
),
MDTextField(
hint_text="Date dd/mm/yyyy in [01/01/1900, None] interval",
helper_text="Enter a valid dd/mm/yyyy date",
validator="date",
date_format="dd/mm/yyyy",
date_interval=["01/01/1900", None],
),
MDTextField(
hint_text="Date dd/mm/yyyy in [None, 01/01/2100] interval",
helper_text="Enter a valid dd/mm/yyyy date",
validator="date",
date_format="dd/mm/yyyy",
date_interval=[None, "01/01/2100"],
),
orientation="vertical",
spacing="20dp",
adaptive_height=True,
size_hint_x=0.8,
pos_hint={"center_x": 0.5, "center_y": 0.5},
)
)
)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator-date.png
:align: center
:attr:`validator` is an :class:`~kivy.properties.OptionProperty`
and defaults to `None`.
"""
line_color_normal = ColorProperty([0, 0, 0, 0]) line_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Line color normal (static underline line) in ``rgba`` format. Line color normal (static underline line) in (r, g, b, a) or string format.
.. code-block:: kv .. code-block:: kv
MDTextField: MDTextField:
hint_text: "line_color_normal" hint_text: "line_color_normal"
line_color_normal: 1, 0, 1, 1 line_color_normal: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.png
:align: center :align: center
:attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty` :attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty`
@ -449,13 +839,13 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
line_color_focus = ColorProperty([0, 0, 0, 0]) line_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Line color focus (active underline line) in ``rgba`` format. Line color focus (active underline line) in (r, g, b, a) or string format.
.. code-block:: kv .. code-block:: kv
MDTextField: MDTextField:
hint_text: "line_color_focus" hint_text: "line_color_focus"
line_color_focus: 0, 1, 0, 1 line_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-focus.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-focus.gif
:align: center :align: center
@ -474,7 +864,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
error_color = ColorProperty([0, 0, 0, 0]) error_color = ColorProperty([0, 0, 0, 0])
""" """
Error color in ``rgba`` format for ``required = True``. Error color in (r, g, b, a) or string format for ``required = True``.
:attr:`error_color` is an :class:`~kivy.properties.ColorProperty` :attr:`error_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`. and defaults to `[0, 0, 0, 0]`.
@ -482,7 +872,18 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
fill_color_normal = ColorProperty([0, 0, 0, 0]) fill_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Fill background color in 'fill' mode when text field is out of focus. Fill background color in (r, g, b, a) or string format in 'fill' mode when]
text field is out of focus.
.. code=block:: kv
MDTextField:
hint_text: "Fill mode"
mode: "fill"
fill_color_normal: "brown"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-normal.png
:align: center
:attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty` :attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`. and defaults to `[0, 0, 0, 0]`.
@ -490,7 +891,18 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
fill_color_focus = ColorProperty([0, 0, 0, 0]) fill_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Fill background color in 'fill' mode when the text field has focus. Fill background color in (r, g, b, a) or string format in 'fill' mode when
the text field has focus.
.. code=block:: kv
MDTextField:
hint_text: "Fill mode"
mode: "fill"
fill_color_focus: "brown"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-focus.gif
:align: center
:attr:`fill_color_focus` is an :class:`~kivy.properties.ColorProperty` :attr:`fill_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`. and defaults to `[0, 0, 0, 0]`.
@ -514,7 +926,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
hint_text_color_normal = ColorProperty([0, 0, 0, 0]) hint_text_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Hint text color when text field is out of focus. Hint text color in (r, g, b, a) or string format when text field is out
of focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -522,9 +935,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
hint_text: "hint_text_color_normal" hint_text: "hint_text_color_normal"
hint_text_color_normal: 0, 1, 0, 1 hint_text_color_normal: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.png
:align: center :align: center
:attr:`hint_text_color_normal` is an :class:`~kivy.properties.ColorProperty` :attr:`hint_text_color_normal` is an :class:`~kivy.properties.ColorProperty`
@ -533,7 +946,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
hint_text_color_focus = ColorProperty([0, 0, 0, 0]) hint_text_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Hint text color when the text field has focus. Hint text color in (r, g, b, a) or string format when the text field has
focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -541,7 +955,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
hint_text: "hint_text_color_focus" hint_text: "hint_text_color_focus"
hint_text_color_focus: 0, 1, 0, 1 hint_text_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-focus.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-focus.gif
:align: center :align: center
@ -552,7 +966,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
helper_text_color_normal = ColorProperty([0, 0, 0, 0]) helper_text_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Helper text color when text field is out of focus. Helper text color in (r, g, b, a) or string format when text field is out
of focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -561,7 +976,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
helper_text: "helper_text_color_normal" helper_text: "helper_text_color_normal"
helper_text_mode: "persistent" helper_text_mode: "persistent"
helper_text_color_normal: 0, 1, 0, 1 helper_text_color_normal: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-normal.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-normal.png
:align: center :align: center
@ -572,7 +987,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
helper_text_color_focus = ColorProperty([0, 0, 0, 0]) helper_text_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Helper text color when the text field has focus. Helper text color in (r, g, b, a) or string format when the text field has
focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -581,7 +997,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
helper_text: "helper_text_color_focus" helper_text: "helper_text_color_focus"
helper_text_mode: "persistent" helper_text_mode: "persistent"
helper_text_color_focus: 0, 1, 0, 1 helper_text_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-focus.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-focus.gif
:align: center :align: center
@ -592,7 +1008,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
icon_right_color_normal = ColorProperty([0, 0, 0, 0]) icon_right_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Color of right icon when text field is out of focus. Color in (r, g, b, a) or string format of right icon when text field is out
of focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -601,9 +1018,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
icon_right: "language-python" icon_right: "language-python"
hint_text: "icon_right_color_normal" hint_text: "icon_right_color_normal"
icon_right_color_normal: 0, 1, 0, 1 icon_right_color_normal: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.png
:align: center :align: center
:attr:`icon_right_color_normal` is an :class:`~kivy.properties.ColorProperty` :attr:`icon_right_color_normal` is an :class:`~kivy.properties.ColorProperty`
@ -612,7 +1029,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
icon_right_color_focus = ColorProperty([0, 0, 0, 0]) icon_right_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Color of right icon when the text field has focus. Color in (r, g, b, a) or string format of right icon when the text field
has focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -621,7 +1039,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
icon_right: "language-python" icon_right: "language-python"
hint_text: "icon_right_color_focus" hint_text: "icon_right_color_focus"
icon_right_color_focus: 0, 1, 0, 1 icon_right_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif
:align: center :align: center
@ -632,47 +1050,30 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
icon_left_color_normal = ColorProperty([0, 0, 0, 0]) icon_left_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Color of right icon when text field is out of focus. Color in (r, g, b, a) or string format of right icon when text field is out
of focus.
.. versionadded:: 1.0.0 .. 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
:attr:`icon_left_color_normal` is an :class:`~kivy.properties.ColorProperty` :attr:`icon_left_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`. and defaults to `[0, 0, 0, 0]`.
""" """
icon_left_color_focus = ColorProperty([0, 0, 0, 0]) icon_left_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Color of right icon when the text field has focus. Color in (r, g, b, a) or string format of right icon when the text field
has focus.
.. versionadded:: 1.0.0 .. 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
:attr:`icon_left_color_focus` is an :class:`~kivy.properties.ColorProperty` :attr:`icon_left_color_focus` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0]`. and defaults to `[0, 0, 0, 0]`.
""" """
max_length_text_color = ColorProperty([0, 0, 0, 0]) max_length_text_color = ColorProperty([0, 0, 0, 0])
""" """
Text color of the maximum length of characters to be input. Text color in (r, g, b, a) or string format of the maximum length of
characters to be input.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -680,10 +1081,10 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
hint_text: "max_length_text_color" hint_text: "max_length_text_color"
max_length_text_color: 0, 1, 0, 1 max_length_text_color: "red"
max_text_length: 5 max_text_length: 5
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.png
:align: center :align: center
:attr:`max_length_text_color` is an :class:`~kivy.properties.ColorProperty` :attr:`max_length_text_color` is an :class:`~kivy.properties.ColorProperty`
@ -718,7 +1119,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text_color_normal = ColorProperty([0, 0, 0, 0]) text_color_normal = ColorProperty([0, 0, 0, 0])
""" """
Text color in ``rgba`` format when text field is out of focus. Text color in (r, g, b, a) or string format when text field is out of focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -726,9 +1127,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
hint_text: "text_color_normal" hint_text: "text_color_normal"
text_color_normal: 0, 1, 0, 1 text_color_normal: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.png
:align: center :align: center
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
@ -737,7 +1138,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text_color_focus = ColorProperty([0, 0, 0, 0]) text_color_focus = ColorProperty([0, 0, 0, 0])
""" """
Text color in ``rgba`` format when text field has focus. Text color in (r, g, b, a) or string format when text field has focus.
.. versionadded:: 1.0.0 .. versionadded:: 1.0.0
@ -745,7 +1146,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField: MDTextField:
hint_text: "text_color_focus" hint_text: "text_color_focus"
text_color_focus: 0, 1, 0, 1 text_color_focus: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.gif .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.gif
:align: center :align: center
@ -879,8 +1280,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text=self.set_text, text=self.set_text,
) )
self.theme_cls.bind( self.theme_cls.bind(
primary_color=lambda x, y: self.set_default_colors(0, True), primary_color=self.set_default_colors,
theme_style=lambda x, y: self.set_default_colors(0, True), theme_style=self.set_default_colors,
) )
Clock.schedule_once(self.check_text) Clock.schedule_once(self.check_text)
@ -930,9 +1331,17 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
) )
if self.error_color == [0, 0, 0, 0] or updated: if self.error_color == [0, 0, 0, 0] or updated:
self.error_color = self.theme_cls.error_color self.error_color = (
self.theme_cls.error_color
if self.error_color == [0, 0, 0, 0]
else self.error_color
)
if self.max_length_text_color == [0, 0, 0, 0] or updated: if self.max_length_text_color == [0, 0, 0, 0] or updated:
self.max_length_text_color = self.theme_cls.disabled_hint_text_color self.max_length_text_color = (
self.theme_cls.disabled_hint_text_color
if self.max_length_text_color == [0, 0, 0, 0]
else self.max_length_text_color
)
self._hint_text_color = self.hint_text_color_normal self._hint_text_color = self.hint_text_color_normal
self._text_color_normal = self.text_color_normal self._text_color_normal = self.text_color_normal
@ -1101,8 +1510,11 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
self.text = re.sub("\n", " ", text) if not self.multiline else text self.text = re.sub("\n", " ", text) if not self.multiline else text
self.set_max_text_length() self.set_max_text_length()
if self.validator and self.validator == "phone":
pass
# self.format(self.text)
if self.text and self.max_length_text_color and self._get_has_error(): if (self.text and self.max_length_text_color) or self._get_has_error():
self.error = True self.error = True
if ( if (
self.text self.text
@ -1301,22 +1713,34 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
if value_height >= self.max_height and self.max_height: if value_height >= self.max_height and self.max_height:
self.height = self.max_height self.height = self.max_height
def on_text_color_normal(self, instance_text_field, color: list): def on_text_color_normal(
self, instance_text_field, color: Union[list, str]
):
self._text_color_normal = color self._text_color_normal = color
def on_hint_text_color_normal(self, instance_text_field, color: list): def on_hint_text_color_normal(
self, instance_text_field, color: Union[list, str]
):
self._hint_text_color = color self._hint_text_color = color
def on_helper_text_color_normal(self, instance_text_field, color: list): def on_helper_text_color_normal(
self, instance_text_field, color: Union[list, str]
):
self._helper_text_color = color self._helper_text_color = color
def on_icon_right_color_normal(self, instance_text_field, color: list): def on_icon_right_color_normal(
self, instance_text_field, color: Union[list, str]
):
self._icon_right_color = color self._icon_right_color = color
def on_line_color_normal(self, instance_text_field, color: list): def on_line_color_normal(
self, instance_text_field, color: Union[list, str]
):
self._line_color_normal = color self._line_color_normal = color
def on_max_length_text_color(self, instance_text_field, color: list): def on_max_length_text_color(
self, instance_text_field, color: Union[list, str]
):
self._max_length_text_color = color self._max_length_text_color = color
def _set_color(self, attr_name: str, color: str, updated: bool) -> None: def _set_color(self, attr_name: str, color: str, updated: bool) -> None:
@ -1353,6 +1777,13 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
the :attr:`~MDTextField.required` parameter is set to `True`. the :attr:`~MDTextField.required` parameter is set to `True`.
""" """
if self.validator and self.validator != "phone":
has_error = {
"date": self.is_date_valid,
"email": self.is_email_valid,
"time": self.is_time_valid,
}[self.validator](self.text)
return has_error
if self.max_text_length and len(self.text) > self.max_text_length: if self.max_text_length and len(self.text) > self.max_text_length:
has_error = True has_error = True
else: else:
@ -1367,9 +1798,12 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
if __name__ == "__main__": if __name__ == "__main__":
from kivy.core.window import Window
from kivy.lang import Builder from kivy.lang import Builder
from kivy.uix.textinput import TextInput from kivy.uix.textinput import TextInput
Window.size = (800, 750)
from kivymd.app import MDApp from kivymd.app import MDApp
KV = """ KV = """
@ -1385,41 +1819,53 @@ MDScreen:
MDTextField: MDTextField:
hint_text: "Label" hint_text: "Label"
helper_text: "Error massage" helper_text: "Error message"
mode: "rectangle" mode: "rectangle"
max_text_length: 5 max_text_length: 5
MDTextField: MDTextField:
icon_left: "git" icon_left: "git"
hint_text: "Label" hint_text: "Label"
helper_text: "Error massage" helper_text: "Error message"
mode: "rectangle" mode: "rectangle"
MDTextField: MDTextField:
icon_left: "git" icon_left: "git"
hint_text: "Label" hint_text: "Label"
helper_text: "Error massage" helper_text: "Error message"
mode: "fill" mode: "fill"
MDTextField: MDTextField:
hint_text: "Label" hint_text: "Label"
helper_text: "Error massage" helper_text: "Error message"
mode: "fill" mode: "fill"
MDTextField: MDTextField:
hint_text: "Label" hint_text: "Label"
helper_text: "Error massage" helper_text: "Error message"
MDTextField: MDTextField:
icon_left: "git" icon_left: "git"
hint_text: "Label" hint_text: "Label"
helper_text: "Error massage" helper_text: "Error message"
MDTextField: MDTextField:
hint_text: "Round mode" hint_text: "Round mode"
mode: "round" mode: "round"
max_text_length: 15 max_text_length: 15
helper_text: "Massage" helper_text: "Message"
MDTextField:
hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval"
helper_text: "Enter a valid dd/mm/yyyy date"
validator: "date"
date_format: "dd/mm/yyyy"
date_interval: "01/01/1900", "01/01/2100"
MDTextField:
hint_text: "Email"
helper_text: "user@gmail.com"
validator: "email"
MDFlatButton: MDFlatButton:
text: "SET TEXT" text: "SET TEXT"
@ -1429,6 +1875,8 @@ MDScreen:
class Test(MDApp): class Test(MDApp):
def build(self): def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV) return Builder.load_string(KV)
def set_text(self): def set_text(self):

View File

@ -121,8 +121,8 @@ Shadow elevation control
.. code-block:: kv .. code-block:: kv
MDTopAppBar: MDTopAppBar:
title: "Elevation 10" title: "Elevation 4"
elevation: 10 elevation: 4
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png
:align: center :align: center
@ -327,7 +327,7 @@ Material design 3 style
:align: center :align: center
""" """
__all__ = ("MDTopAppBar", "MDBottomAppBar") __all__ = ("MDTopAppBar", "MDBottomAppBar", "ActionTopAppBarButton")
import os import os
from math import cos, radians, sin from math import cos, radians, sin
@ -337,10 +337,8 @@ from kivy.animation import Animation
from kivy.clock import Clock from kivy.clock import Clock
from kivy.core.window import Window from kivy.core.window import Window
from kivy.lang import Builder from kivy.lang import Builder
from kivy.logger import Logger
from kivy.metrics import dp from kivy.metrics import dp
from kivy.properties import ( from kivy.properties import (
AliasProperty,
BooleanProperty, BooleanProperty,
ColorProperty, ColorProperty,
ListProperty, ListProperty,
@ -356,15 +354,15 @@ from kivymd import uix_path
from kivymd.color_definitions import text_colors from kivymd.color_definitions import text_colors
from kivymd.theming import ThemableBehavior from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import ( from kivymd.uix.behaviors import (
CommonElevationBehavior,
DeclarativeBehavior, DeclarativeBehavior,
FakeRectangularElevationBehavior, ScaleBehavior,
SpecificBackgroundColorBehavior, SpecificBackgroundColorBehavior,
) )
from kivymd.uix.button import MDFloatingActionButton, MDIconButton from kivymd.uix.button import MDFloatingActionButton, MDIconButton
from kivymd.uix.controllers import WindowController from kivymd.uix.controllers import WindowController
from kivymd.uix.list import OneLineIconListItem from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.menu import MDDropdownMenu from kivymd.uix.menu import MDDropdownMenu
from kivymd.uix.templates import ScaleWidget
from kivymd.uix.tooltip import MDTooltip from kivymd.uix.tooltip import MDTooltip
from kivymd.utils.set_bars_colors import set_bars_colors from kivymd.utils.set_bars_colors import set_bars_colors
@ -374,7 +372,7 @@ with open(
Builder.load_string(kv_file.read()) Builder.load_string(kv_file.read())
class ActionBottomAppBarButton(MDFloatingActionButton, ScaleWidget): class ActionBottomAppBarButton(MDFloatingActionButton, ScaleBehavior):
""" """
Implements a floating action button (FAB) for a toolbar with type 'bottom'. Implements a floating action button (FAB) for a toolbar with type 'bottom'.
""" """
@ -409,11 +407,11 @@ class OverFlowMenuItem(OneLineIconListItem):
class NotchedBox( class NotchedBox(
ThemableBehavior, ThemableBehavior,
FakeRectangularElevationBehavior, CommonElevationBehavior,
SpecificBackgroundColorBehavior, SpecificBackgroundColorBehavior,
BoxLayout, BoxLayout,
): ):
elevation = NumericProperty(6) elevation = NumericProperty(4)
notch_radius = NumericProperty() notch_radius = NumericProperty()
notch_center_x = NumericProperty("100dp") notch_center_x = NumericProperty("100dp")
@ -961,8 +959,10 @@ class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController):
self.icon_color = self.theme_cls.primary_color self.icon_color = self.theme_cls.primary_color
self.bind(specific_text_color=self.update_action_bar_text_colors) self.bind(specific_text_color=self.update_action_bar_text_colors)
self.theme_cls.bind(material_style=self.update_bar_height) self.theme_cls.bind(
self.theme_cls.bind(primary_palette=self.update_md_bg_color) material_style=self.update_bar_height,
primary_palette=self.update_md_bg_color,
)
Clock.schedule_once( Clock.schedule_once(
lambda x: self.on_left_action_items(0, self.left_action_items) lambda x: self.on_left_action_items(0, self.left_action_items)
@ -1103,6 +1103,7 @@ class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController):
+ self.theme_cls.standard_increment / 2 + self.theme_cls.standard_increment / 2
+ self._shift + self._shift
) )
self.shadow_offset = [0, 30]
self.on_mode(None, self.mode) self.on_mode(None, self.mode)
def on_type_height(self, instance_toolbar, height_type_value: str) -> None: def on_type_height(self, instance_toolbar, height_type_value: str) -> None:

View File

@ -314,7 +314,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior):
Clock.schedule_once(self.animation_tooltip_dismiss) Clock.schedule_once(self.animation_tooltip_dismiss)
def on_show(self) -> None: def on_show(self) -> None:
"""Default dismiss event handler.""" """Default display event handler."""
def on_dismiss(self) -> None: def on_dismiss(self) -> None:
""" """

View File

@ -35,7 +35,9 @@ __all__ = (
"MDTransitionBase", "MDTransitionBase",
) )
from kivy import Logger
from kivy.animation import Animation, AnimationTransition from kivy.animation import Animation, AnimationTransition
from kivy.properties import DictProperty
from kivy.uix.screenmanager import ( from kivy.uix.screenmanager import (
ScreenManagerException, ScreenManagerException,
SlideTransition, SlideTransition,
@ -43,13 +45,41 @@ from kivy.uix.screenmanager import (
TransitionBase, TransitionBase,
) )
from kivymd.uix.hero import MDHeroFrom, MDHeroTo
from kivymd.uix.screenmanager import MDScreenManager from kivymd.uix.screenmanager import MDScreenManager
class MDTransitionBase(TransitionBase): class MDTransitionBase(TransitionBase):
"""
TransitionBase is used to animate 2 screens within the
:class:`~kivymd.uix.screenmanager.MDScreenManager`.
For more
information, see in the :class:`~kivy.uix.screenmanager.TransitionBase`
class documentation.
"""
_direction = "in" _direction = "in"
hero_widget = None # Collection of child widgets of all 'MDHeroFrom' widgets that are
hero_from_widget = None # kivymd.uix.hero.MDHeroFrom object # on the screen, for example:
#
# MDScreen:
#
# MDHeroFrom:
# tag: "kivymd"
#
# FitImage:
#
# MDHeroFrom:
# tag: "kivy"
#
# FitImage:
#
# {
# 'kivy': <kivymd.uix.fitimage.fitimage.FitImage object>,
# 'kivymd': <kivymd.uix.fitimage.fitimage.FitImage object>,
# }
_hero_from_widget_children = DictProperty()
def start(self, instance_screen_manager: MDScreenManager) -> None: def start(self, instance_screen_manager: MDScreenManager) -> None:
super().start(instance_screen_manager) super().start(instance_screen_manager)
@ -59,67 +89,160 @@ class MDTransitionBase(TransitionBase):
]() ]()
def animated_hero_in(self) -> None: def animated_hero_in(self) -> None:
if self.manager._heroes_data and self.manager.current_hero: """Animates the flight of heroes from screen **A** to screen **B**."""
self.hero_from_widget = self.manager.get_hero_from_widget()
self._check_widget_properties()
self.hero_widget = self.hero_from_widget.children[0]
self.hero_from_widget.remove_widget(self.hero_widget)
self.hero_widget.pos = self.screen_out.to_widget( if self.manager._heroes_data and self.manager.current_heroes:
*self.hero_from_widget.to_window(*self.hero_from_widget.pos) for hero_from_widget in self.manager.get_hero_from_widget():
) for heroes_tag in self.manager.current_heroes:
self.hero_widget.size = self.hero_from_widget.size if heroes_tag == hero_from_widget.tag:
self.manager.get_root_window().add_widget(self.hero_widget) self._check_widget_properties(hero_from_widget)
Animation( # Get child widget of the 'MDHeroFrom' container.
size=self.screen_in.hero_to.size, hero_widget = hero_from_widget.children[0]
d=self.duration, self._hero_from_widget_children[
pos=self.screen_in.hero_to.pos, hero_from_widget.tag
).start(self.hero_widget) ] = hero_widget
self.hero_from_widget.dispatch(
"on_transform_in", self.hero_widget, self.duration # Removing the child widget from the 'MDHeroFrom'
) # container.
hero_from_widget.remove_widget(hero_widget)
# We set the size, position of the child widget of the
# 'MDHeroFrom' container and add this widget to the
# root window.
hero_widget.pos = self.screen_out.to_widget(
*hero_from_widget.to_window(*hero_from_widget.pos)
)
hero_widget.size = hero_from_widget.size
self.manager.get_root_window().add_widget(hero_widget)
# Animating widgets added to the root window.
if self.screen_in.heroes_to:
for hero_to_widget in self.screen_in.heroes_to:
self._check_hero_to_widget_tag(
hero_to_widget, hero_from_widget
)
if hero_to_widget.tag == heroes_tag:
Animation(
size=hero_to_widget.size,
d=self.duration,
pos=hero_to_widget.pos,
).start(hero_widget)
hero_from_widget.dispatch(
"on_transform_in",
hero_widget,
self.duration,
)
def animated_hero_out(self) -> None: def animated_hero_out(self) -> None:
if self.manager._heroes_data and self.manager.current_hero: """Animates the flight of heroes from screen **B** to screen **A**."""
self.screen_out.hero_to.remove_widget(self.hero_widget)
self.manager.get_root_window().add_widget(self.hero_widget)
self.hero_from_widget.dispatch( if (
"on_transform_out", self.hero_widget, self.duration self.manager._heroes_data
) and self.manager.current_heroes
Animation( and self.screen_out.heroes_to
pos=self.screen_in.to_widget( ):
*self.hero_from_widget.to_window(*self.hero_from_widget.pos)
), for heroes_tag in self.manager.current_heroes:
size=self.hero_from_widget.size, for hero_to_widget in self.screen_out.heroes_to:
d=self.duration, if hero_to_widget.tag == heroes_tag:
).start(self.hero_widget) hero_from_children = self._hero_from_widget_children[
heroes_tag
]
hero_to_widget.remove_widget(hero_from_children)
self.manager.get_root_window().add_widget(
hero_from_children
)
for (
hero_from_widget
) in self.manager.get_hero_from_widget():
hero_from_widget.dispatch(
"on_transform_out",
self._hero_from_widget_children[
hero_from_widget.tag
],
self.duration,
)
Animation(
pos=self.screen_in.to_widget(
*hero_from_widget.to_window(
*hero_from_widget.pos
)
),
size=hero_from_widget.size,
d=self.duration,
).start(
self._hero_from_widget_children[
hero_from_widget.tag
]
)
def on_complete(self) -> None: def on_complete(self) -> None:
"""
Override method.
See :attr:`kivy.uix.screenmanager.TransitionBase.on_complete'.
"""
super().on_complete() super().on_complete()
if self.manager._heroes_data and self.manager.current_heroes:
for hero_from_widget in self.manager.get_hero_from_widget():
for heroes_tag in self.manager.current_heroes:
if heroes_tag == hero_from_widget.tag:
hero_from_children = self._hero_from_widget_children[
heroes_tag
]
self.manager.get_root_window().remove_widget(
hero_from_children
)
# Adding a child widget from the 'MDHeraFrom' container
# to the 'MDHeroTo' container.
if self._direction == "in":
for hero_to_widget in self.screen_in.heroes_to:
if hero_to_widget.tag == heroes_tag:
hero_to_widget.add_widget(
hero_from_children
)
# Restores the child widget for the 'MDHeraFrom'
# container.
elif self._direction == "out":
hero_from_widget.add_widget(hero_from_children)
if self._direction == "out": if self._direction == "out":
self._direction = "in" self._direction = "in"
if self.manager._heroes_data and self.manager.current_hero:
self.manager.get_root_window().remove_widget(self.hero_widget)
self.hero_from_widget.add_widget(self.hero_widget)
else: else:
self._direction = "out" self._direction = "out"
if self.manager._heroes_data and self.manager.current_hero:
self.manager.get_root_window().remove_widget(self.hero_widget)
self.screen_in.hero_to.add_widget(self.hero_widget)
def _check_widget_properties(self): # Checks the attributes for the 'self.screen_in' screen.
if not self.screen_in.hero_to: # Called from the animated_hero_in method.
def _check_widget_properties(self, hero_from_widget: MDHeroFrom):
if not self.screen_in.heroes_to:
raise Exception( raise Exception(
f"The `hero_to` attribute is not specified for screen {self.screen_in}" f"The `heroes_to` attribute is not specified for screen "
f"{self.screen_in}"
) )
if len(self.hero_from_widget.children) > 1: # The 'MDHeroFrom' widget allows you to place only one widget in
# itself.
if len(hero_from_widget.children) > 1:
raise Exception( raise Exception(
f"{self.hero_from_widget.__class__} accept only one widget" f"{hero_from_widget.__class__} accept only one widget"
) )
# For new API support.
def _check_hero_to_widget_tag(
self, hero_to_widget: MDHeroTo, hero_from_widget: MDHeroFrom
) -> None:
if not hero_to_widget.tag:
Logger.warning(
"KivyMD: "
f"Set the tag '{hero_from_widget.tag}' "
f"for the {hero_to_widget} widget to the same "
f"as for the {hero_from_widget} widget"
)
hero_to_widget.tag = hero_from_widget.tag
class MDSwapTransition(SwapTransition, MDTransitionBase): class MDSwapTransition(SwapTransition, MDTransitionBase):
pass pass

View File

@ -36,11 +36,13 @@ MDWidget
__all__ = ("MDWidget",) __all__ = ("MDWidget",)
from kivy.uix.widget import Widget
from kivymd.uix import MDAdaptiveWidget from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior from kivymd.uix.behaviors import DeclarativeBehavior
class MDWidget(DeclarativeBehavior, MDAdaptiveWidget): class MDWidget(DeclarativeBehavior, MDAdaptiveWidget, Widget):
""" """
See :class:`~kivy.uix.Widget` class documentation for more information. See :class:`~kivy.uix.Widget` class documentation for more information.