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")
"""Path to uix directory."""
glsl_path = os.path.join(path, "data", "glsl")
"""Path to glsl directory."""
_log_message = (
"KivyMD:"
+ (" Release" if release else "")

View File

@ -43,9 +43,10 @@ __all__ = ("MDApp",)
import os
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.properties import ObjectProperty
from kivy.properties import ObjectProperty, StringProperty
from kivymd.theming import ThemeManager
@ -56,13 +57,16 @@ class FpsMonitoring:
def fps_monitor_start(self) -> None:
"""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.start()
Window.add_widget(monitor)
monitor = FpsMonitor()
monitor.start()
Window.add_widget(monitor)
Clock.schedule_once(add_monitor)
class MDApp(App, FpsMonitoring):
@ -71,6 +75,16 @@ class MDApp(App, FpsMonitoring):
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()
"""
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()
for name_tab in colors.keys():
tab = Tab(text=name_tab)
tab = Tab(title=name_tab)
self.screen.ids.android_tabs.add_widget(tab)
return self.screen
@ -427,7 +427,7 @@ To demonstrate the shades of the palette, you can run the following code:
{
"viewclass": "ItemColor",
"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)
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 "rec_shadow.atlas" in images
assert "transparent.png" in images
"""
)
pyi_main.run(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,18 +11,20 @@ from .backgroundcolor_behavior import (
)
# flake8: NOQA
from .declarative_bahavior import DeclarativeBehavior
from .declarative_behavior import DeclarativeBehavior
from .elevation import (
CircularElevationBehavior,
CommonElevationBehavior,
FakeCircularElevationBehavior,
FakeRectangularElevationBehavior,
ObservableShadow,
RectangularElevationBehavior,
RoundedRectangularElevationBehavior,
)
from .magic_behavior import MagicBehavior
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 .hover_behavior import HoverBehavior # isort:skip

View File

@ -7,8 +7,9 @@ Behaviors/Background Color
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
from typing import List
from typing import List, Union
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import (
ColorProperty,
@ -24,8 +25,6 @@ from kivy.utils import get_color_from_hex
from kivymd.color_definitions import hue, palette, text_colors
from kivymd.theming import ThemeManager
from .elevation import CommonElevationBehavior
Builder.load_string(
"""
#:import RelativeLayout kivy.uix.relativelayout.RelativeLayout
@ -38,7 +37,7 @@ Builder.load_string(
angle: self.angle
origin: self._background_origin
Color:
rgba: self.md_bg_color
rgba: self._md_bg_color
RoundedRectangle:
group: "Background_instruction"
size: self.size
@ -67,7 +66,7 @@ Builder.load_string(
)
class BackgroundColorBehavior(CommonElevationBehavior):
class BackgroundColorBehavior:
background = StringProperty()
"""
Background image path.
@ -153,15 +152,26 @@ class BackgroundColorBehavior(CommonElevationBehavior):
_background_x = NumericProperty(0)
_background_y = NumericProperty(0)
_background_origin = ReferenceListProperty(
_background_x,
_background_y,
)
_background_origin = ReferenceListProperty(_background_x, _background_y)
_md_bg_color = ColorProperty([0, 0, 0, 0])
def __init__(self, **kwarg):
super().__init__(**kwarg)
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(
self, instance_md_widget, pos: List[float]
) -> None:
@ -206,12 +216,14 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
super().__init__(**kwargs)
if hasattr(self, "theme_cls"):
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.theme_cls.bind(theme_style=self._update_specific_text_color)
self.bind(background_hue=self._update_specific_text_color)
self.bind(background_palette=self._update_specific_text_color)
self.bind(
background_hue=self._update_specific_text_color,
background_palette=self._update_specific_text_color,
)
self._update_specific_text_color(None, None)
def _update_specific_text_color(
@ -234,5 +246,17 @@ class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
secondary_color[3] = 0.54
else:
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
class CommonRipple(object):
class CommonRipple:
"""Base class for ripple effect."""
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
.. code-block:: python
.. tabs::
from kivy.lang import Builder
.. tab:: Declarative KV style
from kivymd.app import MDApp
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
from kivymd.uix.button import MDRectangleFlatButton
.. code-block:: python
KV = '''
Screen:
from kivy.lang import Builder
MDBoxLayout:
adaptive_size: True
pos_hint: {"center_x": .5, "center_y": .5}
from kivymd.app import MDApp
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
from kivymd.uix.button import MDFlatButton
MyToggleButton:
text: "Show ads"
group: "x"
KV = '''
MDScreen:
MyToggleButton:
text: "Do not show ads"
group: "x"
MDBoxLayout:
adaptive_size: True
spacing: "12dp"
pos_hint: {"center_x": .5, "center_y": .5}
MyToggleButton:
text: "Does not matter"
group: "x"
'''
MyToggleButton:
text: "Show ads"
group: "x"
MyToggleButton:
text: "Do not show ads"
group: "x"
MyToggleButton:
text: "Does not matter"
group: "x"
'''
class MyToggleButton(MDRectangleFlatButton, MDToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.background_down = self.theme_cls.primary_light
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):
return Builder.load_string(KV)
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()
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
: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
----------------------------------------------------------------------------
@ -88,6 +131,7 @@ from kivy.properties import BooleanProperty, ColorProperty
from kivy.uix.behaviors import ToggleButtonBehavior
from kivymd.uix.button import (
ButtonContentsIconText,
MDFillRoundFlatButton,
MDFillRoundFlatIconButton,
MDFlatButton,
@ -149,7 +193,8 @@ class MDToggleButton(ToggleButtonBehavior):
# Do the object inherited from the "supported" buttons?
if not issubclass(self.__class__, classinfo):
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 (
not self.background_normal
@ -165,10 +210,12 @@ class MDToggleButton(ToggleButtonBehavior):
):
self.__is_filled = True
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:
self.background_normal = self.md_bg_color[:]
# If no background_down is setted:
self.background_normal = (
self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
)
# If no background_down is setter.
if (
not self.background_down
): # 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
# primary color.
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
@ -7,9 +6,9 @@
height:
STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp"
MDScreenManager:
ScreenManager:
id: tab_manager
transition: sm.FadeTransition(duration=.2)
transition: root.transition(duration=root.transition_duration)
on_current:
root.dispatch( \
"on_switch_tabs", \
@ -96,7 +95,7 @@
radius: [16,]
size: root._selected_region_width, dp(32)
pos:
self.center_x - self.width - dp(8), \
self.center_x - root._selected_region_width / 2, \
self.center_y - (dp(16))
MDLabel:

View File

@ -62,59 +62,120 @@ For ease of understanding, this code works like this:
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):
self.theme_cls.material_style = "M3"
return Builder.load_string(
'''
MDScreen:
def build(self):
self.theme_cls.material_style = "M3"
self.theme_cls.theme_style = "Dark"
return Builder.load_string(
'''
MDScreen:
MDBottomNavigation:
panel_color: "#eeeaea"
selected_color_background: "#97ecf8"
text_color_active: 0, 0, 0, 1
MDBottomNavigation:
#panel_color: "#eeeaea"
selected_color_background: "orange"
text_color_active: "lightgrey"
MDBottomNavigationItem:
name: 'screen 1'
text: 'Mail'
icon: 'gmail'
badge_icon: "numeric-10"
MDBottomNavigationItem:
name: 'screen 1'
text: 'Mail'
icon: 'gmail'
badge_icon: "numeric-10"
MDLabel:
text: 'Mail'
halign: 'center'
MDLabel:
text: 'Mail'
halign: 'center'
MDBottomNavigationItem:
name: 'screen 2'
text: 'Discord'
icon: 'discord'
badge_icon: "numeric-5"
MDBottomNavigationItem:
name: 'screen 2'
text: 'Twitter'
icon: 'twitter'
badge_icon: "numeric-5"
MDLabel:
text: 'Discord'
halign: 'center'
MDLabel:
text: 'Twitter'
halign: 'center'
MDBottomNavigationItem:
name: 'screen 3'
text: 'LinkedIN'
icon: 'linkedin'
MDBottomNavigationItem:
name: 'screen 3'
text: 'LinkedIN'
icon: 'linkedin'
MDLabel:
text: 'LinkedIN'
halign: 'center'
'''
)
MDLabel:
text: 'LinkedIN'
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
:align: center
@ -192,16 +253,13 @@ from kivy.properties import (
)
from kivy.uix.behaviors import ButtonBehavior
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.material_resources import STANDARD_INCREMENT
from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.anchorlayout import MDAnchorLayout
from kivymd.uix.behaviors import (
DeclarativeBehavior,
FakeRectangularElevationBehavior,
)
from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior
from kivymd.uix.behaviors.backgroundcolor_behavior import (
SpecificBackgroundColorBehavior,
)
@ -413,6 +471,28 @@ class MDBottomNavigationItem(MDTab):
def __init__(self, *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:
"""Called when clicking on a panel item."""
@ -420,28 +500,13 @@ class MDBottomNavigationItem(MDTab):
bottom_navigation_header_object = (
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.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
bottom_navigation_object.previous_tab = self
self.animate_header(
bottom_navigation_object, bottom_navigation_header_object
)
super().on_tab_press(*args)
def on_disabled(
self, instance_bottom_navigation_item, disabled_value: bool
@ -498,6 +563,26 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
.. 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 of the label when it is not selected.
@ -772,8 +857,6 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
class MDBottomNavigationBar(
ThemableBehavior,
FakeRectangularElevationBehavior,
MDFloatLayout,
ThemableBehavior, CommonElevationBehavior, MDFloatLayout
):
pass

View File

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

View File

@ -93,6 +93,8 @@ from kivymd.uix.behaviors import DeclarativeBehavior
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.
"""

View File

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

View File

@ -3,9 +3,9 @@
Clear
Color:
rgba:
(self._md_bg_color or [0.0, 0.0, 0.0, 0.0]) \
self._md_bg_color \
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:
size: self.size
pos: self.pos
@ -13,19 +13,17 @@
radius: [root._radius, ]
Color:
rgba:
root._line_color or [0.0, 0.0, 0.0, 0.0] \
root._line_color \
if not root.disabled else \
( \
root._line_color_disabled \
or self._disabled_color \
or [0.0, 0.0, 0.0, 0.0] \
)
(root._line_color_disabled or self._disabled_color)
Line:
width: root.line_width
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, \
self.height)
self.height \
)
size_hint: None, None
anchor_x: root.halign
@ -33,21 +31,28 @@
_round_rad: [self._radius] * 4
<ButtonContentsText>
lbl_txt: lbl_txt
width:
max(root._min_width, \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2])
max( \
root._min_width, \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
)
size_hint_min_x:
max(root._min_width, \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2])
max( \
root._min_width, \
root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
)
height:
max(root._min_height, \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3])
max( \
root._min_height, \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
)
size_hint_min_y:
max(root._min_height, \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3])
max( \
root._min_height, \
root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
)
MDLabel:
id: lbl_txt
@ -84,7 +89,10 @@
# 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) \
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:
if self.icon not in md_icons.keys(): self.size_hint = (1, 1)
theme_text_color: root._theme_icon_color
@ -131,7 +139,7 @@
id: box
adaptive_size: True
padding: 0
spacing: "4dp"
spacing: "8dp"
MDIcon:
id: lbl_ic
@ -193,48 +201,21 @@
radius: [self.height / 2]
<BaseFloatingRootButton>
<MDFloatingRootButton>
theme_text_color: "Custom"
md_bg_color: self.theme_cls.primary_color
<MDFloatingLabel>
padding_x: "8dp"
padding_y: "8dp"
adaptive_size: True
theme_text_color: "Custom"
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:
rgba:
self.theme_cls.primary_color \
if not root.bg_color else \
root.bg_color
rgba: self.bg_color
RoundedRectangle:
pos: self.pos
size: self.size
radius: [5]
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
pos: self.pos
radius: self.radius

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,6 @@
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>
md_bg_color:
self.theme_cls.divider_color \

View File

@ -26,26 +26,6 @@ Components/Card
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
------------------------------------------------------------------------------------
@ -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 kivymd.app import MDApp
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
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.'''
text = StringProperty()
@ -126,7 +105,6 @@ An example of the implementation of a card in the style of material design versi
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
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
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
class MD3Card(MDCard):
'''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,
color="grey",
pos=("12dp", "12dp"),
bold=True,
),
),
line_color=(0.2, 0.2, 0.2, 0.8),
@ -255,10 +232,9 @@ End full code
MDBoxLayout:
orientation: "vertical"
spacing: "10dp"
MDTopAppBar:
elevation: 10
elevation: 4
title: "MDCardSwipe"
MDScrollView:
@ -286,7 +262,7 @@ End full code
'''Creates a list of cards.'''
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}")
)
@ -316,7 +292,7 @@ End full code
MDScreen(
MDBoxLayout(
MDTopAppBar(
elevation=10,
elevation=4,
title="MDCardSwipe",
),
MDScrollView(
@ -328,7 +304,6 @@ End full code
),
id="box",
orientation="vertical",
spacing="10dp",
),
)
)
@ -426,7 +401,7 @@ You can use this event to remove items from a list:
.. code-block:: python
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
@ -496,10 +471,9 @@ End full code
MDBoxLayout:
orientation: "vertical"
spacing: "10dp"
MDTopAppBar:
elevation: 10
elevation: 4
title: "MDCardSwipe"
MDScrollView:
@ -560,7 +534,7 @@ End full code
MDScreen(
MDBoxLayout(
MDTopAppBar(
elevation=10,
elevation=4,
title="MDCardSwipe",
),
MDScrollView(
@ -572,7 +546,6 @@ End full code
),
id="box",
orientation="vertical",
spacing="10dp",
),
)
)
@ -630,26 +603,21 @@ Focus behavior
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.card import MDCard
KV = '''
MDScreen:
ElevationCard:
MDCard:
size_hint: .7, .4
focus_behavior: True
pos_hint: {"center_x": .5, "center_y": .5}
md_bg_color: "darkgrey"
unfocus_color: "darkgrey"
focus_color: "grey"
elevation: 6
'''
class ElevationCard(MDCard, FakeRectangularElevationBehavior):
pass
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
@ -663,27 +631,23 @@ Focus behavior
.. code-block:: python
from kivymd.app import MDApp
from kivymd.uix.behaviors import FakeRectangularElevationBehavior
from kivymd.uix.card import MDCard
from kivymd.uix.screen import MDScreen
class ElevationCard(MDCard, FakeRectangularElevationBehavior):
pass
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
return (
MDScreen(
ElevationCard(
MDCard(
size_hint=(0.7, 0.4),
focus_behavior=True,
pos_hint={"center_x": 0.5, "center_y": 0.5},
md_bg_color="darkgrey",
unfocus_color="darkgrey",
focus_color="grey",
elevation=6,
),
)
)
@ -691,7 +655,6 @@ Focus behavior
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif
:align: center
@ -732,12 +695,14 @@ from kivy.properties import (
VariableListProperty,
)
from kivy.uix.boxlayout import BoxLayout
from kivy.utils import get_color_from_hex
from kivymd import uix_path
from kivymd.color_definitions import colors
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
BackgroundColorBehavior,
CommonElevationBehavior,
DeclarativeBehavior,
RectangularRippleBehavior,
)
@ -781,6 +746,7 @@ class MDCard(
ThemableBehavior,
BackgroundColorBehavior,
RectangularRippleBehavior,
CommonElevationBehavior,
FocusBehavior,
BoxLayout,
):
@ -800,14 +766,6 @@ class MDCard(
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)])
"""
Card radius by default.
@ -831,15 +789,16 @@ class MDCard(
"""
_bg_color_map = (
colors["Light"]["CardsDialogs"],
colors["Dark"]["CardsDialogs"],
get_color_from_hex(colors["Light"]["CardsDialogs"]),
get_color_from_hex(colors["Dark"]["CardsDialogs"]),
[1.0, 1.0, 1.0, 0.0],
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.theme_cls.bind(theme_style=self.update_md_bg_color)
self.theme_cls.bind(material_style=self.set_style)
self.theme_cls.bind(
material_style=self.set_style, theme_style=self.update_md_bg_color
)
Clock.schedule_once(self.set_style)
Clock.schedule_once(
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:
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:
self.set_radius()
@ -865,7 +826,7 @@ class MDCard(
if self.style == "outlined" or self.style == "filled":
self.elevation = 0
elif self.style == "elevated":
self.elevation = 1
self.elevation = 2
def set_radius(self) -> None:
if (

View File

@ -132,7 +132,7 @@ Use with elevation
icon_right: "close-circle-outline"
line_color: app.theme_cls.disabled_hint_text_color
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
:align: center
@ -304,7 +304,6 @@ __all__ = ("MDChip",)
import os
from kivy import Logger
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.metrics import dp
@ -314,14 +313,13 @@ from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
FakeRectangularElevationBehavior,
CommonElevationBehavior,
RectangularRippleBehavior,
ScaleBehavior,
TouchBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDIcon
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.templates import ScaleWidget
with open(
os.path.join(uix_path, "chip", "chip.kv"), encoding="utf-8"
@ -330,12 +328,12 @@ with open(
class MDChip(
MDBoxLayout,
ThemableBehavior,
RectangularRippleBehavior,
FakeRectangularElevationBehavior,
TouchBehavior,
ButtonBehavior,
MDBoxLayout,
CommonElevationBehavior,
TouchBehavior,
):
text = StringProperty()
"""
@ -456,7 +454,7 @@ class MDChip(
self.active = False
class MDScalableCheckIcon(MDIcon, ScaleWidget):
class MDScalableCheckIcon(MDIcon, ScaleBehavior):
pos_hint = {"center_y": 0.5}

View File

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

View File

@ -11,19 +11,6 @@ Components/DataTables
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png
: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.
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
@ -159,6 +146,15 @@ class CellHeader(MDTooltip, BoxLayout):
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.
sort_action = ObjectProperty()
"""
@ -340,11 +336,19 @@ class TableHeader(ThemableBehavior, ScrollView):
CellHeader(
text=col_heading[0],
sort_action=col_heading[2],
tooltip=col_heading[3],
width=self.cols_minimum[i],
table_data=self.table_data,
is_sorted=(col_heading[0] == self.sorted_on),
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
else CellHeader(
text=col_heading[0],
@ -356,6 +360,9 @@ class TableHeader(ThemableBehavior, ScrollView):
else:
# Sets the text in the first cell.
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.width = self.cols_minimum[i]
@ -765,6 +772,9 @@ class TablePagination(ThemableBehavior, MDBoxLayout):
class MDDataTable(ThemableBehavior, AnchorLayout):
"""
See :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation for
more information.
:Events:
:attr:`on_row_press`
Called when a table row is clicked.
@ -775,7 +785,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. code-block:: python
from kivy.metrics import dp
from kivymd.app import MDApp
@ -914,38 +923,82 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
"""
Data for header columns.
.. code-block:: python
.. tabs::
from kivy.metrics import dp
.. tab:: Imperative python style
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivy.uix.anchorlayout import AnchorLayout
.. code-block:: python
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):
def build(self):
layout = AnchorLayout()
self.data_tables = 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)),
],
)
layout.add_widget(self.data_tables)
return layout
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout()
self.data_tables = MDDataTable(
size_hint=(0.7, 0.6),
use_pagination=True,
check=True,
# name column, width column, sorting function column(optional), custom tooltip
column_data=[
("No.", dp(30), None, "Custom tooltip"),
("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)),
],
)
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
:align: center
@ -1060,6 +1113,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
@ -1187,7 +1243,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
"""
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
:attr:`check` is an :class:`~kivy.properties.BooleanProperty`
@ -1209,6 +1265,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
@ -1238,19 +1297,19 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
and defaults to `False`.
"""
elevation = NumericProperty(8)
elevation = NumericProperty(4)
"""
Table elevation.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `8`.
and defaults to `4`.
"""
rows_num = NumericProperty(5)
"""
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
:attr:`rows_num` is an :class:`~kivy.properties.NumericProperty`
@ -1266,7 +1325,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. 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
.. rubric:: Auto
@ -1282,11 +1341,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
"""
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
.. 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 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`.
Use markup strings
@ -1315,6 +1369,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = AnchorLayout()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
@ -1354,7 +1411,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
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
@ -1374,7 +1432,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
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
@ -1395,7 +1454,8 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
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
@ -1408,7 +1468,7 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
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
:attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` and
@ -1503,6 +1563,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
data_tables = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = MDFloatLayout() # root layout
# Creating control buttons.
button_box = MDBoxLayout(
@ -1604,6 +1667,9 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
data_tables = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
layout = MDFloatLayout()
layout.add_widget(
MDRaisedButton(

View File

@ -18,7 +18,11 @@
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>
@ -28,7 +32,6 @@
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
elevation: 24
padding: "24dp", "24dp", "8dp", "8dp"
radius: root.radius
md_bg_color:

View File

@ -38,6 +38,8 @@ Usage
dialog = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_alert_dialog(self):
@ -87,6 +89,7 @@ from kivy.uix.modalview import ModalView
from kivymd import uix_path
from kivymd.material_resources import DEVICE_TYPE
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.button import BaseButton
from kivymd.uix.card import MDSeparator
from kivymd.uix.list import BaseListItem
@ -97,7 +100,40 @@ with open(
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)])
"""
Dialog corners rounding value.
@ -250,21 +286,22 @@ class MDDialog(BaseDialog):
class Example(MDApp):
dialog = None
def build(self):
return Builder.load_string(KV)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_simple_dialog(self):
if not self.dialog:
self.dialog = MDDialog(
title="Set backup account",
type="simple",
items=[
Item(text="user01@gmail.com", source="user-1.png"),
Item(text="user02@gmail.com", source="user-2.png"),
Item(text="Add account", source="add-icon.png"),
],
)
self.dialog.open()
def show_simple_dialog(self):
if not self.dialog:
self.dialog = MDDialog(
title="Set backup account",
type="simple",
items=[
Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"),
Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"),
],
)
self.dialog.open()
Example().run()
@ -317,6 +354,8 @@ class MDDialog(BaseDialog):
dialog = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_confirmation_dialog(self):
@ -385,71 +424,140 @@ class MDDialog(BaseDialog):
"""
Custom content class.
.. code-block:: python
.. tabs::
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
.. tab:: Declarative KV style
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
.. code-block:: python
KV = '''
<Content>
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
MDTextField:
hint_text: "City"
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
MDTextField:
hint_text: "Street"
KV = '''
<Content>
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
MDTextField:
hint_text: "City"
MDTextField:
hint_text: "Street"
MDFloatLayout:
MDFloatLayout:
MDFlatButton:
text: "ALERT DIALOG"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_confirmation_dialog()
'''
MDFlatButton:
text: "ALERT DIALOG"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_confirmation_dialog()
'''
class Content(BoxLayout):
pass
class Content(BoxLayout):
pass
class Example(MDApp):
dialog = None
class Example(MDApp):
dialog = None
def build(self):
return Builder.load_string(KV)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_confirmation_dialog(self):
if not self.dialog:
self.dialog = MDDialog(
title="Address:",
type="custom",
content_cls=Content(),
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()
def show_confirmation_dialog(self):
if not self.dialog:
self.dialog = MDDialog(
title="Address:",
type="custom",
content_cls=Content(),
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()
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
:align: center
@ -460,7 +568,7 @@ class MDDialog(BaseDialog):
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`
and defaults to `None`.

View File

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

View File

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

View File

@ -6,28 +6,29 @@
background_normal: ""
background_down: ""
dir_or_file_name: ""
icon_color: 0, 0, 0, 0
_selected: False
events_callback: lambda x: None
orientation: "vertical"
ModifiedOneLineIconListItem:
text: root.dir_or_file_name
on_release: root.events_callback(root.path, root)
bg_color:
self.theme_cls.bg_darkest \
if root._selected else self.theme_cls.bg_normal
on_release: root.events_callback(root.path, root)
if root._selected else \
self.theme_cls.bg_normal
IconLeftWidget:
icon: root.icon
theme_text_color: "Custom"
text_color: self.theme_cls.primary_color
theme_icon_color: "Custom"
icon_color: root.icon_color
MDSeparator:
<LabelContent@MDLabel>
size_hint_y: None
height: self.texture_size[1]
adaptive_height: True
shorten: True
shorten_from: "center"
halign: "center"
@ -61,23 +62,6 @@
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>
md_bg_color: root.theme_cls.bg_normal
@ -90,7 +74,11 @@
title: root.current_path
right_action_items: [["close-box", lambda x: root.exit_manager(1)]]
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:
id: rv

View File

@ -9,7 +9,7 @@ Usage
.. 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(
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
@ -19,7 +19,7 @@ Usage
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/file-manager.png
: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.
Or with ``preview`` mode:
@ -32,7 +32,7 @@ Or with ``preview`` mode:
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
.. warning:: The `preview` mode is intended only for viewing images and will
@ -43,6 +43,8 @@ Example
.. code-block:: python
import os
from kivy.core.window import Window
from kivy.lang import Builder
@ -53,19 +55,19 @@ Example
KV = '''
MDBoxLayout:
orientation: 'vertical'
orientation: "vertical"
MDTopAppBar:
title: "MDFileManager"
left_action_items: [['menu', lambda x: None]]
elevation: 10
left_action_items: [["menu", lambda x: None]]
elevation: 3
MDFloatLayout:
MDRoundFlatIconButton:
text: "Open manager"
icon: "folder"
pos_hint: {'center_x': .5, 'center_y': .6}
pos_hint: {"center_x": .5, "center_y": .5}
on_release: app.file_manager_open()
'''
@ -76,23 +78,23 @@ Example
Window.bind(on_keyboard=self.events)
self.manager_open = False
self.file_manager = MDFileManager(
exit_manager=self.exit_manager,
select_path=self.select_path,
preview=True,
exit_manager=self.exit_manager, select_path=self.select_path
)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
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
def select_path(self, path):
'''It will be called when you click on the file name
def select_path(self, path: str):
'''
It will be called when you click on the file name
or the catalog selection button.
:type path: str;
:param path: path to the selected directory or file;
'''
@ -126,6 +128,9 @@ Not tested on `iOS`.
def file_manager_open(self):
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",)
@ -136,6 +141,7 @@ import re
from typing import List, Tuple, Union
from kivy import platform
from kivy.clock import Clock
from kivy.factory import Factory
from kivy.lang import Builder
from kivy.metrics import dp
@ -148,7 +154,6 @@ from kivy.properties import (
OptionProperty,
StringProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.modalview import ModalView
@ -156,6 +161,7 @@ from kivymd import images_path, uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import CircularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFloatingActionButton
from kivymd.uix.fitimage import FitImage
from kivymd.uix.list import BaseListItem
from kivymd.uix.relativelayout import MDRelativeLayout
@ -167,9 +173,7 @@ with open(
class BodyManager(MDBoxLayout):
"""
Base class for folders and files icons.
"""
"""Base class for folders and files icons."""
class BodyManagerWithPreview(MDBoxLayout):
@ -182,47 +186,146 @@ class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage):
"""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):
_txt_left_pad = NumericProperty("72dp")
_txt_top_pad = NumericProperty("16dp")
_txt_bot_pad = NumericProperty("15dp")
_num_lines = 1
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.height = dp(48)
class MDFileManager(ThemableBehavior, MDRelativeLayout):
icon = StringProperty("check")
class MDFileManager(MDRelativeLayout, ThemableBehavior):
"""
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`
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")
"""
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`
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)
"""
Function called when the user reaches directory tree root.
@ -259,12 +362,12 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
and defaults to `all`.
"""
current_path = StringProperty(os.getcwd())
current_path = StringProperty(os.path.expanduser("~"))
"""
Current directory.
:attr:`current_path` is an :class:`~kivy.properties.StringProperty`
and defaults to `/`.
and defaults to `os.path.expanduser("~")`.
"""
use_access = BooleanProperty(True)
@ -295,9 +398,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"name", options=["nothing", "name", "date", "size", "type"]
)
"""
It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option
By default, sort by name.
Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.
It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files
by option. By default, sort by name. Available options are:
`'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.
:attr:`sort_by` is an :class:`~kivy.properties.OptionProperty`
and defaults to `name`.
@ -325,29 +428,33 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"""
Contains the list of files that are currently selected.
:attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and
defaults to `[]`.
:attr:`selection` is a read-only :class:`~kivy.properties.ListProperty`
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_open = False
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __init__(self, *args, **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.font_style = "Subtitle1"
if (
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,
)
)
Clock.schedule_once(self._create_selection_button)
if self.preview:
self.ext = [".png", ".jpg", ".jpeg"]
@ -400,15 +507,7 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
}
)
self.ids.rv.data = manager_list
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
self._show()
def show(self, path: str) -> None:
"""
@ -474,6 +573,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"icon": icon,
"dir_or_file_name": name,
"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,
}
)
@ -488,19 +590,14 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
"icon": "file-outline",
"dir_or_file_name": os.path.split(name)[1],
"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,
}
)
self.ids.rv.data = manager_list
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
self._show()
def get_access_string(self, path: str) -> str:
access_string = ""
@ -557,7 +654,9 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
def close(self) -> None:
"""Closes the file manager window."""
self.dispatch("on_pre_dismiss")
self._window_manager.dismiss()
self.dispatch("on_dismiss")
self._window_manager_open = False
def select_dir_or_file(
@ -609,6 +708,84 @@ class MDFileManager(ThemableBehavior, MDRelativeLayout):
if self.selector == "folder" or self.selector == "any":
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_by_name(files):
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.widget import Widget
from kivymd.uix.behaviors import StencilBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.templates import StencilWidget
class FitImage(MDBoxLayout, StencilWidget):
class FitImage(MDBoxLayout, StencilBehavior):
source = ObjectProperty()
"""
Filename/source of your image.

View File

@ -90,4 +90,7 @@ from kivymd.uix.behaviors import DeclarativeBehavior
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
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: hero_from.size
@ -72,7 +72,7 @@ Base example
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen B"
MDScreen:
@ -82,6 +82,7 @@ Base example
MDHeroTo:
id: hero_to
tag: "hero"
size_hint: None, None
size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5}
@ -91,7 +92,7 @@ Base example
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen A"
'''
@ -113,6 +114,7 @@ Note that the child of the :class:`~MDHeroFrom` widget must have the size of the
MDHeroFrom:
id: hero_from
tag: "hero"
FitImage:
size_hint: None, None
@ -127,7 +129,7 @@ container in which the hero is located:
MDRaisedButton:
text: "Move Hero To Screen B"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen 2"
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:
text: "Go To Another Screen"
on_release:
root.current_hero = ""
root.current_heroes = []
root.current = "another screen"
Example
@ -166,7 +168,7 @@ Example
x: 24
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: hero_from.size
@ -175,7 +177,7 @@ Example
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen B"
MDScreen:
@ -185,6 +187,7 @@ Example
MDHeroTo:
id: hero_to
tag: "hero"
size_hint: None, None
size: "220dp", "220dp"
pos_hint: {"center_x": .5, "center_y": .5}
@ -194,7 +197,7 @@ Example
pos_hint: {"center_x": .5}
y: "52dp"
on_release:
root.current_hero = ""
root.current_heroes = []
root.current = "screen C"
MDRaisedButton:
@ -202,7 +205,7 @@ Example
pos_hint: {"center_x": .5}
y: "8dp"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen A"
MDScreen:
@ -283,7 +286,7 @@ background color of the hero during the flight between the screens:
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen B"
MDScreen:
@ -293,6 +296,7 @@ background color of the hero during the flight between the screens:
MDHeroTo:
id: hero_to
tag: "hero"
size_hint: None, None
size: "220dp", "220dp"
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}
y: "36dp"
on_release:
root.current_hero = "hero"
root.current_heroes = ["hero"]
root.current = "screen A"
'''
@ -370,7 +374,7 @@ Usage with ScrollView
radius: 24
box_radius: 0, 0, 24, 24
box_color: 0, 0, 0, .5
source: "image.jpg"
source: "kivymd/images/logo/kivymd-icon-512.png"
size_hint: None, None
size: root.size
mipmap: True
@ -399,7 +403,7 @@ Usage with ScrollView
MDScreen:
name: "screen B"
hero_to: hero_to
heroes_to: [hero_to]
MDHeroTo:
id: hero_to
@ -412,7 +416,7 @@ Usage with ScrollView
pos_hint: {"center_x": .5}
y: "36dp"
on_release:
root.current_hero = "hero"
root.current_heroes = [hero_to.tag]
root.current = "screen A"
'''
@ -441,7 +445,8 @@ Usage with ScrollView
def on_release(self):
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"
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
: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
@ -476,6 +568,9 @@ class MDHeroFrom(MDBoxLayout):
"""
The container from which the hero begins his flight.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
:Events:
`on_transform_in`
when the hero flies from screen **A** to screen **B**.
@ -487,7 +582,7 @@ class MDHeroFrom(MDBoxLayout):
"""
Tag ID for heroes.
:attr:`shift_right` is an :class:`~kivy.properties.StringProperty`
:attr:`tag` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
@ -504,4 +599,17 @@ class MDHeroFrom(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
"""
__all__ = "MDSmartTile"
__all__ = [
"MDSmartTile",
]
import os
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.properties import (
BooleanProperty,
ColorProperty,

View File

@ -17,8 +17,14 @@
rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0)
Rectangle:
source: self.source if self.source else None
pos: self.pos
size: self.size
pos:
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"
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
from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.lang import Builder
from kivy.metrics import sp
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
ListProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
)
@ -322,6 +326,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
parent_background = ColorProperty(None)
can_capitalize = BooleanProperty(True)
canvas_bg = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -349,6 +354,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
font_info = self.theme_cls.font_styles[self.font_style]
self.font_name = font_info[0]
self.font_size = sp(font_info[1])
if font_info[2] and self.can_capitalize:
self._capitalizing = True
else:
@ -374,29 +380,68 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
# generic None value it's not yet been set
self._text_color_str = ""
if theme_text_color == "Custom" and self.text_color:
self.color = self.text_color
color = self.text_color
elif (
theme_text_color == "ContrastParentBackground"
and self.parent_background
):
self.color = get_contrast_text_color(self.parent_background)
color = get_contrast_text_color(self.parent_background)
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":
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:
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):
if self._text_color_str:
self.color = getattr(self.theme_cls, self._text_color_str)
if not self.disabled:
self.color = getattr(self.theme_cls, self._text_color_str)
color = getattr(self.theme_cls, self._text_color_str)
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):
@ -456,11 +501,16 @@ class MDIcon(MDFloatLayout, MDLabel):
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
super().__init__(**kwargs)
if not isinstance(self, MDCheckbox):
self.size_hint = (None, None)
self.size = self.texture_size
self._size = self.texture_size[1], self.texture_size[1]
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>
@ -14,7 +14,7 @@
<MDMenu>
size_hint: None, None
width: root.width_mult * STD_INC
width: root.width_mult * STANDARD_INCREMENT
bar_width: 0
key_viewclass: "viewclass"
key_size: "height"
@ -28,7 +28,7 @@
orientation: "vertical"
<MenuContainer@MDCard+FakeRectangularElevationBehavior>
<MenuContainer@MDCard>
<MDDropdownMenu>

View File

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

View File

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

View File

@ -29,45 +29,86 @@ Usage
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:
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:
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
Example().run()
.. 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
:align: center
@ -75,202 +116,412 @@ Usage
Example
=======
.. code-block:: python
.. tabs::
from kivy.clock import Clock
from kivy.lang import Builder
.. tab:: Declarative KV and imperative python styles
from kivymd.app import MDApp
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
.. code-block:: python
KV = '''
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
from kivy.clock import Clock
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>
elevation: 3
-height: "56dp"
<ExtendedButton>
elevation: 3.5
shadow_radius: 12
shadow_softness: 4
-height: "56dp"
<DrawerClickableItem@MDNavigationDrawerItem>
focus_color: "#e7e4c0"
unfocus_color: "#fffcf4"
<DrawerClickableItem@MDNavigationDrawerItem>
focus_color: "#e7e4c0"
unfocus_color: "#fffcf4"
MDScreen:
MDScreen:
MDNavigationLayout:
MDNavigationLayout:
ScreenManager:
ScreenManager:
MDScreen:
MDScreen:
MDBoxLayout:
orientation: "vertical"
MDBoxLayout:
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:
orientation: "vertical"
adaptive_height: True
md_bg_color: "#fffcf4"
padding: "12dp"
spacing: "12dp"
padding: "3dp", 0, 0, "12dp"
MDLabel:
text: "12:00"
adaptive_height: True
pos_hint: {"center_y": .5}
MDIconButton:
icon: "menu"
MDBoxLayout:
ExtendedButton:
text: "Compose"
icon: "pencil"
MDNavigationRail:
id: navigation_rail
md_bg_color: "#fffcf4"
selected_color_background: "#e7e4c0"
ripple_color_item: "#e7e4c0"
on_item_release: app.switch_screen(*args)
DrawerClickableItem:
text: "Python"
icon: "language-python"
MDNavigationRailMenuButton:
on_release: nav_drawer.set_state("open")
DrawerClickableItem:
text: "JavaScript"
icon: "language-javascript"
MDNavigationRailFabButton:
md_bg_color: "#b0f0d6"
DrawerClickableItem:
text: "CPP"
icon: "language-cpp"
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: 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.
DrawerClickableItem:
text: "Swift"
icon: "language-swift"
'''
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,
)
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)
from kivymd.uix.navigationrail import (
MDNavigationRail,
MDNavigationRailMenuButton,
MDNavigationRailFabButton,
MDNavigationRailItem,
)
from kivymd.uix.screen import MDScreen
from kivymd.uix.screenmanager import MDScreenManager
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
:align: center
@ -306,12 +557,11 @@ from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
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.button import MDFloatingActionButton, MDIconButton
from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.templates import ScaleWidget
from kivymd.uix.widget import MDWidget
with open(
@ -333,7 +583,7 @@ class PanelItems(MDBoxLayout):
"""Box for menu items."""
class RippleWidget(MDWidget, ScaleWidget):
class RippleWidget(MDWidget, ScaleBehavior):
"""
Implements a background color for a menu item -
(:class:`~MDNavigationRailItem`).
@ -562,7 +812,7 @@ class MDNavigationRailItem(ThemableBehavior, ButtonBehavior, MDBoxLayout):
self.navigation_rail.dispatch("on_item_release", self)
class MDNavigationRail(MDCard, FakeRectangularElevationBehavior):
class MDNavigationRail(MDCard):
"""
:Events:
:attr:`on_item_press`

View File

@ -307,34 +307,6 @@
else (dp(32), dp(32))
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
# or use `min_date/max_date`.
canvas.before:
@ -355,12 +327,14 @@
if root.theme_cls.device_orientation == "portrait" \
else \
(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( \
self.current_year, \
self.current_month, \
int(self.text) \
) \
or self.text and int(self.text) == \
calendar.monthrange(self.current_year, self.current_month)[1] \
else (dp(46), dp(28))
pos:
(self.x - dp(1.5), self.y + dp(5)) \
@ -395,29 +369,15 @@
else [0, 0, 0, 0]) \
)
# Circle marking the beginning and end of the date range if the "range"
# mode is used.
# Selection circle.
Color:
rgba:
[0, 0, 0, 0] if not self.owner._date_range else \
(
( \
self.theme_cls.primary_color if not root.owner.selector_color \
else root.owner.selector_color \
) \
if self.text and self.owner._date_range[0] == date( \
self.current_year, \
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) \
)
if root.is_selected and not self.disabled \
else (0, 0, 0, 0)
Ellipse:
size:
(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
: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
.. code-block:: python
.. tabs::
from kivy.lang import Builder
.. tab:: Declarative KV style
from kivymd.app import MDApp
from kivymd.uix.pickers import MDDatePicker
.. code-block:: python
KV = '''
MDFloatLayout:
from kivy.lang import Builder
MDTopAppBar:
title: "MDDatePicker"
pos_hint: {"top": 1}
elevation: 10
from kivymd.app import MDApp
from kivymd.uix.pickers import MDDatePicker
MDRaisedButton:
text: "Open date picker"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_date_picker()
'''
KV = '''
MDFloatLayout:
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
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'>;
MDRaisedButton:
text: "Open date picker"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_date_picker()
'''
print(instance, value, date_range)
def on_cancel(self, instance, value):
'''Events called when the "CANCEL" dialog box button is clicked.'''
class Test(MDApp):
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):
date_dialog = MDDatePicker()
date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel)
date_dialog.open()
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):
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
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.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
Interval date
@ -94,12 +139,16 @@ that are not included in this range will have the status `disabled`.
def show_date_picker(self):
date_dialog = MDDatePicker(
min_date=datetime.date(2021, 2, 15),
max_date=datetime.date(2021, 3, 27),
min_date=datetime.date.today(),
max_date=datetime.date(
datetime.date.today().year,
datetime.date.today().month,
datetime.date.today().day + 2,
),
)
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
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
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()
.. 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
"""
from __future__ import annotations
__all__ = ("MDDatePicker", "BaseDialogPicker", "DatePickerInputField")
import calendar
import datetime
import math
import os
import time
from datetime import date
from itertools import zip_longest
from typing import Union
from kivy import Logger
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
@ -175,7 +227,7 @@ from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.toast import toast
from kivymd.uix.behaviors import (
CircularRippleBehavior,
FakeRectangularElevationBehavior,
CommonElevationBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
@ -194,7 +246,7 @@ with open(
class BaseDialogPicker(
BaseDialog,
FakeRectangularElevationBehavior,
CommonElevationBehavior,
SpecificBackgroundColorBehavior,
):
"""
@ -255,11 +307,11 @@ class BaseDialogPicker(
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
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
:align: center
@ -270,13 +322,13 @@ class BaseDialogPicker(
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
primary_color="brown",
accent_color="darkred",
)
.. 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)
"""
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
primary_color="brown",
accent_color="darkred",
selector_color="red",
)
.. 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)
"""
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
primary_color="brown",
accent_color="darkred",
selector_color="red",
text_toolbar_color="lightgrey",
)
.. 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)
"""
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
text_color=("#ffffff"),
primary_color="brown",
accent_color="darkred",
selector_color="red",
text_toolbar_color="lightgrey",
text_color="orange",
)
.. 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)
"""
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
text_color=("#ffffff"),
text_current_color=get_color_from_hex("#e93f39"),
primary_color="brown",
accent_color="darkred",
selector_color="red",
text_toolbar_color="lightgrey",
text_color="orange",
text_current_color="white",
)
.. 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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
text_color=("#ffffff"),
text_current_color=get_color_from_hex("#e93f39"),
text_button_color=(1, 1, 1, .5),
primary_color="brown",
accent_color="darkred",
selector_color="red",
text_toolbar_color="lightgrey",
text_color="orange",
text_current_color="white",
text_button_color="lightgrey",
)
.. 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`.
"""
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
text_color=("#ffffff"),
text_current_color=get_color_from_hex("#e93f39"),
input_field_background_color=(1, 1, 1, 0.2),
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",
)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png
: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`.
"""
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)
"""
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
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
text_color=("#ffffff"),
text_current_color=get_color_from_hex("#e93f39"),
input_field_background_color=(1, 1, 1, 0.2),
input_field_text_color=(1, 1, 1, 1),
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-background-color-date.png
.. 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` 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`.
"""
@ -447,16 +573,18 @@ class BaseDialogPicker(
.. code-block:: python
MDDatePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
selector_color=get_color_from_hex("#e93f39"),
text_toolbar_color=get_color_from_hex("#cccccc"),
text_color=("#ffffff"),
text_current_color=get_color_from_hex("#e93f39"),
input_field_background_color=(1, 1, 1, 0.2),
input_field_text_color=(1, 1, 1, 1),
font_name="Weather.ttf",
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",
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
@ -471,6 +599,20 @@ class BaseDialogPicker(
self.register_event_type("on_save")
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:
"""Events called when the "OK" dialog box button is clicked."""
@ -606,13 +748,18 @@ class DatePickerDaySelectableItem(
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):
"""Implements an item for a pick list of the year."""
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
selected_color = ColorProperty([0, 0, 0, 0])
owner = ObjectProperty()
@ -623,7 +770,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
def on_touch_down(self, touch):
if super().on_touch_down(touch):
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.sel_year = self.owner.year
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)
def apply_selection(self, table_data, index, is_selected):
self.selected = is_selected
if is_selected:
self.selected_color = (
self.owner.selector_color
@ -661,7 +807,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
class MDDatePicker(BaseDialogPicker):
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
:align: center
@ -813,7 +959,6 @@ class MDDatePicker(BaseDialogPicker):
_enter_data_field_two = None
_enter_data_field_container = None
_date_range = []
_sel_day_widget = ObjectProperty()
_scale_calendar_layout = NumericProperty(1)
_scale_year_layout = NumericProperty(0)
_shift_dialog_height = NumericProperty(0)
@ -838,11 +983,6 @@ class MDDatePicker(BaseDialogPicker):
self.month = self.sel_month
self.year = self.sel_year
self.day = self.sel_day
self._current_selected_date = (
self.sel_day,
self.sel_month,
self.sel_year,
)
super().__init__(**kwargs)
self.theme_cls.bind(device_orientation=self.on_device_orientation)
@ -861,16 +1001,6 @@ class MDDatePicker(BaseDialogPicker):
self.generate_list_widgets_days()
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(
self, instance_theme_manager: ThemeManager, orientation_value: str
) -> 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_right)
Animation(_scale_year_layout=0, d=0.15).start(self)
Animation(
_shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15
).start(self)
Animation(_scale_calendar_layout=1, d=0.15).start(self)
self._calendar_layout.clear_widgets()
self.generate_list_widgets_days()
# Move selection to the same day and month of the selected year.
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)
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 disabled_chevron_buttons(*args):
self.ids.chevron_left.disabled = True
@ -943,15 +1069,20 @@ class MDDatePicker(BaseDialogPicker):
self._select_year_dialog_open = True
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_right)
Animation(_scale_calendar_layout=0, d=0.15).start(self)
anim = Animation(_scale_year_layout=1, d=0.15)
anim.bind(on_complete=disabled_chevron_buttons)
anim.start(self)
self.ids.triangle.icon = "menu-up"
self.generate_list_widgets_years()
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 set_date_to_input_field():
@ -1063,13 +1194,10 @@ class MDDatePicker(BaseDialogPicker):
if not self.min_date and not self.max_date:
list_date = self._enter_data_field.get_list_date()
if len(list_date) == 3 and len(list_date[2]) == 4:
# self._sel_day_widget.is_selected = False
self.update_calendar(int(list_date[2]), int(list_date[1]))
self.set_month_day(int(list_date[0]))
# self._sel_day_widget.dispatch("on_release")
if self.mode != "range":
self._sel_day_widget.is_selected = False
self._sel_day_widget.dispatch("on_release")
self.sel_day = int(list_date[0])
self.sel_month = int(list_date[1])
self.sel_year = int(list_date[2])
self.update_calendar(self.sel_year, self.sel_month)
elif self.min_date and self.max_date:
list_min_date = self._enter_data_field.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:
# self.compare_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)
def update_text_full_date(self, list_date) -> None:
@ -1140,79 +1266,72 @@ class MDDatePicker(BaseDialogPicker):
)
def update_calendar(self, year, month) -> None:
try:
dates = [x for x in self.calendar.itermonthdates(year, month)]
except ValueError as e:
if str(e) == "year is out of range":
pass
self.year, self.month = year, month
if self.mode == "picker":
selected_date = date(self.sel_year, self.sel_month, self.sel_day)
selected_dates = {selected_date}
else:
self.year = year
self.month = month
for idx in range(len(self._calendar_list)):
self._calendar_list[idx].current_month = int(self.month)
self._calendar_list[idx].current_year = int(self.year)
# Dates of the month not in the range 1-31.
if idx >= len(dates) or dates[idx].month != month:
# self._calendar_list[idx].disabled = True
self._calendar_list[idx].text = ""
# Dates of the month in the range 1-31.
else:
self._calendar_list[idx].disabled = False
self._calendar_list[idx].text = str(dates[idx].day)
self._calendar_list[idx].is_today = dates[idx] == self.today
# The marked date widget has a True value in the `is_selected`
# attribute. In the KV file it is checked if the date widget
# (DatePickerDaySelectableItem) has the `is_selected = False`
# attribute value, then the date widget is not highlighted.
if (
0
if not self._calendar_list[idx].text
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
selected_dates = {self._start_range_date, self._end_range_date}
dates = self.calendar.itermonthdates(year, month)
for widget, widget_date in zip_longest(self._calendar_list, dates):
# Only widgets whose dates are in the displayed month are visible.
visible = (
widget_date is not None
and widget_date.month == month
and widget_date.year == year
)
widget.text = str(widget_date.day) if visible else ""
widget.current_year = year
widget.current_month = month
widget.is_today = visible and widget_date == self.today
widget.is_selected = visible and widget_date in selected_dates
# I don't understand why, but this line is important. Without this
# line, some widgets that we are trying to disable remain enabled.
widget.disabled = False
widget.disabled = (
not visible
or self.mode == "range"
and self._date_range
and widget_date not in self._date_range
)
def get_field(self) -> MDTextField:
"""Creates and returns a text field object used to enter dates."""
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(
owner=self,
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
else:
raise TypeError(
@ -1239,10 +1358,7 @@ class MDDatePicker(BaseDialogPicker):
"set_text_full_date:\n\t" f"Month [{month}] out of range."
)
if int(day) > calendar.monthrange(int(year), (month))[1]:
raise ValueError(
"set_text_full_date:\n\t"
f"Day [{day}] out of range for the month {month}"
)
return ""
date = datetime.date(int(year), int(month), int(day))
separator = (
"\n"
@ -1345,46 +1461,55 @@ class MDDatePicker(BaseDialogPicker):
)
def set_selected_widget(self, widget) -> None:
if self._sel_day_widget:
self._sel_day_widget.is_selected = False
widget.is_selected = True
self.sel_month = int(self.month)
self.sel_year = int(self.year)
self.sel_year = self.year
self.sel_month = self.month
self.sel_day = int(widget.text)
self._current_selected_date = (
self.sel_day,
self.sel_month,
self.sel_year,
)
self._sel_day_widget = widget
self.update_calendar(self.sel_year, self.sel_month)
def set_month_day(self, day) -> None:
for idx in range(len(self._calendar_list)):
if str(day) == str(self._calendar_list[idx].text):
self._sel_day_widget = self._calendar_list[idx]
self.sel_day = int(self._calendar_list[idx].text)
if self._sel_day_widget:
self._sel_day_widget.is_selected = False
self._sel_day_widget = self._calendar_list[idx]
# This method is no longer used. The code bellow repeats the behavior
# that was previously required of it for backward compatibility
# reasons.
self.sel_day = day
self.update_calendar(self.sel_year, self.sel_month)
def set_position_to_current_year(self) -> None:
# TODO: Add the feature to set the position of the list of years
# for the current year. This is not currently possible because the
# ``RecycleView`` class does not support this functionality.
# There is a solution to this problem
# - https://github.com/Bakterija/log_fruit/blob/dev/src/app_modules/widgets/app_recycleview/recycleview.py.
# But I have not been able to get it to work.
pass
year_layout = self.ids._year_layout
# When this method is called for the first time, RecycleView has not
# yet added widgets to the year list, so we use the default height.
widget_height = year_layout.children[0].default_size[1]
cols_amount = year_layout.children[0].cols
rows_amount = math.ceil((self.max_year - self.min_year) / cols_amount)
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:
self.ids._year_layout.data = []
for i, number_year in enumerate(range(self.min_year, self.max_year)):
self.ids._year_layout.data.append(
{
"owner": self,
"text": str(number_year),
"index": i,
"selectable": True,
"viewclass": "DatePickerYearSelectableItem",
}
)
@ -1416,26 +1541,9 @@ class MDDatePicker(BaseDialogPicker):
Called when "chevron-left" and "chevron-right" buttons are pressed.
Switches the calendar to the previous/next month.
"""
operation = 1 if operation == "next" else -1
month = (
12
if self.month + operation == 0
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
)
month_delta = 1 if operation == "next" else -1
year = self.year + (self.month - 1 + month_delta) // 12
month = (self.month - 1 + month_delta) % 12 + 1
if year <= 0:
year, month = 1, 1
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
.. code-block::
.. tabs::
from kivy.lang import Builder
.. tab:: Declarative KV style
from kivymd.app import MDApp
from kivymd.uix.pickers import MDTimePicker
.. code-block:: python
KV = '''
MDFloatLayout:
from kivy.lang import Builder
MDRaisedButton:
text: "Open time picker"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_time_picker()
'''
from kivymd.app import MDApp
from kivymd.uix.pickers import MDTimePicker
KV = '''
MDFloatLayout:
MDRaisedButton:
text: "Open time picker"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_time_picker()
'''
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def show_time_picker(self):
'''Open time picker dialog.'''
def show_time_picker(self):
'''Open time picker dialog.'''
time_dialog = MDTimePicker()
time_dialog.open()
time_dialog = MDTimePicker()
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
:align: center
@ -91,14 +129,14 @@ Use the :attr:`~MDTimePicker.set_time` method of the
.. code-block:: python
time_dialog = MDTimePicker(
primary_color=get_color_from_hex("#72225b"),
accent_color=get_color_from_hex("#5d1a4a"),
text_button_color=(1, 1, 1, 1),
)
MDTimePicker(
primary_color="brown",
accent_color="red",
text_button_color="white",
).open()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png
:align: center
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png
:align: center
"""
__all__ = ("MDTimePicker",)
@ -194,8 +232,8 @@ class TimeInputTextField(MDTextField):
hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$"
minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$"
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Clock.schedule_once(self.set_text)
self.register_event_type("on_select")
self.bind(text_color_focus=self.setter("hint_text_color_normal"))
@ -217,17 +255,20 @@ class TimeInputTextField(MDTextField):
to somehow make them aligned.
"""
if not self.text:
self.text = " "
def set_text(*args):
if not self.text:
self.text = " "
self._refresh_text(self.text)
max_size = max(self._lines_rects, key=lambda r: r.size[0]).size
dx = (self.width - max_size[0]) / 2.0
dy = (self.height - max_size[1]) / 2.0
self.padding = [dx, dy, dx, dy]
self._refresh_text(self.text)
max_size = max(self._lines_rects, key=lambda r: r.size[0]).size
dx = (self.width - max_size[0]) / 2.0
dy = (self.height - max_size[1]) / 2.0
self.padding = [dx, dy, dx, dy]
if len(self.text) > 1:
self.text = self.text.replace(" ", "")
if len(self.text) > 1:
self.text = self.text.replace(" ", "")
Clock.schedule_once(set_text)
def on_focus(self, *args) -> None:
super().on_focus(*args)

View File

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

View File

@ -29,7 +29,7 @@ MDScreen
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 kivymd.uix import MDAdaptiveWidget
@ -44,20 +44,34 @@ class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
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
`MDHeroTo <https://kivymd.readthedocs.io/en/latest/components/hero/>`_
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`
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(
widget.__class__, MDHeroTo
):
@ -65,3 +79,4 @@ class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
f"The `{widget}` widget must be an `kivymd.uix.hero.MDHeroTo` "
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
:class:`~kivymd.uix.screenmanager.MDScreenManager` not
: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.properties import ListProperty, StringProperty
from kivy.uix.screenmanager import ScreenManager
@ -21,17 +36,22 @@ from kivymd.uix.hero import MDHeroFrom
class MDScreenManager(DeclarativeBehavior, ScreenManager):
"""
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`
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`
and :class:`~kivymd.uix.hero.MDHeroTo` objects that will be animated when
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/>`_
module documentation for more information about creating and using Hero
animations.
@ -40,6 +60,17 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager):
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
# screen manager.
_heroes_data = ListProperty()
@ -58,28 +89,48 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager):
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
:attr:`~current_hero` tag.
Get a list of :class:`~kivymd.uix.hero.MDHeroFrom` objects according
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:
if isinstance(hero_widget, MDHeroFrom) or issubclass(
hero_widget.__class__, MDHeroFrom
):
if hero_widget.tag == self.current_hero:
hero_from_widget = hero_widget
break
for name_hero in self.current_heroes:
for hero_widget in self._heroes_data:
if isinstance(hero_widget, MDHeroFrom) or issubclass(
hero_widget.__class__, MDHeroFrom
):
if hero_widget.tag == name_hero:
hero_from_widget.append(hero_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):
super().add_widget(widget, *args, **kwargs)
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 find_hero_widget(child_widget):
widget_hero = None

View File

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

View File

@ -10,58 +10,77 @@ Components/SegmentedControl
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 = '''
MDScreen:
KV = '''
MDScreen:
MDSegmentedControl:
pos_hint: {"center_x": .5, "center_y": .5}
MDSegmentedControl:
pos_hint: {"center_x": .5, "center_y": .5}
MDSegmentedControlItem:
text: "Male"
MDSegmentedControlItem:
text: "Male"
MDSegmentedControlItem:
text: "Female"
MDSegmentedControlItem:
text: "Female"
MDSegmentedControlItem:
text: "All"
'''
MDSegmentedControlItem:
text: "All"
'''
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
class Example(MDApp):
def build(self):
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.uix.screen import MDScreen
from kivymd.uix.segmentedcontrol import MDSegmentedControl, MDSegmentedControlItem
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.segmentedcontrol import (
MDSegmentedControl, MDSegmentedControlItem
)
class Test(MDApp):
def build(self):
screen = MDScreen()
segment_control = MDSegmentedControl(pos_hint={"center_x": .5, "center_y": .5})
segment_control.add_widget(MDSegmentedControlItem(text="Male"))
segment_control.add_widget(MDSegmentedControlItem(text="Female"))
segment_control.add_widget(MDSegmentedControlItem(text="All"))
screen.add_widget(segment_control)
return screen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDSegmentedControl(
MDSegmentedControlItem(
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
:align: center
@ -117,12 +136,22 @@ with open(
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.
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:
`on_active`
Called when the segment is activated.
@ -135,7 +164,7 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
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
:align: center
@ -151,8 +180,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
md_bg_color: "#451938"
segment_color: "#e4514f"
md_bg_color: "brown"
segment_color: "red"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-color.png
:align: center
@ -160,8 +189,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
md_bg_color: "#451938"
segment_color: "#e4514f"
md_bg_color: "brown"
segment_color: "red"
MDSegmentedControlItem:
text: "[color=fff]Male[/color]"
@ -196,9 +225,9 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
.. code-block:: kv
MDSegmentedControl:
md_bg_color: "#451938"
segment_color: "#e4514f"
separator_color: 1, 1, 1, 1
md_bg_color: "brown"
segment_color: "red"
separator_color: "white"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-separator-color.png
:align: center
@ -255,9 +284,6 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
Clock.schedule_once(self.set_default_colors)
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:
"""
@ -313,6 +339,10 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
self.ids.segment_panel.add_widget(widget)
separator = MDSeparator(orientation="vertical")
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(
lambda x: self.update_separator_color(separator)
)
@ -326,15 +356,6 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
self.current_active_segment = 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):
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`
for the :class:`~MDSegmentedControl` class.
"""
children_number = NumericProperty(1)
_started = BooleanProperty(defaultvalue=False)

View File

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

View File

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

View File

@ -3,7 +3,7 @@
#:import colors kivymd.color_definitions.colors
<HintBoxContainer@MDCard+FakeRectangularElevationBehavior>
<HintBoxContainer@MDCard>
<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:
id: hint_box
size_hint: None, None
md_bg_color: root.hint_bg_color
elevation: 0
md_bg_color: root.hint_bg_color if root.hint_bg_color else [0, 0, 0, 0]
elevation: 1.5
opacity: 1 if root.active else 0
radius: root.hint_radius
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:
lbl_value.width + self.padding[0] * 2, \
lbl_value.height + self.padding[0]

View File

@ -82,7 +82,7 @@ class MDSlider(ThemableBehavior, Slider):
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.

View File

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

View File

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

View File

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

View File

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

View File

@ -79,6 +79,10 @@
layout: layout
size_hint: 1, None
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
md_bg_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.behaviors import ToggleButtonBehavior
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.utils import boundary
from kivymd import uix_path
@ -953,11 +952,11 @@ from kivymd.icon_definitions import md_icons
from kivymd.theming import ThemableBehavior, ThemeManager
from kivymd.uix.behaviors import (
DeclarativeBehavior,
FakeRectangularElevationBehavior,
RectangularRippleBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.carousel import MDCarousel
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)
class MDTabsBase(Widget):
class MDTabsBase:
"""
This class allow you to create a tab.
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.
"""
def __init__(self, **kwargs):
def __init__(self, *args, **kwargs):
self.tab_label = MDTabsLabel(tab=self)
super().__init__(**kwargs)
super().__init__(*args, **kwargs)
self.bind(
icon=self._update_text,
title=self._update_text,
@ -1273,9 +1272,7 @@ class MDTabsScrollView(ScrollView):
_update(self.effect_y, scroll_y)
class MDTabsBar(
ThemableBehavior, FakeRectangularElevationBehavior, MDBoxLayout
):
class MDTabsBar(MDCard):
"""
This class is just a boxlayout that contains the scroll view for tabs.
It is also responsible for resizing the tab shortcut when necessary.
@ -1551,13 +1548,43 @@ class MDTabs(
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)
"""
Tab value elevation.
.. seealso::
`Behaviors/Elevation <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/index.html>`_
See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation`
attribute.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.

View File

@ -487,6 +487,8 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
_outer_radius = NumericProperty(0)
_target_radius = NumericProperty(0)
__elevation = 0
def __init__(self, **kwargs):
self.ripple_max_dist = dp(90)
self.on_outer_radius(self, self.outer_radius)
@ -514,6 +516,89 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
if not self.outer_circle_color:
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):
setattr(self.widget, "_outer_radius", 0)
setattr(self.widget, "_target_radius", 0)
@ -527,7 +612,7 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
def _draw_canvas(self):
_pos = self._ttv_pos()
self.widget.canvas.before.clear()
self.widget.canvas.before.remove_group("ttv_group")
with self.widget.canvas.before:
# Outer circle.
@ -588,34 +673,14 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
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):
self.widget.canvas.before.remove_group("ttv_group")
args[0].stop_all(self.widget)
elev = getattr(self.widget, "elevation", None)
if elev:
self._fix_elev()
elevation = getattr(self.widget, "elevation", None)
if elevation:
self.widget.elevation = self.__elevation
self.dispatch("on_close")
# Don't forget to unbind the function or it'll mess
@ -639,16 +704,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
)
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):
anim = Animation(
d=0.2,
@ -684,53 +739,6 @@ class MDTapTargetView(ThemableBehavior, EventDispatcher):
setattr(self.widget, "target_ripple_alpha", 1)
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):
"""
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
======================
.. versionadded:: 1.0.0
.. deprecated:: 1.0.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 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()
.. note:: `RotateWidget` class has been deprecated. Please use
`RotateBahavior <https://kivymd.readthedocs.io/en/latest/behaviors/rotate/>`_
class instead.
"""
__all__ = ("RotateWidget",)
import os
from kivy import Logger
from kivy.lang import Builder
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())
from kivymd.uix.behaviors import RotateBehavior
class RotateWidget:
"""Base class for controlling the rotate of the widget."""
rotate_value_angle = NumericProperty(0)
class RotateWidget(RotateBehavior):
"""
Property for getting/setting the angle of the rotation.
:attr:`rotate_value_angle` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
.. deprecated:: 1.1.0
Use :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior`
class instead.
"""
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.NumericProperty`
and defaults to `(0, 0, 1)`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
Logger.warning(
"KivyMD: "
"The `RotateWidget` class has been deprecated. "
"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
=====================
.. versionadded:: 1.0.0
.. deprecated:: 1.1.0
Base class for controlling the scale of the widget.
.. note:: See `kivy.graphics.Scale
<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 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()
.. note:: `ScaleWidget` class has been deprecated. Please use
`ScaleBehavior <https://kivymd.readthedocs.io/en/latest/behaviors/scale/>`_
class instead.
"""
__all__ = ("ScaleWidget",)
import os
from kivy import Logger
from kivy.lang import Builder
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())
from kivymd.uix.behaviors import ScaleBehavior
class ScaleWidget:
"""Base class for controlling the scale of the widget."""
scale_value_x = NumericProperty(1)
class ScaleWidget(ScaleBehavior):
"""
X-axis value.
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
.. deprecated:: 1.1.0
Use :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior`
class instead.
"""
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`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
Logger.warning(
"KivyMD: "
"The `ScaleWidget` class has been deprecated. "
"Use the `ScaleBehavior` class instead."
)

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
=======================
.. versionadded:: 1.0.0
.. deprecated:: 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.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()
.. note:: `StencilWidget` class has been deprecated. Please use
`StencilBehavior <https://kivymd.readthedocs.io/en/latest/behaviors/stencil/>`_
class instead.
"""
__all__ = ("StencilWidget",)
import os
from kivy import Logger
from kivy.lang import Builder
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())
from kivymd.uix.behaviors import StencilBehavior
class StencilWidget:
"""Base class for controlling the stencil instructions of the widget"""
radius = VariableListProperty([0], length=4)
class StencilWidget(StencilBehavior):
"""
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]`.
.. deprecated:: 1.1.0
Use :class:`~kivymd.uix.behaviors.scale_behavior.StencilBehavior`
class instead.
"""
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>
input_filter: self.field_filter
do_backspace: self.do_backspace
canvas.before:
Clear

View File

@ -14,7 +14,6 @@ Components/TextField
`KivyMD` provides the following field classes for use:
- MDTextField_
- MDTextFieldRound_
- MDTextFieldRect_
.. Note:: :class:`~MDTextField` inherited from
@ -79,15 +78,15 @@ parameter to `True`:
from kivymd.app import MDApp
KV = '''
BoxLayout:
padding: "10dp"
MDScreen:
MDTextField:
id: text_field_error
hint_text: "Helper text on error (press 'Enter')"
helper_text: "There will always be a mistake"
helper_text_mode: "on_error"
pos_hint: {"center_y": .5}
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)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.screen.ids.text_field_error.bind(
on_text_validate=self.set_error_message,
on_focus=self.set_error_message,
@ -119,6 +120,7 @@ Helper text mode `'on_error'` (with required)
MDTextField:
hint_text: "required = True"
text: "required = True"
required: True
helper_text_mode: "on_error"
helper_text: "Enter text"
@ -186,7 +188,7 @@ Round mode
max_text_length: 15
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
.. MDTextFieldRect:
@ -203,6 +205,7 @@ MDTextFieldRect
MDTextFieldRect:
size_hint: 1, None
height: "30dp"
background_color: app.theme_cls.bg_normal
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif
:align: center
@ -278,18 +281,17 @@ __all__ = ("MDTextField", "MDTextFieldRect")
import os
import re
from datetime import date
from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.metrics import dp, sp
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
DictProperty,
ListProperty,
NumericProperty,
ObjectProperty,
@ -311,6 +313,220 @@ with open(
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):
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])
class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
class MDTextField(
DeclarativeBehavior,
ThemableBehavior,
TextInput,
Validator,
AutoFormatTelephoneNumber,
):
helper_text = StringProperty()
"""
Text for ``helper_text`` mode.
@ -430,17 +652,185 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
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 (static underline line) in ``rgba`` format.
Line color normal (static underline line) in (r, g, b, a) or string format.
.. code-block:: kv
MDTextField:
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
: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 (active underline line) in ``rgba`` format.
Line color focus (active underline line) in (r, g, b, a) or string format.
.. code-block:: kv
MDTextField:
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
:align: center
@ -474,7 +864,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
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`
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 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`
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 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`
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 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
@ -522,9 +935,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
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
: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 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
@ -541,7 +955,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
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
:align: center
@ -552,7 +966,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
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
@ -561,7 +976,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
helper_text: "helper_text_color_normal"
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
:align: center
@ -572,7 +987,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
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
@ -581,7 +997,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
helper_text: "helper_text_color_focus"
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
:align: center
@ -592,7 +1008,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
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
@ -601,9 +1018,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
icon_right: "language-python"
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
: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])
"""
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
@ -621,7 +1039,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
icon_right: "language-python"
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
:align: center
@ -632,47 +1050,30 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
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
.. 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`
and defaults to `[0, 0, 0, 0]`.
"""
icon_left_color_focus = ColorProperty([0, 0, 0, 0])
"""
Color of right icon when the text field has focus.
Color in (r, g, b, a) or string format of right icon when the text field
has focus.
.. 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`
and defaults to `[0, 0, 0, 0]`.
"""
max_length_text_color = ColorProperty([0, 0, 0, 0])
"""
Text color of the maximum length of characters to be input.
Text color in (r, g, b, a) or string format of the maximum length of
characters to be input.
.. versionadded:: 1.0.0
@ -680,10 +1081,10 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
hint_text: "max_length_text_color"
max_length_text_color: 0, 1, 0, 1
max_length_text_color: "red"
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
: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 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
@ -726,9 +1127,9 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
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
: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 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
@ -745,7 +1146,7 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
MDTextField:
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
:align: center
@ -879,8 +1280,8 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
text=self.set_text,
)
self.theme_cls.bind(
primary_color=lambda x, y: self.set_default_colors(0, True),
theme_style=lambda x, y: self.set_default_colors(0, True),
primary_color=self.set_default_colors,
theme_style=self.set_default_colors,
)
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:
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:
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._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.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
if (
self.text
@ -1301,22 +1713,34 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
if value_height >= self.max_height and self.max_height:
self.height = self.max_height
def on_text_color_normal(self, instance_text_field, color: list):
def on_text_color_normal(
self, instance_text_field, color: Union[list, str]
):
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
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
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
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
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
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`.
"""
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:
has_error = True
else:
@ -1367,9 +1798,12 @@ class MDTextField(DeclarativeBehavior, ThemableBehavior, TextInput):
if __name__ == "__main__":
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
Window.size = (800, 750)
from kivymd.app import MDApp
KV = """
@ -1385,41 +1819,53 @@ MDScreen:
MDTextField:
hint_text: "Label"
helper_text: "Error massage"
helper_text: "Error message"
mode: "rectangle"
max_text_length: 5
MDTextField:
icon_left: "git"
hint_text: "Label"
helper_text: "Error massage"
helper_text: "Error message"
mode: "rectangle"
MDTextField:
icon_left: "git"
hint_text: "Label"
helper_text: "Error massage"
helper_text: "Error message"
mode: "fill"
MDTextField:
hint_text: "Label"
helper_text: "Error massage"
helper_text: "Error message"
mode: "fill"
MDTextField:
hint_text: "Label"
helper_text: "Error massage"
helper_text: "Error message"
MDTextField:
icon_left: "git"
hint_text: "Label"
helper_text: "Error massage"
helper_text: "Error message"
MDTextField:
hint_text: "Round mode"
mode: "round"
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:
text: "SET TEXT"
@ -1429,6 +1875,8 @@ MDScreen:
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def set_text(self):

View File

@ -121,8 +121,8 @@ Shadow elevation control
.. code-block:: kv
MDTopAppBar:
title: "Elevation 10"
elevation: 10
title: "Elevation 4"
elevation: 4
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png
:align: center
@ -327,7 +327,7 @@ Material design 3 style
:align: center
"""
__all__ = ("MDTopAppBar", "MDBottomAppBar")
__all__ = ("MDTopAppBar", "MDBottomAppBar", "ActionTopAppBarButton")
import os
from math import cos, radians, sin
@ -337,10 +337,8 @@ from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.metrics import dp
from kivy.properties import (
AliasProperty,
BooleanProperty,
ColorProperty,
ListProperty,
@ -356,15 +354,15 @@ from kivymd import uix_path
from kivymd.color_definitions import text_colors
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
CommonElevationBehavior,
DeclarativeBehavior,
FakeRectangularElevationBehavior,
ScaleBehavior,
SpecificBackgroundColorBehavior,
)
from kivymd.uix.button import MDFloatingActionButton, MDIconButton
from kivymd.uix.controllers import WindowController
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.menu import MDDropdownMenu
from kivymd.uix.templates import ScaleWidget
from kivymd.uix.tooltip import MDTooltip
from kivymd.utils.set_bars_colors import set_bars_colors
@ -374,7 +372,7 @@ with open(
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'.
"""
@ -409,11 +407,11 @@ class OverFlowMenuItem(OneLineIconListItem):
class NotchedBox(
ThemableBehavior,
FakeRectangularElevationBehavior,
CommonElevationBehavior,
SpecificBackgroundColorBehavior,
BoxLayout,
):
elevation = NumericProperty(6)
elevation = NumericProperty(4)
notch_radius = NumericProperty()
notch_center_x = NumericProperty("100dp")
@ -961,8 +959,10 @@ class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController):
self.icon_color = self.theme_cls.primary_color
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(primary_palette=self.update_md_bg_color)
self.theme_cls.bind(
material_style=self.update_bar_height,
primary_palette=self.update_md_bg_color,
)
Clock.schedule_once(
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._shift
)
self.shadow_offset = [0, 30]
self.on_mode(None, self.mode)
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)
def on_show(self) -> None:
"""Default dismiss event handler."""
"""Default display event handler."""
def on_dismiss(self) -> None:
"""

View File

@ -35,7 +35,9 @@ __all__ = (
"MDTransitionBase",
)
from kivy import Logger
from kivy.animation import Animation, AnimationTransition
from kivy.properties import DictProperty
from kivy.uix.screenmanager import (
ScreenManagerException,
SlideTransition,
@ -43,13 +45,41 @@ from kivy.uix.screenmanager import (
TransitionBase,
)
from kivymd.uix.hero import MDHeroFrom, MDHeroTo
from kivymd.uix.screenmanager import MDScreenManager
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"
hero_widget = None
hero_from_widget = None # kivymd.uix.hero.MDHeroFrom object
# Collection of child widgets of all 'MDHeroFrom' widgets that are
# 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:
super().start(instance_screen_manager)
@ -59,67 +89,160 @@ class MDTransitionBase(TransitionBase):
]()
def animated_hero_in(self) -> None:
if self.manager._heroes_data and self.manager.current_hero:
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)
"""Animates the flight of heroes from screen **A** to screen **B**."""
self.hero_widget.pos = self.screen_out.to_widget(
*self.hero_from_widget.to_window(*self.hero_from_widget.pos)
)
self.hero_widget.size = self.hero_from_widget.size
self.manager.get_root_window().add_widget(self.hero_widget)
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:
self._check_widget_properties(hero_from_widget)
Animation(
size=self.screen_in.hero_to.size,
d=self.duration,
pos=self.screen_in.hero_to.pos,
).start(self.hero_widget)
self.hero_from_widget.dispatch(
"on_transform_in", self.hero_widget, self.duration
)
# Get child widget of the 'MDHeroFrom' container.
hero_widget = hero_from_widget.children[0]
self._hero_from_widget_children[
hero_from_widget.tag
] = hero_widget
# 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:
if self.manager._heroes_data and self.manager.current_hero:
self.screen_out.hero_to.remove_widget(self.hero_widget)
self.manager.get_root_window().add_widget(self.hero_widget)
"""Animates the flight of heroes from screen **B** to screen **A**."""
self.hero_from_widget.dispatch(
"on_transform_out", self.hero_widget, self.duration
)
Animation(
pos=self.screen_in.to_widget(
*self.hero_from_widget.to_window(*self.hero_from_widget.pos)
),
size=self.hero_from_widget.size,
d=self.duration,
).start(self.hero_widget)
if (
self.manager._heroes_data
and self.manager.current_heroes
and self.screen_out.heroes_to
):
for heroes_tag in self.manager.current_heroes:
for hero_to_widget in self.screen_out.heroes_to:
if hero_to_widget.tag == heroes_tag:
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:
"""
Override method.
See :attr:`kivy.uix.screenmanager.TransitionBase.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":
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:
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):
if not self.screen_in.hero_to:
# Checks the attributes for the 'self.screen_in' screen.
# 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(
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(
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):
pass

View File

@ -36,11 +36,13 @@ MDWidget
__all__ = ("MDWidget",)
from kivy.uix.widget import Widget
from kivymd.uix import MDAdaptiveWidget
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.