Updated build system for Kivy 2.2.1

This commit is contained in:
Mark Qvist 2023-07-10 02:49:58 +02:00
parent 23e6b6e0c6
commit 67a8f61af8
126 changed files with 9967 additions and 4279 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
sbapp/kivymd_working
sbapp/.buildozer
sbapp/requirements.txt
sbapp/venv
@ -29,4 +30,4 @@ build
dist
docs/build
sideband*.egg-info
sbapp*.egg-info
sbapp*.egg-info

View File

@ -30,12 +30,14 @@ patchsdl:
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/src/main/java/org/kivy/android/PythonService.java
injectxml:
# mkdir /home/markqvist/.local/lib/python3.11/site-packages/pythonforandroid/bootstraps/sdl2/build/src/main/xml
# Inject XML on arm64-v8a
mkdir -p .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/src/main/res/xml
mkdir -p .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/templates
cp patches/device_filter.xml .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/src/main/res/xml/
cp patches/file_paths.xml .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/src/main/res/xml/
cp patches/AndroidManifest.tmpl.xml .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/templates/
cp patches/p4a_build.py .buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/sideband/build.py
debug:
buildozer android debug

View File

@ -12,9 +12,10 @@ version.regex = __version__ = ['"](.*)['"]
version.filename = %(source.dir)s/main.py
android.numeric_version = 20230204
requirements = python3==3.9.5,hostpython3==3.9.5,cryptography,cffi,pycparser,kivy==2.1.0,pygments,sdl2,sdl2_ttf==2.0.15,pillow,qrcode==7.3.1,netifaces,libbz2,pydenticon,usb4a,usbserial4a
#requirements = python3==3.9.5,hostpython3==3.9.5,cryptography,cffi,pycparser,kivy==2.2.1,pygments,sdl2,sdl2_ttf==2.0.15,pillow,qrcode==7.3.1,netifaces,libbz2,pydenticon,usb4a,usbserial4a
requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a
p4a.local_recipes = ../Others/python-for-android/pythonforandroid/recipes
requirements.source.kivymd = ../../Others/KivyMD-master
icon.filename = %(source.dir)s/assets/icon.png
presplash.filename = %(source.dir)s/assets/presplash_small.png
@ -27,7 +28,7 @@ fullscreen = 0
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT
android.api = 30
android.minapi = 24
android.ndk = 23b
android.ndk = 25b
android.skip_update = False
android.accept_sdk_license = True
android.release_artifact = apk

View File

@ -26,11 +26,12 @@ import os
import kivy
from kivy.logger import Logger
__version__ = "1.1.0.dev0"
__version__ = "1.2.0.dev0"
"""KivyMD version."""
release = False
kivy.require("2.0.0")
if "READTHEDOCS" not in os.environ:
kivy.require("2.2.0")
try:
from kivymd._version import __date__, __hash__, __short_hash__
@ -49,9 +50,6 @@ 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

@ -54,7 +54,7 @@ from kivymd.theming import ThemeManager
class FpsMonitoring:
"""Implements a monitor to display the current FPS in the toolbar."""
def fps_monitor_start(self) -> None:
def fps_monitor_start(self, anchor: str = "top") -> None:
"""Adds a monitor to the main application window."""
def add_monitor(*args):
@ -62,7 +62,7 @@ class FpsMonitoring:
from kivymd.utils.fpsmonitor import FpsMonitor
monitor = FpsMonitor()
monitor = FpsMonitor(anchor=anchor)
monitor.start()
Window.add_widget(monitor)

View File

@ -1,51 +0,0 @@
/*
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

@ -1,10 +0,0 @@
#ifdef GL_FRAGMENT_PRECISION_HIGH
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

@ -1,10 +0,0 @@
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));
}

View File

@ -164,6 +164,9 @@ class FadingEdgeEffect(ThemableBehavior):
index,
),
)
self.update_canvas(
self, self.size, rectangle_top, rectangle_bottom, i
)
def update_canvas(
self,

View File

@ -5,6 +5,8 @@ Register KivyMD widgets to use without import.
from kivy.factory import Factory
register = Factory.register
register("MDSegmentedButton", module="kivymd.uix.segmentedbutton")
register("MDSegmentedButtonItem", module="kivymd.uix.segmentedbutton")
register("MDScrollView", module="kivymd.uix.scrollview")
register("MDRecycleView", module="kivymd.uix.recycleview")
register("MDResponsiveLayout", module="kivymd.uix.responsivelayout")
@ -37,6 +39,7 @@ register("FitImage", module="kivymd.uix.fitimage")
register("MDBackdrop", module="kivymd.uix.backdrop")
register("MDBanner", module="kivymd.uix.banner")
register("MDTooltip", module="kivymd.uix.tooltip")
register("MDBottomSheet", module="kivymd.uix.bottomsheet")
register("MDBottomNavigation", module="kivymd.uix.bottomnavigation")
register("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation")
register("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior")

View File

@ -12,7 +12,7 @@ Themes/Icon Definitions
List of icons from materialdesignicons.com. These expanded material design
icons are maintained by Austin Andrews (Templarian on Github).
LAST UPDATED: Version 7.0.96
LAST UPDATED: Version 7.1.96
To preview the icons and their names, you can use the following application:
----------------------------------------------------------------------------
@ -242,6 +242,8 @@ md_icons = {
"account-switch-outline": "\U000F04CB",
"account-sync": "\U000F191B",
"account-sync-outline": "\U000F191C",
"account-tag": "\U000F1C1B",
"account-tag-outline": "\U000F1C1C",
"account-tie": "\U000F0CE3",
"account-tie-hat": "\U000F1898",
"account-tie-hat-outline": "\U000F1899",
@ -774,6 +776,7 @@ md_icons = {
"audio-video": "\U000F093D",
"audio-video-off": "\U000F11B6",
"augmented-reality": "\U000F0850",
"aurora": "\U000F1BB9",
"auto-download": "\U000F137E",
"auto-fix": "\U000F0068",
"auto-upload": "\U000F0069",
@ -852,6 +855,8 @@ md_icons = {
"bandage": "\U000F0DAF",
"bank": "\U000F0070",
"bank-check": "\U000F1655",
"bank-circle": "\U000F1C03",
"bank-circle-outline": "\U000F1C04",
"bank-minus": "\U000F0DB0",
"bank-off": "\U000F1656",
"bank-off-outline": "\U000F1657",
@ -1443,6 +1448,8 @@ md_icons = {
"camera-image": "\U000F08CC",
"camera-iris": "\U000F0104",
"camera-lock": "\U000F1A14",
"camera-lock-open": "\U000F1C0D",
"camera-lock-open-outline": "\U000F1C0E",
"camera-lock-outline": "\U000F1A15",
"camera-marker": "\U000F19A7",
"camera-marker-outline": "\U000F19A8",
@ -1707,6 +1714,7 @@ md_icons = {
"chart-multiline": "\U000F08D4",
"chart-multiple": "\U000F1213",
"chart-pie": "\U000F012B",
"chart-pie-outline": "\U000F1BDF",
"chart-ppf": "\U000F1380",
"chart-sankey": "\U000F11DF",
"chart-sankey-variant": "\U000F11E0",
@ -1978,22 +1986,53 @@ md_icons = {
"closed-caption-outline": "\U000F0DBD",
"cloud": "\U000F015F",
"cloud-alert": "\U000F09E0",
"cloud-alert-outline": "\U000F1BE0",
"cloud-arrow-down": "\U000F1BE1",
"cloud-arrow-down-outline": "\U000F1BE2",
"cloud-arrow-left": "\U000F1BE3",
"cloud-arrow-left-outline": "\U000F1BE4",
"cloud-arrow-right": "\U000F1BE5",
"cloud-arrow-right-outline": "\U000F1BE6",
"cloud-arrow-up": "\U000F1BE7",
"cloud-arrow-up-outline": "\U000F1BE8",
"cloud-braces": "\U000F07B5",
"cloud-check": "\U000F0160",
"cloud-check-outline": "\U000F12CC",
"cloud-cancel": "\U000F1BE9",
"cloud-cancel-outline": "\U000F1BEA",
"cloud-check": "\U000F1BEB",
"cloud-check-outline": "\U000F1BEC",
"cloud-check-variant": "\U000F0160",
"cloud-check-variant-outline": "\U000F12CC",
"cloud-circle": "\U000F0161",
"cloud-circle-outline": "\U000F1BED",
"cloud-clock": "\U000F1BEE",
"cloud-clock-outline": "\U000F1BEF",
"cloud-cog": "\U000F1BF0",
"cloud-cog-outline": "\U000F1BF1",
"cloud-download": "\U000F0162",
"cloud-download-outline": "\U000F0B7D",
"cloud-lock": "\U000F11F1",
"cloud-lock-open": "\U000F1BF2",
"cloud-lock-open-outline": "\U000F1BF3",
"cloud-lock-outline": "\U000F11F2",
"cloud-minus": "\U000F1BF4",
"cloud-minus-outline": "\U000F1BF5",
"cloud-off": "\U000F1BF6",
"cloud-off-outline": "\U000F0164",
"cloud-outline": "\U000F0163",
"cloud-percent": "\U000F1A35",
"cloud-percent-outline": "\U000F1A36",
"cloud-plus": "\U000F1BF7",
"cloud-plus-outline": "\U000F1BF8",
"cloud-print": "\U000F0165",
"cloud-print-outline": "\U000F0166",
"cloud-question": "\U000F0A39",
"cloud-refresh": "\U000F052A",
"cloud-question-outline": "\U000F1BF9",
"cloud-refresh": "\U000F1BFA",
"cloud-refresh-outline": "\U000F1BFB",
"cloud-refresh-variant": "\U000F052A",
"cloud-refresh-variant-outline": "\U000F1BFC",
"cloud-remove": "\U000F1BFD",
"cloud-remove-outline": "\U000F1BFE",
"cloud-search": "\U000F0956",
"cloud-search-outline": "\U000F0957",
"cloud-sync": "\U000F063F",
@ -2311,6 +2350,7 @@ md_icons = {
"currency-rub": "\U000F01B1",
"currency-rupee": "\U000F1976",
"currency-sign": "\U000F07BE",
"currency-thb": "\U000F1C05",
"currency-try": "\U000F01B2",
"currency-twd": "\U000F07BF",
"currency-uah": "\U000F1B9B",
@ -2750,6 +2790,10 @@ md_icons = {
"eye-check-outline": "\U000F0D05",
"eye-circle": "\U000F0B94",
"eye-circle-outline": "\U000F0B95",
"eye-lock": "\U000F1C06",
"eye-lock-open": "\U000F1C07",
"eye-lock-open-outline": "\U000F1C08",
"eye-lock-outline": "\U000F1C09",
"eye-minus": "\U000F1026",
"eye-minus-outline": "\U000F1027",
"eye-off": "\U000F0209",
@ -2858,6 +2902,8 @@ md_icons = {
"file-document": "\U000F0219",
"file-document-alert": "\U000F1A97",
"file-document-alert-outline": "\U000F1A98",
"file-document-arrow-right": "\U000F1C0F",
"file-document-arrow-right-outline": "\U000F1C10",
"file-document-check": "\U000F1A99",
"file-document-check-outline": "\U000F1A9A",
"file-document-edit": "\U000F0DC8",
@ -3731,6 +3777,9 @@ md_icons = {
"helicopter": "\U000F0AC2",
"help": "\U000F02D6",
"help-box": "\U000F078B",
"help-box-multiple": "\U000F1C0A",
"help-box-multiple-outline": "\U000F1C0B",
"help-box-outline": "\U000F1C0C",
"help-circle": "\U000F02D7",
"help-circle-outline": "\U000F0625",
"help-network": "\U000F06F5",
@ -3907,6 +3956,7 @@ md_icons = {
"image-filter-center-focus-strong-outline": "\U000F0F00",
"image-filter-center-focus-weak": "\U000F02F2",
"image-filter-drama": "\U000F02F3",
"image-filter-drama-outline": "\U000F1BFF",
"image-filter-frames": "\U000F02F4",
"image-filter-hdr": "\U000F02F5",
"image-filter-none": "\U000F02F6",
@ -4021,6 +4071,7 @@ md_icons = {
"keyboard-backspace": "\U000F030D",
"keyboard-caps": "\U000F030E",
"keyboard-close": "\U000F030F",
"keyboard-close-outline": "\U000F1C00",
"keyboard-esc": "\U000F12B7",
"keyboard-f1": "\U000F12AB",
"keyboard-f10": "\U000F12B4",
@ -4263,6 +4314,12 @@ md_icons = {
"lock-open-variant-outline": "\U000F0FC7",
"lock-outline": "\U000F0341",
"lock-pattern": "\U000F06EA",
"lock-percent": "\U000F1C12",
"lock-percent-open": "\U000F1C13",
"lock-percent-open-outline": "\U000F1C14",
"lock-percent-open-variant": "\U000F1C15",
"lock-percent-open-variant-outline": "\U000F1C16",
"lock-percent-outline": "\U000F1C17",
"lock-plus": "\U000F05FB",
"lock-plus-outline": "\U000F16B2",
"lock-question": "\U000F08EF",
@ -5072,6 +5129,7 @@ md_icons = {
"pencil-remove": "\U000F0DED",
"pencil-remove-outline": "\U000F0DEE",
"pencil-ruler": "\U000F1353",
"pencil-ruler-outline": "\U000F1C11",
"penguin": "\U000F0EC0",
"pentagon": "\U000F0701",
"pentagon-outline": "\U000F0700",
@ -5309,6 +5367,41 @@ md_icons = {
"printer-off-outline": "\U000F1785",
"printer-outline": "\U000F1786",
"printer-pos": "\U000F1057",
"printer-pos-alert": "\U000F1BBC",
"printer-pos-alert-outline": "\U000F1BBD",
"printer-pos-cancel": "\U000F1BBE",
"printer-pos-cancel-outline": "\U000F1BBF",
"printer-pos-check": "\U000F1BC0",
"printer-pos-check-outline": "\U000F1BC1",
"printer-pos-cog": "\U000F1BC2",
"printer-pos-cog-outline": "\U000F1BC3",
"printer-pos-edit": "\U000F1BC4",
"printer-pos-edit-outline": "\U000F1BC5",
"printer-pos-minus": "\U000F1BC6",
"printer-pos-minus-outline": "\U000F1BC7",
"printer-pos-network": "\U000F1BC8",
"printer-pos-network-outline": "\U000F1BC9",
"printer-pos-off": "\U000F1BCA",
"printer-pos-off-outline": "\U000F1BCB",
"printer-pos-outline": "\U000F1BCC",
"printer-pos-pause": "\U000F1BCD",
"printer-pos-pause-outline": "\U000F1BCE",
"printer-pos-play": "\U000F1BCF",
"printer-pos-play-outline": "\U000F1BD0",
"printer-pos-plus": "\U000F1BD1",
"printer-pos-plus-outline": "\U000F1BD2",
"printer-pos-refresh": "\U000F1BD3",
"printer-pos-refresh-outline": "\U000F1BD4",
"printer-pos-remove": "\U000F1BD5",
"printer-pos-remove-outline": "\U000F1BD6",
"printer-pos-star": "\U000F1BD7",
"printer-pos-star-outline": "\U000F1BD8",
"printer-pos-stop": "\U000F1BD9",
"printer-pos-stop-outline": "\U000F1BDA",
"printer-pos-sync": "\U000F1BDB",
"printer-pos-sync-outline": "\U000F1BDC",
"printer-pos-wrench": "\U000F1BDD",
"printer-pos-wrench-outline": "\U000F1BDE",
"printer-search": "\U000F1457",
"printer-settings": "\U000F0707",
"printer-wireless": "\U000F0A0B",
@ -5497,7 +5590,10 @@ md_icons = {
"remote-off": "\U000F0EC4",
"remote-tv": "\U000F0EC5",
"remote-tv-off": "\U000F0EC6",
"rename": "\U000F1C18",
"rename-box": "\U000F0455",
"rename-box-outline": "\U000F1C19",
"rename-outline": "\U000F1C1A",
"reorder-horizontal": "\U000F0688",
"reorder-vertical": "\U000F0689",
"repeat": "\U000F0456",
@ -5566,8 +5662,10 @@ md_icons = {
"robot-outline": "\U000F167A",
"robot-vacuum": "\U000F070D",
"robot-vacuum-alert": "\U000F1B5D",
"robot-vacuum-off": "\U000F1C01",
"robot-vacuum-variant": "\U000F0908",
"robot-vacuum-variant-alert": "\U000F1B5E",
"robot-vacuum-variant-off": "\U000F1C02",
"rocket": "\U000F0463",
"rocket-launch": "\U000F14DE",
"rocket-launch-outline": "\U000F14DF",
@ -6605,6 +6703,8 @@ md_icons = {
"tooltip-outline": "\U000F0526",
"tooltip-plus": "\U000F0BD6",
"tooltip-plus-outline": "\U000F0527",
"tooltip-question": "\U000F1BBA",
"tooltip-question-outline": "\U000F1BBB",
"tooltip-remove": "\U000F1560",
"tooltip-remove-outline": "\U000F1561",
"tooltip-text": "\U000F0528",
@ -7219,3 +7319,94 @@ md_icons = {
"zodiac-virgo": "\U000F0A88",
"blank": " ",
}
if __name__ == "__main__":
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem
Builder.load_string(
"""
#:import images_path kivymd.images_path
<CustomOneLineIconListItem>
IconLeftWidget:
icon: root.icon
<PreviousMDIcons>
MDBoxLayout:
orientation: 'vertical'
spacing: dp(10)
padding: dp(20)
MDBoxLayout:
adaptive_height: True
MDIconButton:
icon: 'magnify'
MDTextField:
id: search_field
hint_text: 'Search icon'
on_text: root.set_list_md_icons(self.text, True)
RecycleView:
id: rv
key_viewclass: 'viewclass'
key_size: 'height'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
"""
)
class CustomOneLineIconListItem(OneLineIconListItem):
icon = StringProperty()
class PreviousMDIcons(Screen):
def set_list_md_icons(self, text="", search=False):
"""Builds a list of icons for the screen MDIcons."""
def add_icon_item(name_icon):
self.ids.rv.data.append(
{
"viewclass": "CustomOneLineIconListItem",
"icon": name_icon,
"text": name_icon,
"callback": lambda x: x,
}
)
self.ids.rv.data = []
for name_icon in md_icons.keys():
if search:
if text in name_icon:
add_icon_item(name_icon)
else:
add_icon_item(name_icon)
class MainApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = PreviousMDIcons()
def build(self):
return self.screen
def on_start(self):
self.screen.set_list_md_icons()
MainApp().run()

View File

@ -35,4 +35,30 @@ else:
PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT
LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT
# Elevation.
SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION = 1
FILE_MANAGER_TOP_APP_BAR_ELEVATION = 1
FLOATING_ACTION_BUTTON_M2_ELEVATION = 1
FLOATING_ACTION_BUTTON_M3_ELEVATION = 0.5
CARD_STYLE_ELEVATED_M3_ELEVATION = 0.5
CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION = 0
DATA_TABLE_ELEVATION = 4
DROP_DOWN_MENU_ELEVATION = 2
TOP_APP_BAR_ELEVATION = 2
SNACK_BAR_ELEVATION = 2
# Shadow softness.
RAISED_BUTTON_SOFTNESS = 4
FLOATING_ACTION_BUTTON_M3_SOFTNESS = 0
DATA_TABLE_SOFTNESS = 12
DROP_DOWN_MENU_SOFTNESS = 6
# Shadow offset.
RAISED_BUTTON_OFFSET = (0, -2)
FLOATING_ACTION_BUTTON_M2_OFFSET = (0, -1)
FLOATING_ACTION_BUTTON_M3_OFFSET = (0, -2)
DATA_TABLE_OFFSET = (0, -2)
DROP_DOWN_MENU_OFFSET = (0, -2)
SNACK_BAR_OFFSET = (0, -2)
TOUCH_TARGET_HEIGHT = dp(48)

View File

@ -1,9 +0,0 @@
from kivy.tests.common import GraphicUnitTest
from kivymd.app import MDApp
class BaseTest(GraphicUnitTest):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.app = MDApp() # NOQA

View File

@ -1,94 +0,0 @@
"""
PyInstaller freezing test
=========================
PyInstaller must package KivyMD apps correctly.
"""
import subprocess
from PyInstaller import __main__ as pyi_main
def test_datas(tmp_path) -> None:
"""Test fonts and images."""
app_name = "userapp"
workpath = tmp_path / "build"
distpath = tmp_path / "dist"
app = tmp_path / (app_name + ".py")
app.write_text(
"""
import os
from kivy.core.text import LabelBase
import kivymd
fonts = os.listdir(kivymd.fonts_path)
print(fonts)
assert "Roboto-Regular.ttf" in fonts
assert "materialdesignicons-webfont.ttf" in fonts
print(LabelBase._fonts.keys())
assert "Roboto" in LabelBase._fonts.keys() # NOQA
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 "transparent.png" in images
"""
)
pyi_main.run(
[
"--workpath",
str(workpath),
"--distpath",
str(distpath),
"--specpath",
str(tmp_path),
str(app),
]
)
subprocess.run([str(distpath / app_name / app_name)], check=True)
def test_widgets(tmp_path) -> None:
"""Test that all widgets are accesible."""
app_name = "userapp"
workpath = tmp_path / "build"
distpath = tmp_path / "dist"
app = tmp_path / (app_name + ".py")
app.write_text(
"""
import os
import kivymd # NOQA
__import__("kivymd.uix.label")
__import__("kivymd.uix.button")
__import__("kivymd.uix.list")
__import__("kivymd.uix.navigationdrawer")
print(os.listdir(os.path.dirname(kivymd.uix.__path__[0])))
"""
)
pyi_main.run(
[
"--workpath",
str(workpath),
"--distpath",
str(distpath),
"--specpath",
str(tmp_path),
str(app),
]
)
subprocess.run([str(distpath / app_name / app_name)], check=True)

View File

@ -1,21 +0,0 @@
from kivy import lang
from kivy.clock import Clock
from kivy.tests.common import GraphicUnitTest
from kivymd.app import MDApp
from kivymd.theming import ThemeManager
class AppTest(GraphicUnitTest):
def test_start_raw_app(self):
lang._delayed_start = None
a = MDApp()
Clock.schedule_once(a.stop, 0.1)
a.run()
def test_theme_manager_existance(self):
lang._delayed_start = None
a = MDApp()
Clock.schedule_once(a.stop, 0.1)
a.run()
assert isinstance(a.theme_cls, ThemeManager)

View File

@ -1,24 +0,0 @@
from kivymd.tests.base_test import BaseTest
class BackdropTest(BaseTest):
def test_backdrop_raw_app(self):
from kivymd.uix.backdrop import MDBackdrop
from kivymd.uix.backdrop.backdrop import (
MDBackdropBackLayer,
MDBackdropFrontLayer,
)
from kivymd.uix.screen import MDScreen
from kivymd.uix.widget import MDWidget
self.render(
MDScreen(
MDBackdrop(
MDBackdropBackLayer(MDWidget()),
MDBackdropFrontLayer(MDWidget()),
id="backdrop",
title="Example Backdrop",
header_text="Menu:",
)
)
)

View File

@ -1,32 +0,0 @@
from kivymd.tests.base_test import BaseTest
class BottomNavigationTest(BaseTest):
def test_bottom_navigation_m3_style_raw_app(self):
from kivymd.uix.bottomnavigation import (
MDBottomNavigation,
MDBottomNavigationItem,
)
from kivymd.uix.screen import MDScreen
self.app.theme_cls.material_style = "M3"
self.render(
MDScreen(
MDBottomNavigation(
MDBottomNavigationItem(
name="screen 1",
text="Mail",
icon="gmail",
),
MDBottomNavigationItem(
name="screen 2",
text="Twitter",
icon="twitter",
badge_icon="numeric-10",
),
panel_color="#eeeaea",
selected_color_background="#97ecf8",
text_color_active="red",
)
)
)

View File

@ -1,25 +0,0 @@
from kivymd.tests.base_test import BaseTest
class CardTest(BaseTest):
def test_card_m3_style_raw_app(self):
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
from kivymd.uix.screen import MDScreen
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
pass
self.app.theme_cls.material_style = "M3"
self.render(
MDScreen(
MD3Card(
size_hint=(None, None),
pos_hint={"center_x": 0.5, "center_y": 0.5},
size=("200dp", "100dp"),
line_color=(0.2, 0.2, 0.2, 0.8),
style="elevated",
md_bg_color="lightblue",
)
)
)

View File

@ -1,16 +0,0 @@
from kivymd.tests.base_test import BaseTest
class ChipTest(BaseTest):
def test_chip_raw_app(self):
from kivymd.uix.chip import MDChip
from kivymd.uix.screen import MDScreen
self.render(
MDScreen(
MDChip(
text="Portland",
pos_hint={"center_x": 0.5, "center_y": 0.5},
)
)
)

View File

@ -1,15 +0,0 @@
def test_create_project():
import os
os.system(
f"python3.10 -m kivymd.tools.patterns.create_project "
f"MVC "
f"{os.path.expanduser('~')} "
f"TestProject "
f"python3.10 "
f"stable "
f"--name_screen TestProjectScreen "
f"--name_database restdb "
f"--use_hotreload yes"
)
assert os.path.exists(os.path.join(os.path.expanduser("~"), "TestProject"))

View File

@ -1,24 +0,0 @@
from kivymd.tests.base_test import BaseTest
class FitImageTest(BaseTest):
def test_fitimage_raw_app(self):
import os
from kivymd import images_path
from kivymd.uix.fitimage import FitImage
from kivymd.uix.screen import MDScreen
self.render(
MDScreen(
FitImage(
source=os.path.join(
images_path, "logo", "kivymd-icon-512.png"
),
size_hint=(0.5, 0.5),
pos_hint={"center_x": 0.5, "center_y": 0.5},
radius=[36, 36, 0, 0],
mipmap=True,
)
)
)

View File

@ -1,16 +0,0 @@
def test_fonts_registration():
# This should register fonts:
from kivy.core.text import LabelBase
import kivymd # NOQA
fonts = [
"Roboto",
"RobotoThin",
"RobotoLight",
"RobotoMedium",
"RobotoBlack",
"Icons",
]
for font in fonts:
assert font in LabelBase._fonts.keys()

View File

@ -1,10 +0,0 @@
def test_icons_have_size():
from kivy.core.text import Label
from kivymd.icon_definitions import md_icons
lbl = Label(font_name="Icons")
for icon_name, icon_value in md_icons.items():
assert len(icon_value) == 1
lbl.refresh()
assert lbl.get_extents(icon_value) is not None

View File

@ -1,39 +0,0 @@
from kivymd.tests.base_test import BaseTest
class ImageListTest(BaseTest):
def test_imagelist_raw_app(self):
import os
from kivymd import images_path
from kivymd.uix.button import MDIconButton
from kivymd.uix.imagelist import MDSmartTile
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
self.render(
MDScreen(
MDSmartTile(
MDIconButton(
icon="heart-outline",
theme_icon_color="Custom",
icon_color="red",
pos_hint={"center_y": 0.5},
),
MDLabel(
text="Julia and Julie",
bold=True,
color="white",
),
radius=24,
box_radius=[0, 0, 24, 24],
box_color="grey",
source=os.path.join(
images_path, "logo", "kivymd-icon-512.png"
),
pos_hint={"center_x": 0.5, "center_y": 0.5},
size_hint=(None, None),
size=("320dp", "320dp"),
)
)
)

View File

@ -1,67 +0,0 @@
from kivymd.tests.base_test import BaseTest
class ListTest(BaseTest):
def test_list_raw_app(self):
import os
from kivymd import images_path
from kivymd.uix.list import (
IconLeftWidget,
IconRightWidget,
ImageLeftWidget,
IRightBodyTouch,
MDList,
OneLineAvatarIconListItem,
OneLineAvatarListItem,
OneLineIconListItem,
OneLineListItem,
ThreeLineListItem,
TwoLineListItem,
)
from kivymd.uix.screen import MDScreen
from kivymd.uix.scrollview import MDScrollView
from kivymd.uix.selectioncontrol import MDCheckbox
class RightCheckbox(IRightBodyTouch, MDCheckbox):
pass
self.render(
MDScreen(
MDScrollView(
MDList(
OneLineListItem(text="Text"),
TwoLineListItem(
text="Text", secondary_text="secondary text"
),
ThreeLineListItem(
text="Text",
secondary_text="secondary text",
tertiary_text="tertiary text",
),
OneLineAvatarListItem(
ImageLeftWidget(
source=os.path.join(
images_path, "logo", "kivymd-icon-512.png"
)
),
text="Text",
),
OneLineIconListItem(
IconLeftWidget(icon="plus"),
text="Text",
),
OneLineAvatarIconListItem(
IconLeftWidget(icon="plus"),
IconRightWidget(icon="minus"),
text="Text",
),
OneLineAvatarIconListItem(
IconLeftWidget(icon="plus"),
RightCheckbox(),
text="Text",
),
)
)
)
)

View File

@ -1,94 +0,0 @@
from kivymd.tests.base_test import BaseTest
class NavigationDrawerTest(BaseTest):
def test_navigationdrawer_raw_app(self):
from kivymd.uix.navigationdrawer import (
MDNavigationDrawer,
MDNavigationDrawerDivider,
MDNavigationDrawerHeader,
MDNavigationDrawerItem,
MDNavigationDrawerLabel,
MDNavigationDrawerMenu,
MDNavigationLayout,
)
from kivymd.uix.screen import MDScreen
from kivymd.uix.screenmanager import MDScreenManager
from kivymd.uix.toolbar import MDTopAppBar
class DrawerClickableItem(MDNavigationDrawerItem):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.focus_color = "#e7e4c0"
self.unfocus_color = "#f7f4e7"
self.text_color = "#4a4939"
self.icon_color = "#4a4939"
self.ripple_color = "#c5bdd2"
self.selected_color = "#0c6c4d"
class DrawerLabelItem(MDNavigationDrawerItem):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bg_color = "#f7f4e7"
self.text_color = "#4a4939"
self.icon_color = "#4a4939"
_no_ripple_effect = True # NOQA
self.app.theme_cls.material_style = "M3"
self.render(
MDNavigationLayout(
MDScreenManager(
MDScreen(
MDTopAppBar(
title="Navigation Drawer",
elevation=10,
pos_hint={"top": 1},
md_bg_color="#e7e4c0",
specific_text_color="#4a4939",
left_action_items=[
["menu", lambda x: self.nav_drawer_open()]
],
)
)
),
MDNavigationDrawer(
MDNavigationDrawerMenu(
MDNavigationDrawerHeader(
title="Header title",
title_color="#4a4939",
text="Header text",
spacing="4dp",
padding=("12dp", 0, 0, "56dp"),
),
MDNavigationDrawerLabel(
text="Mail",
),
DrawerClickableItem(
icon="gmail",
right_text="+99",
text_right_color="#4a4939",
text="Inbox",
radius=24,
),
DrawerClickableItem(
icon="send",
text="Outbox",
radius=24,
),
MDNavigationDrawerDivider(),
MDNavigationDrawerLabel(
text="Labels",
),
DrawerLabelItem(
icon="information-outline",
text="Label",
),
DrawerLabelItem(
icon="information-outline",
text="Label",
),
),
id="nav_drawer",
),
)
)

View File

@ -1,14 +0,0 @@
from kivymd.tests.base_test import BaseTest
class TabTest(BaseTest):
def test_tab_raw_app(self):
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.tab import MDTabs, MDTabsBase
class Tab(MDFloatLayout, MDTabsBase):
pass
tab = MDTabs()
tab.add_widget(Tab(title="Tab"))
self.render(tab)

View File

@ -1,72 +0,0 @@
# from kivy.clock import Clock
# from kivy.uix.textinput import TextInput
from kivymd.tests.base_test import BaseTest
class TextFieldTest(BaseTest):
def test_textfield_raw_app(self):
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFlatButton
from kivymd.uix.screen import MDScreen
from kivymd.uix.textfield import MDTextField
# def set_text():
# for widget in self.screen.ids.box.children:
# if issubclass(widget.__class__, TextInput):
# widget.text = "Input text"
self.render(
MDScreen(
MDBoxLayout(
MDTextField(
hint_text="Label",
helper_text="Error massage",
mode="rectangle",
max_text_length=5,
),
MDTextField(
icon_left="git",
hint_text="Label",
helper_text="Error massage",
mode="rectangle",
),
MDTextField(
icon_left="git",
hint_text="Label",
helper_text="Error massage",
mode="fill",
),
MDTextField(
hint_text="Label",
helper_text="Error massage",
mode="fill",
),
MDTextField(
hint_text="Label",
helper_text="Error massage",
),
MDTextField(
icon_left="git",
hint_text="Label",
helper_text="Error massage",
),
MDTextField(
hint_text="Round mode",
mode="round",
max_text_length=15,
helper_text="Massage",
),
MDFlatButton(
text="SET TEXT",
pos_hint={"center_x": 0.5},
),
id="box",
orientation="vertical",
spacing="20dp",
adaptive_height=True,
size_hint_x=0.8,
pos_hint={"center_x": 0.5, "center_y": 0.5},
)
)
)

View File

@ -606,13 +606,16 @@ class ThemeManager(EventDispatcher):
readonly.
"""
material_style = OptionProperty("M2", options=["M2", "M3"])
material_style = OptionProperty("M3", options=["M2", "M3"])
"""
Material design style.
Available options are: 'M2', 'M3'.
.. versionadded:: 1.0.0
.. versionchanged:: 1.2.0
By default now `'M3'`.
.. seealso::
`Material Design 2 <https://material.io/>`_ and
@ -620,7 +623,7 @@ class ThemeManager(EventDispatcher):
:attr:`material_style` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'M2'`.
and defaults to `'M3'`.
"""
theme_style_switch_animation = BooleanProperty(False)
@ -647,9 +650,8 @@ class ThemeManager(EventDispatcher):
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
elevation: 2
shadow_offset: 0, -2
MDLabel:
text: "Theme style - {}".format(app.theme_cls.theme_style)
@ -720,9 +722,8 @@ class ThemeManager(EventDispatcher):
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),
elevation=2,
shadow_offset=(0, -2),
)
)
)
@ -1665,25 +1666,60 @@ class ThemableBehavior(EventDispatcher):
"https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions"
)
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")
# Fix circular imports.
from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.label import MDLabel
from kivymd.uix.textfield import MDTextField
# 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
self.common_elevation_behavior = CommonElevationBehavior
self.md_label = MDLabel
self.md_textfield = MDTextField
# super().dec_disabled(*args, **kwargs)
def remove_widget(self, widget) -> None:
if not hasattr(widget, "theme_cls"):
super().remove_widget(widget)
return
callbacks = widget.theme_cls.get_property_observers("theme_style")
for callback in callbacks:
try:
if hasattr(callback, "proxy") and hasattr(
callback.proxy, "theme_cls"
):
if issubclass(widget.__class__, self.md_textfield):
widget.theme_cls.unbind(
**{
"theme_style": getattr(
callback.proxy, callback.method_name
)
}
)
for property_name in self.unbind_properties:
if widget == callback.proxy:
widget.theme_cls.unbind(
**{
property_name: getattr(
callback.proxy, callback.method_name
)
}
)
# KivyMD widgets may contain other MD widgets.
for children in widget.children:
if hasattr(children, "theme_cls"):
self.remove_widget(children)
except ReferenceError:
pass
# Canceling a scheduled method call on_window_touch for MDLabel
# objects.
if (
issubclass(widget.__class__, self.md_label)
and self.md_label.allow_selection
):
Window.unbind(on_touch_down=widget.on_window_touch)
super().remove_widget(widget)

View File

@ -82,7 +82,7 @@ class Toast(BaseDialog):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.label_toast = Label(size_hint=(None, None), opacity=0)
self.label_toast = Label(size_hint=(None, None), markup=True, opacity=0)
self.label_toast.bind(texture_size=self.label_check_texture_size)
self.add_widget(self.label_toast)

View File

@ -13,18 +13,6 @@ 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

@ -381,13 +381,12 @@ class {name_screen}Controller:
temp_base_screen = '''from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.theming import ThemableBehavior
from kivymd.uix.screen import MDScreen
from Utility.observer import Observer
class BaseScreenView(ThemableBehavior, MDScreen, Observer):
class BaseScreenView(MDScreen, Observer):
"""
A base class that implements a visual representation of the model data.
The view class must be inherited from this class.

View File

@ -59,6 +59,8 @@ class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
else:
if not isinstance(self, (FloatLayout, Screen)):
self.bind(minimum_height=self.setter("height"))
if not self.children:
self.height = 0
def on_adaptive_width(self, md_widget, value: bool) -> None:
self.size_hint_x = None
@ -71,6 +73,8 @@ class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
else:
if not isinstance(self, (FloatLayout, Screen)):
self.bind(minimum_width=self.setter("width"))
if not self.children:
self.width = 0
def on_adaptive_size(self, md_widget, value: bool) -> None:
self.size_hint = (None, None)
@ -84,3 +88,5 @@ class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
else:
if not isinstance(self, (FloatLayout, Screen)):
self.bind(minimum_size=self.setter("size"))
if not self.children:
self.size = (0, 0)

View File

@ -33,11 +33,14 @@ __all__ = ("MDAnchorLayout",)
from kivy.uix.anchorlayout import AnchorLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDAnchorLayout(DeclarativeBehavior, AnchorLayout, MDAdaptiveWidget):
class MDAnchorLayout(
DeclarativeBehavior, ThemableBehavior, AnchorLayout, MDAdaptiveWidget
):
"""
Anchor layout class. For more information, see in the
:class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation.

View File

@ -201,7 +201,6 @@ from kivy.properties import (
from kivy.uix.boxlayout import BoxLayout
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.floatlayout import MDFloatLayout
@ -214,8 +213,11 @@ with open(
Builder.load_string(kv_file.read())
class MDBackdrop(MDFloatLayout, ThemableBehavior):
class MDBackdrop(MDFloatLayout):
"""
For more information, see in the
:class:`~kivymd.uix.floatlayout.MDFloatLayout` class documentation.
:Events:
:attr:`on_open`
When the front layer drops.
@ -277,7 +279,7 @@ class MDBackdrop(MDFloatLayout, ThemableBehavior):
back_layer_color = ColorProperty(None)
"""
Background color of back layer.
Background color of back layer in (r, g, b, a) or string format.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-back-layer-color.png
:align: center
@ -288,7 +290,7 @@ class MDBackdrop(MDFloatLayout, ThemableBehavior):
front_layer_color = ColorProperty(None)
"""
Background color of front layer.
Background color of front layer in (r, g, b, a) or string format.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-front-layer-color.png
:align: center
@ -512,15 +514,30 @@ class MDBackdrop(MDFloatLayout, ThemableBehavior):
class MDBackdropToolbar(MDTopAppBar):
"""Implements a toolbar for back content."""
"""
Implements a toolbar for back content.
For more information, see in the
:class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar` classes documentation.
"""
class MDBackdropFrontLayer(MDBoxLayout):
"""Container for front content."""
"""
Container for front content.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` classes documentation.
"""
class MDBackdropBackLayer(MDBoxLayout):
"""Container for back content."""
"""
Container for back content.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
"""
class _BackLayer(BoxLayout):

View File

@ -177,6 +177,13 @@ with open(
class MDBanner(MDCard):
"""
Banner class.
For more information, see in the :class:`~kivymd.uix.card.MDCard`
class documentation.
"""
vertical_pad = NumericProperty(dp(68))
"""
Indent the banner at the top of the screen.

View File

@ -20,6 +20,11 @@ from .elevation import (
RectangularElevationBehavior,
RoundedRectangularElevationBehavior,
)
from .motion_behavior import (
MotionDialogBehavior,
MotionShackBehavior,
MotionDropDownMenuBehavior,
)
from .magic_behavior import MagicBehavior
from .ripple_behavior import CircularRippleBehavior, RectangularRippleBehavior
from .rotate_behavior import RotateBehavior

View File

@ -5,9 +5,9 @@ Behaviors/Background Color
.. note:: The following classes are intended for in-house use of the library.
"""
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
from __future__ import annotations
from typing import List, Union
__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
from kivy.animation import Animation
from kivy.lang import Builder
@ -49,6 +49,8 @@ Builder.load_string(
source: root.background
Color:
rgba: self.line_color if self.line_color else (0, 0, 0, 0)
# TODO: maybe we should use SmoothLine,
# but this should be tested on all widgets.
Line:
width: root.line_width
rounded_rectangle:
@ -58,7 +60,6 @@ Builder.load_string(
self.width, \
self.height, \
*self.radius, \
100, \
]
PopMatrix
""",
@ -90,6 +91,8 @@ class BackgroundColorBehavior:
and defaults to `[0, 0, 0, 0]`.
"""
# FIXME: in this case, we will not be able to animate this property
# using the `Animation` class.
md_bg_color = ColorProperty([1, 1, 1, 0])
"""
The background color of the widget (:class:`~kivy.uix.widget.Widget`)
@ -154,12 +157,34 @@ class BackgroundColorBehavior:
_background_y = NumericProperty(0)
_background_origin = ReferenceListProperty(_background_x, _background_y)
_md_bg_color = ColorProperty([0, 0, 0, 0])
_origin_line_color = ColorProperty(None)
_origin_md_bg_color = ColorProperty(None)
def __init__(self, **kwarg):
super().__init__(**kwarg)
self.bind(pos=self.update_background_origin)
self.bind(
pos=self.update_background_origin,
disabled=self.restore_color_origin,
)
def restore_color_origin(self, instance_md_widget, value: bool) -> None:
"""Called when the values of :attr:`disabled` change."""
if not value:
if self._origin_line_color:
self.line_color = self._origin_line_color
if self._origin_md_bg_color:
self.md_bg_color = self._origin_md_bg_color
def on_line_color(self, instance_md_widget, value: list | str) -> None:
"""Called when the values of :attr:`line_color` change."""
if not self.disabled:
self._origin_line_color = value
def on_md_bg_color(self, instance_md_widget, color: list | str):
"""Called when the values of :attr:`md_bg_color` change."""
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
@ -172,9 +197,12 @@ class BackgroundColorBehavior:
else:
self._md_bg_color = color
def update_background_origin(
self, instance_md_widget, pos: List[float]
) -> None:
if not self.disabled:
self._origin_md_bg_color = color
def update_background_origin(self, instance_md_widget, pos: list) -> None:
"""Called when the values of :attr:`pos` change."""
if self.background_origin:
self._background_origin = self.background_origin
else:

View File

@ -41,8 +41,9 @@ For example, let's create a button with a rectangular elevation effect:
# With elevation effect
RectangularElevationButton:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 4.5
shadow_offset: 0, 6
elevation: 4
shadow_offset: 0, -6
shadow_softness: 4
# Without elevation effect
RectangularElevationButton:
@ -102,8 +103,9 @@ For example, let's create a button with a rectangular elevation effect:
MDScreen(
RectangularElevationButton(
pos_hint={"center_x": .5, "center_y": .6},
elevation=4.5,
shadow_offset=(0, 6),
elevation=4,
shadow_softness=4,
shadow_offset=(0, -6),
),
RectangularElevationButton(
pos_hint={"center_x": .5, "center_y": .4},
@ -164,6 +166,7 @@ Similarly, create a circular button:
CircularElevationButton:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 4
shadow_softness: 4
'''
@ -231,6 +234,7 @@ Similarly, create a circular button:
CircularElevationButton(
pos_hint={"center_x": .5, "center_y": .5},
elevation=4,
shadow_softness=4,
)
)
)
@ -266,7 +270,7 @@ Animating the elevation
size_hint: None, None
size: 100, 100
md_bg_color: 0, 0, 1, 1
elevation: 4
elevation: 2
radius: 18
'''
@ -336,7 +340,7 @@ Animating the elevation
size_hint=(None, None),
size=(100, 100),
md_bg_color="blue",
elevation=4,
elevation=2,
radius=18,
)
)
@ -360,32 +364,62 @@ __all__ = (
"FakeCircularElevationBehavior",
)
import os
from kivy import Logger
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.graphics import RenderContext, RoundedRectangle
from kivy.lang import Builder
from kivy.properties import (
AliasProperty,
BooleanProperty,
BoundedNumericProperty,
ColorProperty,
ListProperty,
NumericProperty,
ObjectProperty,
VariableListProperty,
)
from kivy.uix.widget import Widget
from kivymd import glsl_path
from kivymd.app import MDApp
Builder.load_string(
"""
<CommonElevationBehavior>
canvas.before:
PushMatrix
Scale:
x: self.scale_value_x
y: self.scale_value_y
z: self.scale_value_x
origin:
self.center \
if not self.scale_value_center else \
self.scale_value_center
Rotate:
angle: self.rotate_value_angle
axis: tuple(self.rotate_value_axis)
origin: self.center
Color:
rgba:
(0, 0, 0, 0) \
if self.disabled or not self.elevation else \
root.shadow_color
BoxShadow:
pos: self.pos
size: self.size
offset: root.shadow_offset
spread_radius: -(root.shadow_softness), -(root.shadow_softness)
blur_radius: root.elevation * 10
border_radius:
(root.radius if hasattr(self, "radius") else [0, 0, 0, 0]) \
if root.shadow_radius == [0.0, 0.0, 0.0, 0.0] else \
root.shadow_radius
canvas.after:
PopMatrix
"""
)
# FIXME: Add shadow manipulation with canvas instructions such as
# PushMatrix and PopMatrix.
class CommonElevationBehavior(Widget):
"""Common base class for rectangular and circular elevation behavior."""
"""
Common base class for rectangular and circular elevation behavior.
For more information, see in the :class:`~kivy.uix.widget.Widget`
class documentation.
"""
elevation = BoundedNumericProperty(0, min=0, errorvalue=0)
"""
@ -418,9 +452,9 @@ class CommonElevationBehavior(Widget):
radius: 12, 46, 12, 46
size_hint: .5, .3
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 4
shadow_softness: 8
shadow_offset: (-2, 2)
elevation: 2
shadow_softness: 4
shadow_offset: (2, -2)
'''
@ -434,21 +468,11 @@ class CommonElevationBehavior(Widget):
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-radius.png
:align: center
.. note::
However, if you want to use this parameter, remember that the angle
values for the radius of the Kivy widgets and the radius for the shader
are different.
.. code-block:: python
shadow_radius = ['top-right', 'bot-right', 'top-left', 'bot-left']
kivy_radius = ['top-left', 'top-right', 'bottom-right', 'bottom-left']
:attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[0, 0, 0, 0]`.
"""
shadow_softness = NumericProperty(12)
shadow_softness = NumericProperty(0.0)
"""
Softness of the shadow.
@ -482,7 +506,9 @@ class CommonElevationBehavior(Widget):
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
md_bg_color = [0, 0, 1, 1]
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = "blue"
class Example(MDApp):
@ -499,7 +525,19 @@ class CommonElevationBehavior(Widget):
and defaults to `12`.
"""
shadow_offset = ListProperty((0, 2))
shadow_softness_size = BoundedNumericProperty(2, min=2, deprecated=True)
"""
The value of the softness of the shadow.
.. versionadded:: 1.1.0
.. deprecated:: 1.2.0
:attr:`shadow_softness_size` is an :class:`~kivy.properties.NumericProperty`
and defaults to `2`.
"""
shadow_offset = ListProperty((0, 0))
"""
Offset of the shadow.
@ -523,14 +561,16 @@ class CommonElevationBehavior(Widget):
RectangularElevationButton:
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 6
shadow_radius: 18
shadow_softness: 24
shadow_offset: 12, 12
shadow_radius: 6
shadow_softness: 12
shadow_offset: -12, -12
'''
class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
md_bg_color = [0, 0, 1, 1]
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = "blue"
class Example(MDApp):
@ -546,7 +586,7 @@ class CommonElevationBehavior(Widget):
.. code-block:: kv
RectangularElevationButton:
shadow_offset: -12, 12
shadow_offset: 12, -12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png
:align: center
@ -554,7 +594,7 @@ class CommonElevationBehavior(Widget):
.. code-block:: kv
RectangularElevationButton:
shadow_offset: -12, -12
shadow_offset: 12, 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png
:align: center
@ -562,13 +602,13 @@ class CommonElevationBehavior(Widget):
.. code-block:: kv
RectangularElevationButton:
shadow_offset: 12, -12
shadow_offset: -12, 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png
:align: center
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
and defaults to `(0, 2)`.
and defaults to `(0, 0)`.
"""
shadow_color = ColorProperty([0, 0, 0, 0.6])
@ -586,252 +626,75 @@ class CommonElevationBehavior(Widget):
:align: center
:attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0.4, 0.4, 0.4, 0.8]`.
and defaults to `[0, 0, 0, 0.6]`.
"""
scale_value_x = NumericProperty(1)
"""
X-axis value.
.. versionadded:: 1.2.0
:attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
scale_value_y = NumericProperty(1)
"""
Y-axis value.
.. versionadded:: 1.2.0
:attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
scale_value_z = NumericProperty(1)
"""
Z-axis value.
.. versionadded:: 1.2.0
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
scale_value_center = ListProperty()
"""
Origin of the scale.
.. versionadded:: 1.2.0
The format of the origin can be either (x, y) or (x, y, z).
:attr:`scale_value_center` is an :class:`~kivy.properties.NumericProperty`
and defaults to `[]`.
"""
rotate_value_angle = NumericProperty(0)
"""
Property for getting/setting the angle of the rotation.
.. versionadded:: 1.2.0
: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.
.. versionadded:: 1.2.0
:attr:`rotate_value_axis` is an :class:`~kivy.properties.ListProperty`
and defaults to `(0, 0, 1)`.
"""
_transition_ref = ObjectProperty()
_has_relative_position = BooleanProperty(defaultvalue=False)
_elevation = 0
_shadow_color = [0.0, 0.0, 0.0, 0.0]
def _get_window_pos(self, *args):
window_pos = self.to_window(*self.pos)
# To list, so it can be compared to self.pos directly.
return [window_pos[0], window_pos[1]]
def _set_window_pos(self, value):
self.window_pos = value
window_pos = AliasProperty(_get_window_pos, _set_window_pos)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if hasattr(MDApp.get_running_app(), "shaders_disabled") and MDApp.get_running_app().shaders_disabled:
self.shaders_disabled = True
else:
self.shaders_disabled = False
with self.canvas.before:
self.context = RenderContext(use_parent_projection=True)
with self.context:
if self.shaders_disabled:
self.rect = None
del self.rect
else:
self.rect = RoundedRectangle(pos=self.pos, size=self.size)
self.after_init()
def after_init(self, *args):
Clock.schedule_once(self.check_for_relative_behavior)
if not self.shaders_disabled:
Clock.schedule_once(self.set_shader_string)
Clock.schedule_once(lambda x: self.on_elevation(self, self.elevation))
self.on_pos()
def check_for_relative_behavior(self, *args) -> None:
"""
Checks if the widget has relative properties and if necessary
binds Window.on_draw and screen events to fix behavior
"""
if self.pos != self.window_pos:
self._has_relative_position = True
# Loops to check if its inside screenmanager or bottom_navigation.
widget = self
while True:
# Checks if has screen event function
# works for Screen and MDTab objects.
if hasattr(widget, "on_pre_enter"):
widget.bind(on_pre_enter=self.apply_correction)
widget.bind(on_pre_leave=self.apply_correction)
widget.bind(on_enter=self.reset_correction)
widget.bind(on_leave=self.reset_correction)
self._has_relative_position = True
# Save refs to objects with transition property.
if hasattr(widget, "header"): # specific to bottom_nav
self._transition_ref = widget.header.panel
elif hasattr(widget, "manager"): # specific to screen
if widget.manager: # manager cant be None
self._transition_ref = widget.manager
break
elif widget.parent and str(widget) != str(widget.parent):
widget = widget.parent
else:
break
if self._has_relative_position:
Window.bind(on_draw=self.update_window_position)
def apply_correction(self, *args):
if self._transition_ref:
transition = str(self._transition_ref.transition)
# Slide and Card transitions only need _has_relative_pos to be
# always on.
if (
"SlideTransition" in transition
or "CardTransition" in transition
):
self.context.use_parent_modelview = False
else:
self.context.use_parent_modelview = True
def reset_correction(self, *args):
self.context.use_parent_modelview = False
self.update_window_position()
def get_shader_string(self) -> str:
shader_string = ""
for name_file in ["header.frag", "elevation.frag", "main.frag"]:
with open(
os.path.join(glsl_path, "elevation", name_file),
encoding="utf-8",
) as file:
shader_string += f"{file.read()}\n\n"
return shader_string
def set_shader_string(self, *args) -> None:
self.context["shadow_radius"] = list(map(float, self.shadow_radius))
self.context["shadow_softness"] = float(self.shadow_softness)
self.context["shadow_color"] = list(map(float, self.shadow_color))[
:-1
] + [float(self.opacity)]
self.context["pos"] = list(map(float, self.rect.pos))
self.context.shader.fs = self.get_shader_string()
def update_resolution(self) -> None:
self.context["resolution"] = (*self.rect.size, *self.rect.pos)
def on_shadow_color(self, instance, value) -> None:
def on_shadow_color(*args):
self._shadow_color = list(map(float, value))[:-1] + [
float(self.opacity) if not self.disabled else 0
]
self.context["shadow_color"] = self._shadow_color
Clock.schedule_once(on_shadow_color)
def on_shadow_radius(self, instance, value) -> None:
def on_shadow_radius(*args):
if hasattr(self, "context"):
self.context["shadow_radius"] = list(map(float, value))
Clock.schedule_once(on_shadow_radius)
def on_shadow_softness(self, instance, value) -> None:
def on_shadow_softness(*args):
if hasattr(self, "context"):
self.context["shadow_softness"] = float(value)
Clock.schedule_once(on_shadow_softness)
def on_elevation(self, instance, value) -> None:
def on_elevation(*args):
if hasattr(self, "context"):
self._elevation = value
self.hide_elevation(
True if (value <= 0 or self.disabled) else False
)
Clock.schedule_once(on_elevation)
def on_shadow_offset(self, instance, value) -> None:
self.on_size()
self.on_pos()
def update_window_position(self, *args) -> None:
"""
This function is used only when the widget has relative position
properties.
"""
self.on_pos()
def on_pos(self, *args) -> None:
if not hasattr(self, "rect"):
return
if (
self._has_relative_position
and not self.context.use_parent_modelview
):
pos = self.window_pos
else:
pos = self.pos
self.rect.pos = [
pos[0]
- ((self.rect.size[0] - self.width) / 2)
- self.shadow_offset[0],
pos[1]
- ((self.rect.size[1] - self.height) / 2)
- self.shadow_offset[1],
]
self.context["mouse"] = [self.rect.pos[0], 0.0, 0.0, 0.0]
self.context["pos"] = list(map(float, self.rect.pos))
self.update_resolution()
def on_size(self, *args) -> None:
if not hasattr(self, "rect"):
return
# If the elevation value is 0, set the canvas size to zero.
# Because even with a zero elevation value, the shadow is displayed
# under the widget. This is visible if we change the scale
# of the widget.
width = self.size[0] if self.elevation else 0
height = self.size[1] if self.elevation else 0
self.rect.size = (
width + (self._elevation * self.shadow_softness / 2),
height + (self._elevation * self.shadow_softness / 2),
)
self.context["mouse"] = [self.rect.pos[0], 0.0, 0.0, 0.0]
self.context["size"] = list(map(float, self.rect.size))
self.update_resolution()
def on_opacity(self, instance, value: int | float) -> None:
"""
Adjusts the transparency of the shadow according to the transparency
of the widget.
"""
def on_opacity(*args):
self._shadow_color = list(map(float, self._shadow_color))[:-1] + [
float(value)
]
self.context["shadow_color"] = self._shadow_color
super().on_opacity(instance, value)
Clock.schedule_once(on_opacity)
def on_radius(self, instance, value) -> None:
self.shadow_radius = [value[1], value[2], value[0], value[3]]
def on_disabled(self, instance, value) -> None:
if value:
self._elevation = 0
self.hide_elevation(True)
else:
self.hide_elevation(False)
def hide_elevation(self, hide: bool) -> None:
if hide:
self._elevation = -self.elevation
self._shadow_color = [0.0, 0.0, 0.0, 0.0]
else:
self._elevation = self.elevation
self._shadow_color = self.shadow_color[:-1] + [float(self.opacity)]
self.on_shadow_color(self, self._shadow_color)
self.on_size()
self.on_pos()
self._elevation = value
class RectangularElevationBehavior(CommonElevationBehavior):

View File

@ -15,8 +15,9 @@ Usage
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.behaviors import RectangularElevationBehavior, FocusBehavior
from kivymd.uix.behaviors import RectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
KV = '''
MDScreen:
@ -72,6 +73,18 @@ from kivymd.uix.behaviors import HoverBehavior
class FocusBehavior(HoverBehavior, ButtonBehavior):
"""
Focus behavior class.
For more information, see in the :class:`~kivymd.uix.behavior.HoverBehavior`
and :class:`~kivy.uix.button.ButtonBehavior` classes documentation.
:Events:
:attr:`on_enter`
Called when mouse enters the bbox of the widget AND the widget is visible
:attr:`on_leave`
Called when the mouse exits the widget AND the widget is visible
"""
focus_behavior = BooleanProperty(True)
"""

View File

@ -11,13 +11,13 @@ In `KV file`:
.. code-block:: kv
<HoverItem@MDBoxLayout+ThemableBehavior+HoverBehavior>
<HoverItem@MDBoxLayout+HoverBehavior>
In `python file`:
.. code-block:: python
class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior):
class HoverItem(MDBoxLayout, HoverBehavior):
'''Custom item implementing hover behavior.'''
After creating a class, you must define two methods for it:
@ -38,7 +38,6 @@ the widget.
from kivymd.app import MDApp
from kivymd.uix.behaviors import HoverBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.theming import ThemableBehavior
KV = '''
Screen
@ -51,7 +50,7 @@ the widget.
'''
class HoverItem(MDBoxLayout, ThemableBehavior, HoverBehavior):
class HoverItem(MDBoxLayout, HoverBehavior):
'''Custom item implementing hover behavior.'''
def on_enter(self, *args):

View File

@ -118,7 +118,6 @@ Builder.load_string(
class MagicBehavior:
magic_speed = NumericProperty(1)
"""
Animation playback speed.

View File

@ -0,0 +1,287 @@
"""
Behaviors/Motion
================
.. rubric:: Use motion to make a UI expressive and easy to use.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/motion.png
:align: center
.. versionadded:: 1.2.0
Classes of the `Motion` type implement the display behavior of widgets such
as dialogs, dropdown menu, snack bars, and so on.
"""
__all__ = (
"MotionBase",
"MotionDropDownMenuBehavior",
"MotionDialogBehavior",
"MotionShackBehavior",
)
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.properties import StringProperty, NumericProperty
from kivymd.uix.behaviors.stencil_behavior import StencilBehavior
class MotionBase:
"""Base class for widget display movement behavior."""
show_transition = StringProperty("linear")
"""
The type of transition of the widget opening.
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'linear'`.
"""
show_duration = NumericProperty(0.2)
"""
Duration of widget display transition.
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
hide_transition = StringProperty("linear")
"""
The type of transition of the widget closing.
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'linear'`.
"""
hide_duration = NumericProperty(0.2)
"""
Duration of widget closing transition.
:attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
class MotionDropDownMenuBehavior(MotionBase):
"""
Base class for the dropdown menu movement behavior.
For more information, see in the :class:`~MotionBase` class documentation.
"""
show_transition = StringProperty("out_back")
"""
The type of transition of the widget opening.
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_back'`.
"""
show_duration = NumericProperty(0.4)
"""
Duration of widget display transition.
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.2`.
"""
hide_transition = StringProperty("out_cubic")
"""
The type of transition of the widget closing.
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_cubic'`.
"""
_scale_x = NumericProperty(None)
"""
Default X-axis scaling values.
:attr:`_scale_x` is a :class:`~kivy.properties.NumericProperty`
and defaults to `None`.
"""
_scale_y = NumericProperty(None)
"""
Default Y-axis scaling values.
:attr:`_scale_y` is a :class:`~kivy.properties.NumericProperty`
and defaults to `None`.
"""
_opacity = NumericProperty(None)
"""
Menu transparency values.
:attr:`_opacity` is a :class:`~kivy.properties.NumericProperty`
and defaults to `None`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_scale()
# self.set_opacity()
def set_opacity(self) -> None:
self._opacity = 0
def set_scale(self) -> None:
self._scale_x = 0
self._scale_y = 0
def on_dismiss(self) -> None:
Window.remove_widget(self)
# anim = Animation(
# _scale_x=0,
# _scale_y=0,
# # _opacity=0,
# duration=self.hide_duration,
# transition=self.hide_transition,
# )
# anim.bind(on_complete=lambda *args: Window.remove_widget(self))
# anim.start(self)
def on_open(self, *args):
pass
anim = Animation(
_scale_y=1,
# _opacity=1,
duration=0.0,
transition=self.show_transition,
)
anim &= Animation(
_scale_x=1,
duration=0.0,
transition="out_quad",
)
anim.start(self)
def on__opacity(self, instance, value):
self.opacity = value
def on__scale_x(self, instance, value):
self.scale_value_x = value
def on__scale_y(self, instance, value):
self.scale_value_y = value
class MotionDialogBehavior(MotionBase):
"""
Base class for dialog movement behavior.
For more information, see in the :class:`~MotionBase` class documentation.
"""
show_duration = NumericProperty(0.0)
"""
Duration of widget display transition.
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.1`.
"""
scale_x = NumericProperty(1.0)
"""
Default X-axis scaling values.
:attr:`scale_x` is a :class:`~kivy.properties.NumericProperty`
and defaults to `1.5`.
"""
scale_y = NumericProperty(1.0)
"""
Default Y-axis scaling values.
:attr:`scale_y` is a :class:`~kivy.properties.NumericProperty`
and defaults to `1.5`.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_default_values()
def set_default_values(self):
"""Sets default scaled and transparency values."""
self.scale_value_x = self.scale_x
self.scale_value_y = self.scale_y
self.opacity = 0
def on_dismiss(self, *args):
"""Called when a dialog closed."""
self.set_default_values()
def on_open(self, *args):
"""Called when a dialog opened."""
Animation(
opacity=1,
scale_value_x=1,
scale_value_y=1,
t=self.show_transition,
d=self.show_duration,
).start(self)
class MotionShackBehavior(StencilBehavior, MotionBase):
"""
The base class for the behavior of the movement of snack bars.
For more information, see in the
:class:`~MotionBase` class and
:class:`~kivy.uix.behaviors.stencil_behavior.StencilBehavior` class
documentation.
"""
_interval = 0
_height = 0
def on_dismiss(self, *args):
"""Called when a snackbar closed."""
def remove_snackbar(*args):
Window.parent.remove_widget(self)
self.height = self._height
self.dispatch("on_dismiss")
Clock.unschedule(self._wait_interval)
anim = Animation(
opacity=0,
height=0,
t=self.hide_transition,
d=self.hide_duration,
)
anim.bind(on_complete=remove_snackbar)
anim.start(self)
def on_open(self, *args):
"""Called when a snackbar opened."""
def open(*args):
self._height = self.height
self.height = 0
anim = Animation(
opacity=1,
height=self._height,
t=self.show_transition,
d=self.show_duration,
)
anim.bind(
on_complete=lambda *args: Clock.schedule_interval(
self._wait_interval, 1
)
)
anim.start(self)
Clock.schedule_once(open)
self.dispatch("on_open")
def _wait_interval(self, interval):
self._interval += interval
if self._interval > self.duration:
self.dismiss()
self._interval = 0

14
sbapp/kivymd/uix/behaviors/ripple_behavior.py Executable file → Normal file
View File

@ -413,7 +413,12 @@ class CommonRipple:
class RectangularRippleBehavior(CommonRipple):
"""Class implements a rectangular ripple effect."""
"""
Class implements a rectangular ripple effect.
For more information, see in the :class:`~kivymd.uix.behavior.CommonRipple`
class documentation.
"""
ripple_scale = NumericProperty(2.75)
"""
@ -472,7 +477,12 @@ class RectangularRippleBehavior(CommonRipple):
class CircularRippleBehavior(CommonRipple):
"""Class implements a circular ripple effect."""
"""
Class implements a circular ripple effect.
For more information, see in the :class:`~kivymd.uix.behavior.CommonRipple`
class documentation.
"""
ripple_scale = NumericProperty(1)
"""

View File

@ -91,6 +91,10 @@ KivyMD
Test().run()
.. warning:: Do not use `RotateBehavior` class with classes that inherited`
from `CommonElevationBehavior` class. `CommonElevationBehavior` classes
by default contains attributes for rotate widget.
"""
__all__ = ("RotateBehavior",)

View File

@ -105,12 +105,16 @@ KivyMD
Test().run()
.. warning:: Do not use `ScaleBehavior` class with classes that inherited`
from `CommonElevationBehavior` class. `CommonElevationBehavior` classes
by default contains attributes for scale widget.
"""
__all__ = ("ScaleBehavior",)
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.properties import ListProperty, NumericProperty
Builder.load_string(
"""
@ -120,8 +124,11 @@ Builder.load_string(
Scale:
x: self.scale_value_x
y: self.scale_value_y
z: self.scale_value_x
origin: self.center
z: self.scale_value_z
origin:
self.center \
if not self.scale_value_center else \
self.scale_value_center
canvas.after:
PopMatrix
"""
@ -154,3 +161,15 @@ class ScaleBehavior:
:attr:`scale_value_z` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
scale_value_center = ListProperty()
"""
Origin of the scale.
.. versionadded:: 1.2.0
The format of the origin can be either (x, y) or (x, y, z).
:attr:`scale_value_center` is an :class:`~kivy.properties.NumericProperty`
and defaults to `[]`.
"""

View File

@ -22,7 +22,7 @@ Usage
from kivymd.uix.button import MDRaisedButton
KV = '''
Screen:
MDScreen:
MyButton:
text: "PRESS ME"
@ -74,9 +74,10 @@ class TouchBehavior:
def create_clock(self, widget, touch, *args):
if self.collide_point(touch.x, touch.y):
callback = partial(self.on_long_touch, touch)
Clock.schedule_once(callback, self.duration_long_touch)
touch.ud["event"] = callback
if "event" not in touch.ud:
callback = partial(self.on_long_touch, touch)
Clock.schedule_once(callback, self.duration_long_touch)
touch.ud["event"] = callback
if touch.is_double_tap:
self.on_double_tap(touch, *args)
@ -85,10 +86,9 @@ class TouchBehavior:
def delete_clock(self, widget, touch, *args):
if self.collide_point(touch.x, touch.y):
try:
if "event" in touch.ud:
Clock.unschedule(touch.ud["event"])
except KeyError:
pass
del touch.ud["event"]
def on_long_touch(self, touch, *args):
"""Called when the widget is pressed for a long time."""

40
sbapp/kivymd/uix/bottomnavigation/bottomnavigation.py Executable file → Normal file
View File

@ -274,12 +274,19 @@ with open(
Builder.load_string(kv_file.read())
class MDBottomNavigationHeader(
ThemableBehavior, ButtonBehavior, MDAnchorLayout
):
class MDBottomNavigationHeader(ButtonBehavior, MDAnchorLayout):
"""
Bottom navigation header class.
For more information, see in the
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.anchorlayout.MDAnchorLayout`
classes documentation.
"""
panel_color = ColorProperty([1, 1, 1, 0])
"""
Panel color of bottom navigation.
Panel color of bottom navigation in (r, g, b, a) or string format.
:attr:`panel_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 0]`.
@ -307,7 +314,8 @@ class MDBottomNavigationHeader(
text_color_normal = ColorProperty([1, 1, 1, 1])
"""
Text color of the label when it is not selected.
Text color in (r, g, b, a) or string format of the label when it is not
selected.
:attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
@ -315,7 +323,7 @@ class MDBottomNavigationHeader(
text_color_active = ColorProperty([1, 1, 1, 1])
"""
Text color of the label when it is selected.
Text color in (r, g, b, a) or string format of the label when it is selected.
:attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
@ -323,7 +331,8 @@ class MDBottomNavigationHeader(
selected_color_background = ColorProperty(None)
"""
The background color of the highlighted item when using Material Design v3.
The background color in (r, g, b, a) or string format of the highlighted
item when using Material Design v3.
.. versionadded:: 1.0.0
@ -384,10 +393,13 @@ class MDBottomNavigationHeader(
)
class MDTab(MDScreen, ThemableBehavior):
class MDTab(MDScreen):
"""
A tab is simply a screen with meta information that defines the content
that goes in the tab header.
For more information, see in the
:class:`~kivymd.uix.screen.MDScreen` class documentation.
"""
__events__ = (
@ -524,6 +536,10 @@ class TabbedPanelBase(
A class that contains all variables a :class:`~kivy.properties.TabPannel`
must have. It is here so I (zingballyhoo) don't get mad about
the :class:`~kivy.properties.TabbedPannels` not being DRY.
For more information, see in the :class:`~kivymd.theming.ThemableBehavior`
and :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior`
and :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation.
"""
current = StringProperty(None)
@ -555,6 +571,10 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
A bottom navigation that is implemented by delegating all items to a
:class:`~kivy.uix.screenmanager.ScreenManager`.
For more information, see in the
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~TabbedPanelBase` classes documentation.
:Events:
:attr:`on_switch_tabs`
Called when switching tabs. Returns the object of the tab to be
@ -856,7 +876,5 @@ class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase):
return bottom_navigation_item
class MDBottomNavigationBar(
ThemableBehavior, CommonElevationBehavior, MDFloatLayout
):
class MDBottomNavigationBar(CommonElevationBehavior, MDFloatLayout):
pass

View File

@ -1,7 +1,10 @@
# NOQA F401
from .bottomsheet import (
GridBottomSheetItem,
MDBottomSheet,
MDBottomSheetContent,
MDBottomSheetDragHandle,
MDBottomSheetDragHandleButton,
MDBottomSheetDragHandleTitle,
MDCustomBottomSheet,
MDGridBottomSheet,
MDListBottomSheet,

View File

@ -1,73 +1,42 @@
#:import Window kivy.core.window.Window
<MDBottomSheetContent>
size_hint_y: None
height: self.minimum_height
<SheetList>
<MDBottomSheetDragHandle>
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
padding: "16dp", "8dp", "16dp", "16dp"
MDGridLayout:
id: box_sheet_list
cols: 1
adaptive_height: True
padding: 0, 0, 0, "96dp"
BottomSheetDragHandle:
md_bg_color:
app.theme_cls.disabled_hint_text_color \
if not root.drag_handle_color else \
root.drag_handle_color
size_hint: None, None
size: "32dp", "4dp"
radius: 4
pos_hint: {"center_x": .5}
BottomSheetDragHandleContainer:
id: header_container
size_hint_y: None
height: self.minimum_height
<MDBottomSheet>
md_bg_color: root.value_transparent
_upper_padding: _upper_padding
_gl_content: _gl_content
_position_content: Window.height
orientation: "vertical"
md_bg_color: root.bg_color if root.bg_color else app.theme_cls.bg_darkest
radius: 16, 16, 0, 0
padding: 0, "8dp", 0, 0
MDBoxLayout:
orientation: "vertical"
padding: 0, 1, 0, 0
id: drag_handle_container
size_hint_y: None
height: self.minimum_height
BsPadding:
id: _upper_padding
size_hint_y: None
height: root.height - min(root.width * 9 / 16, root._gl_content.height)
on_release: root.dismiss()
BottomSheetContent:
id: _gl_content
size_hint_y: None
cols: 1
md_bg_color: 0, 0, 0, 0
canvas:
Color:
rgba: root.theme_cls.bg_normal if not root.bg_color else root.bg_color
RoundedRectangle:
pos: self.pos
size: self.size
radius:
[
(root.radius, root.radius) if root.radius_from == "top_left" or root.radius_from == "top" else (0, 0),
(root.radius, root.radius) if root.radius_from == "top_right" or root.radius_from == "top" else (0, 0),
(root.radius, root.radius) if root.radius_from == "bottom_right" or root.radius_from == "bottom" else (0, 0),
(root.radius, root.radius) if root.radius_from == "bottom_left" or root.radius_from == "bottom" else (0, 0)
]
<ListBottomSheetIconLeft>
theme_text_color: "Primary"
pos_hint: {"center_x": .5, "center_y": .5}
<GridBottomSheetItem>
orientation: "vertical"
padding: 0, dp(24), 0, 0
size_hint_y: None
size: dp(64), dp(96)
AnchorLayout:
anchor_x: "center"
MDIconButton:
icon: root.source
user_font_size: root.icon_size
on_release: root.dispatch("on_release")
MDLabel:
font_style: "Caption"
theme_text_color: "Secondary"
text: root.caption
halign: "center"
MDBoxLayout:
id: container
size_hint_y: None
height: self.minimum_height

1412
sbapp/kivymd/uix/bottomsheet/bottomsheet.py Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@ -87,11 +87,14 @@ __all__ = ("MDBoxLayout",)
from kivy.uix.boxlayout import BoxLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDBoxLayout(DeclarativeBehavior, BoxLayout, MDAdaptiveWidget):
class MDBoxLayout(
DeclarativeBehavior, ThemableBehavior, BoxLayout, MDAdaptiveWidget
):
"""
Box layout class.

View File

@ -2,6 +2,7 @@
canvas:
Clear
Color:
group: "bg-color"
rgba:
self._md_bg_color \
if not self.disabled else \
@ -12,6 +13,7 @@
source: self.source if hasattr(self, "source") else ""
radius: [root._radius, ]
Color:
group: "outline-color"
rgba:
root._line_color \
if not root.disabled else \
@ -92,9 +94,11 @@
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)
# Fix https://github.com/kivymd/KivyMD/issues/1448
# TODO: Perhaps this change may affect other widgets.
# You need to create tests.
# on_icon:
# if self.icon not in md_icons.keys(): self.size_hint = (1, 1)
theme_text_color: root._theme_icon_color

View File

@ -679,6 +679,15 @@ from kivy.weakproxy import WeakProxy
from kivymd import uix_path
from kivymd.color_definitions import text_colors
from kivymd.font_definitions import theme_font_styles
from kivymd.material_resources import (
FLOATING_ACTION_BUTTON_M2_ELEVATION,
FLOATING_ACTION_BUTTON_M2_OFFSET,
FLOATING_ACTION_BUTTON_M3_ELEVATION,
FLOATING_ACTION_BUTTON_M3_OFFSET,
FLOATING_ACTION_BUTTON_M3_SOFTNESS,
RAISED_BUTTON_OFFSET,
RAISED_BUTTON_SOFTNESS,
)
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
CommonElevationBehavior,
@ -704,62 +713,6 @@ theme_text_color_options = (
"ContrastParentBackground",
)
# FIXME: If you set a new elevation value for the button
# (press the "Set elevation" button), then disable the button
# (press the "Disabled" button), and then enable the button
# (press the "Undisabled" button), then the previously set elevation value is
# reset to zero.
# In addition, if you set a new elevation value
# (press the "Set elevation" button) and click on the button for which we set
# the elevation value, then the new elevation value will receive the previous
# elevation value. This problem is only related to the buttons.
# For example, there is no such problem for the MDCard widget.
"""
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDRaisedButton:
size_hint: .5, .5
id: button
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 0
MDBoxLayout:
adaptive_size: True
pos_hint: {"center_x": .5}
spacing: 12
padding: 12
MDRaisedButton:
text: "Set elevation"
pos_hint: {"center_x": .5, "bottom": 1}
on_release: button.elevation = 4
MDRaisedButton:
text: "Disabled"
pos_hint: {"center_x": .5, "bottom": 1}
on_release: button.disabled = True
MDRaisedButton:
text: "Undisabled"
pos_hint: {"center_x": .5, "bottom": 1}
on_release: button.disabled = False
'''
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
Test().run()
"""
class BaseButton(
DeclarativeBehavior,
@ -772,7 +725,12 @@ class BaseButton(
Base class for all buttons.
For more information, see in the
:class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation.
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivy.uix.anchorlayout.AnchorLayout`
classes documentation.
"""
padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)])
@ -1208,7 +1166,7 @@ class ButtonElevationBehaviour(CommonElevationBehavior):
_elevation_raised = NumericProperty()
_anim_raised = ObjectProperty(None, allownone=True)
_default_elevation = 3
_default_elevation = 2
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -1220,8 +1178,9 @@ class ButtonElevationBehaviour(CommonElevationBehavior):
self.on_disabled(self, self.disabled)
def create_anim_raised(self, *args) -> None:
self._elevation_raised = self.elevation + 1.2
self._anim_raised = Animation(elevation=self.elevation + 1, d=0.15)
if self.elevation:
self._elevation_raised = self.elevation
self._anim_raised = Animation(elevation=self.elevation + 1, d=0.15)
def on_touch_down(self, touch):
if not self.disabled:
@ -1231,21 +1190,21 @@ class ButtonElevationBehaviour(CommonElevationBehavior):
return False
if self in touch.ud:
return False
if self._anim_raised:
if self._anim_raised and self.elevation:
self._anim_raised.start(self)
return super().on_touch_down(touch)
def on_touch_up(self, touch):
if not self.disabled:
if touch.grab_current is not self:
if self in touch.ud:
self.stop_elevation_anim()
return super().on_touch_up(touch)
self.stop_elevation_anim()
return super().on_touch_up(touch)
def stop_elevation_anim(self):
Animation.cancel_all(self, "elevation")
self.elevation = self._elevation_raised - 1
if self._anim_raised and self.elevation:
self.elevation = self._elevation_raised
class ButtonContentsText:
@ -1313,6 +1272,10 @@ class MDFlatButton(BaseButton, ButtonContentsText):
"""
A flat rectangular button with (by default) no border or background.
Text is the default text color.
For more information, see in the
:class:`~BaseButton` and :class:`~ButtonContentsText`
classes documentation.
"""
padding = VariableListProperty([dp(8), dp(8), dp(8), dp(8)])
@ -1334,6 +1297,12 @@ class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText):
"""
A flat button with (by default) a primary color fill and matching
color text.
For more information, see in the
:class:`~BaseButton` and
:class:`~ButtonElevationBehaviour` and
:class:`~ButtonContentsText`
classes documentation.
"""
# FIXME: Move the underlying attributes to the :class:`~BaseButton` class.
@ -1345,15 +1314,19 @@ class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.shadow_softness = 8
self.shadow_offset = (0, 2)
self.shadow_radius = self._radius * 2
self.shadow_softness = RAISED_BUTTON_SOFTNESS
self.shadow_offset = RAISED_BUTTON_OFFSET
# self.shadow_radius = self._radius * 2
class MDRectangleFlatButton(BaseButton, ButtonContentsText):
"""
A flat button with (by default) a primary color border and primary
color text.
For more information, see in the
:class:`~BaseButton` and :class:`~ButtonContentsText`
classes documentation.
"""
_default_line_color = None
@ -1368,6 +1341,12 @@ class MDRectangleFlatIconButton(
"""
A flat button with (by default) a primary color border, primary color text
and a primary color icon on the left.
For more information, see in the
:class:`~BaseButton` and
:class:`~OldButtonIconMixin` and
:class:`~ButtonContentsIconText`
classes documentation.
"""
_default_line_color = None
@ -1382,6 +1361,10 @@ class MDRoundFlatButton(BaseButton, ButtonContentsText):
"""
A flat button with (by default) fully rounded corners, a primary
color border and primary color text.
For more information, see in the
:class:`~BaseButton` and :class:`~ButtonContentsText`
classes documentation.
"""
_default_line_color = None
@ -1400,6 +1383,12 @@ class MDRoundFlatIconButton(
"""
A flat button with (by default) rounded corners, a primary color border,
primary color text and a primary color icon on the left.
For more information, see in the
:class:`~BaseButton` and
:class:`~OldButtonIconMixin` and
:class:`~ButtonContentsIconText`
classes documentation.
"""
_default_line_color = None
@ -1418,6 +1407,10 @@ class MDFillRoundFlatButton(BaseButton, ButtonContentsText):
"""
A flat button with (by default) rounded corners, a primary color fill
and primary color text.
For more information, see in the
:class:`~BaseButton` and :class:`~ButtonContentsText`
classes documentation.
"""
_default_md_bg_color = None
@ -1436,6 +1429,12 @@ class MDFillRoundFlatIconButton(
"""
A flat button with (by default) rounded corners, a primary color fill,
primary color text and a primary color icon on the left.
For more information, see in the
:class:`~BaseButton` and
:class:`~OldButtonIconMixin` and
:class:`~ButtonContentsIconText`
classes documentation.
"""
_default_md_bg_color = None
@ -1451,7 +1450,14 @@ class MDFillRoundFlatIconButton(
class MDIconButton(BaseButton, OldButtonIconMixin, ButtonContentsIcon):
"""A simple rounded icon button."""
"""
A simple rounded icon button.
For more information, see in the
:class:`~BaseButton` and
:class:`~OldButtonIconMixin` and
:class:`~ButtonContentsIcon` classes documentation.
"""
icon = StringProperty("checkbox-blank-circle")
"""
@ -1489,6 +1495,12 @@ class MDFloatingActionButton(
Implementation
`FAB <https://m3.material.io/components/floating-action-button/overview>`_
button.
For more information, see in the
:class:`~BaseButton` and
:class:`~OldButtonIconMixin` and
:class:`~ButtonElevationBehaviour` and
:class:`~ButtonContentsIcon` classes documentation.
"""
type = OptionProperty("standard", options=["small", "large", "standard"])
@ -1530,10 +1542,13 @@ class MDFloatingActionButton(
def set__radius(self, *args) -> None:
if self.theme_cls.material_style == "M2":
self.shadow_radius = self.height / 2
self.elevation = FLOATING_ACTION_BUTTON_M2_ELEVATION
self.shadow_offset = FLOATING_ACTION_BUTTON_M2_OFFSET
self.rounded_button = True
else:
self.shadow_softness = 8
self.shadow_offset = (0, 2)
self.shadow_softness = FLOATING_ACTION_BUTTON_M3_SOFTNESS
self.shadow_offset = FLOATING_ACTION_BUTTON_M3_OFFSET
self.elevation = FLOATING_ACTION_BUTTON_M3_ELEVATION
self.rounded_button = False
if self.type == "small":
@ -1566,6 +1581,14 @@ class MDFloatingActionButton(
class MDTextButton(ButtonBehavior, MDLabel):
"""
Text button class.
For more information, see in the
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.label.MDLabel` classes documentation.
"""
color = ColorProperty(None)
"""
Button color in (r, g, b, a) or string format.
@ -1638,6 +1661,12 @@ class MDFloatingActionButtonSpeedDial(
For more information, see in the
:class:`~kivy.uix.floatlayout.FloatLayout` class documentation.
For more information, see in the
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.floatlayout.FloatLayout`
lasses documentation.
:Events:
:attr:`on_open`
Called when a stack is opened.
@ -1868,7 +1897,7 @@ class MDFloatingActionButtonSpeedDial(
"""
Background color of root button in (r, g, b, a) or string format.
.. code-clock:: kv
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
bg_color_root_button: "red"
@ -1884,7 +1913,7 @@ class MDFloatingActionButtonSpeedDial(
"""
Background color of the stack buttons in (r, g, b, a) or string format.
.. code-clock:: kv
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
bg_color_root_button: "red"
@ -1901,7 +1930,7 @@ class MDFloatingActionButtonSpeedDial(
"""
The color icon of the stack buttons in (r, g, b, a) or string format.
.. code-clock:: kv
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
bg_color_root_button: "red"
@ -1919,7 +1948,7 @@ class MDFloatingActionButtonSpeedDial(
"""
The color icon of the root button in (r, g, b, a) or string format.
.. code-clock:: kv
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
bg_color_root_button: "red"
@ -1939,7 +1968,7 @@ class MDFloatingActionButtonSpeedDial(
Background color for the floating text of the buttons in (r, g, b, a)
or string format.
.. code-clock:: kv
.. code-block:: kv
MDFloatingActionButtonSpeedDial:
bg_hint_color: "red"

View File

@ -94,6 +94,7 @@ An example of the implementation of a card in the style of material design versi
style=style,
text=style.capitalize(),
md_bg_color=styles[style],
shadow_offset=(0, -1),
)
)
@ -152,10 +153,9 @@ An example of the implementation of a card in the style of material design versi
),
line_color=(0.2, 0.2, 0.2, 0.8),
style=style,
padding="4dp",
size_hint=(None, None),
size=("200dp", "100dp"),
text=style.capitalize(),
md_bg_color=styles[style],
shadow_offset=(0, -1),
)
)
@ -699,7 +699,12 @@ from kivy.utils import get_color_from_hex
from kivymd import uix_path
from kivymd.color_definitions import colors
from kivymd.material_resources import (
CARD_STYLE_ELEVATED_M3_ELEVATION,
CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION,
)
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import (
BackgroundColorBehavior,
CommonElevationBehavior,
@ -716,12 +721,17 @@ with open(
Builder.load_string(kv_file.read())
class MDSeparator(ThemableBehavior, MDBoxLayout):
"""A separator line."""
class MDSeparator(MDBoxLayout):
"""
A separator line.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
"""
color = ColorProperty(None)
"""
Separator color.
Separator color in (r, g, b, a) or string format.
:attr:`color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -743,6 +753,7 @@ class MDSeparator(ThemableBehavior, MDBoxLayout):
class MDCard(
DeclarativeBehavior,
MDAdaptiveWidget,
ThemableBehavior,
BackgroundColorBehavior,
RectangularRippleBehavior,
@ -750,6 +761,21 @@ class MDCard(
FocusBehavior,
BoxLayout,
):
"""
Card class.
For more information, see in the
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~kivymd.uix.MDAdaptiveWidget` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivymd.uix.behaviors.BackgroundColorBehavior` and
:class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
:class:`~kivymd.uix.behaviors.CommonElevationBehavior` and
:class:`~kivymd.uix.behaviors.FocusBehavior` and
:class:`~kivy.uix.boxlayout.BoxLayout` and
classes documentation.
"""
focus_behavior = BooleanProperty(False)
"""
Using focus when hovering over a card.
@ -824,9 +850,9 @@ class MDCard(
def set_elevation(self) -> None:
if self.theme_cls.material_style == "M3":
if self.style == "outlined" or self.style == "filled":
self.elevation = 0
self.elevation = CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION
elif self.style == "elevated":
self.elevation = 2
self.elevation = CARD_STYLE_ELEVATED_M3_ELEVATION
def set_radius(self) -> None:
if (
@ -848,6 +874,11 @@ class MDCard(
class MDCardSwipe(MDRelativeLayout):
"""
Card swipe class.
For more information, see in the
:class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation.
:Events:
:attr:`on_swipe_complete`
Called when a swipe of card is completed.
@ -1065,7 +1096,11 @@ class MDCardSwipe(MDRelativeLayout):
class MDCardSwipeFrontBox(MDCard):
pass
"""
Card swipe front box.
For more information, see in the :class:`~MDCard` class documentation.
"""
class MDCardSwipeLayerBox(MDBoxLayout):

View File

@ -57,10 +57,11 @@ MDCarousel
from kivy.animation import Animation
from kivy.uix.carousel import Carousel
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import DeclarativeBehavior
class MDCarousel(DeclarativeBehavior, Carousel):
class MDCarousel(DeclarativeBehavior, ThemableBehavior, Carousel):
"""
based on kivy's carousel.

View File

@ -1 +1 @@
from .chip import MDChip # NOQA F401
from .chip import MDChip, MDChipText # NOQA F401

View File

@ -1,110 +1,38 @@
<MDScalableCheckIcon>
scale_value_x: 0
scale_value_y: 0
scale_value_z: 0
<MDChip>
size_hint_y: None
height: "32dp"
spacing: "8dp"
adaptive_width: True
radius: 16 if self.radius == [0, 0, 0, 0] else self.radius
padding:
"12dp" if not self.icon_left else "4dp", \
0, \
"12dp" if not self.icon_right else "8dp", \
0
radius:
16 \
if self.radius == [0, 0, 0, 0] else \
(max(self.radius) if max(self.radius) < self.height / 2 else 16)
md_bg_color:
( \
( \
app.theme_cls.bg_darkest \
if app.theme_cls.theme_style == "Light" else \
app.theme_cls.bg_light \
) \
if not self.disabled else app.theme_cls.disabled_hint_text_color
if not self._origin_md_bg_color else \
self._origin_md_bg_color
) \
if not self.disabled else app.theme_cls.disabled_primary_color
line_color:
app.theme_cls.disabled_hint_text_color \
if self.disabled else ( \
self._origin_line_color \
if self._origin_line_color else \
self.line_color \
)
canvas.before:
Color:
rgba:
self.line_color \
if not self.disabled else \
app.theme_cls.disabled_hint_text_color
Line:
width: 1
rounded_rectangle:
( \
self.x, \
self.y, \
self.width, \
self.height, \
*self.radius, \
self.height \
)
LeadingIconContainer:
id: leading_icon_container
adaptive_width: True
MDRelativeLayout:
id: relative_box
size_hint: None, None
size: ("24dp", "24dp") if root.icon_left else (0, 0)
pos_hint: {"center_y": .5}
radius: [int(self.height / 2),]
LabelTextContainer:
id: label_container
adaptive_width: True
MDIcon:
id: icon_left
icon: root.icon_left
size_hint: None, None
size: ("28dp", "28dp") if root.icon_left else (0, 0)
theme_text_color: "Custom"
pos_hint: {"center_y": .5}
pos: 0, -2
text_color:
( \
root.icon_left_color \
if root.icon_left_color else \
root.theme_cls.disabled_hint_text_color \
) \
if not self.disabled else app.theme_cls.disabled_hint_text_color
MDBoxLayout:
id: icon_left_box
size_hint: None, None
radius: [int(self.height / 2),]
size: ("28dp", "28dp") if root.icon_left else (0, 0)
pos: 0, -2
MDScalableCheckIcon:
id: check_icon
icon: "check"
size_hint: None, None
size: "28dp", "28dp"
color: (1, 1, 1, 1) if not root.icon_check_color else root.icon_check_color
pos: 2, -2
MDLabel:
id: label
text: root.text
adaptive_size: True
markup: True
pos_hint: {"center_y": .5}
color:
( \
root.text_color \
if root.text_color else \
root.theme_cls.disabled_hint_text_color \
) \
if not self.disabled else app.theme_cls.disabled_hint_text_color
MDIcon:
id: icon_right
icon: root.icon_right
size_hint: None, None
size: ("18dp", "18dp") if root.icon_right else (0, 0)
font_size: "18sp" if root.icon_right else 0
theme_text_color: "Custom"
pos_hint: {"center_y": .5}
text_color:
( \
root.icon_right_color \
if root.icon_right_color else \
root.theme_cls.disabled_hint_text_color \
) \
if not self.disabled else app.theme_cls.disabled_hint_text_color
TrailingIconContainer:
id: trailing_icon_container
adaptive_width: True

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,6 @@ from kivymd.uix.floatlayout import MDFloatLayout
class MDCircularLayout(MDFloatLayout):
degree_spacing = NumericProperty(30)
"""
The space between children in degree.

View File

@ -231,4 +231,11 @@
id: container
orientation: "vertical"
elevation: root.elevation
shadow_radius: root.shadow_radius
shadow_softness: root.shadow_softness
shadow_offset: root.shadow_offset
shadow_color: root.shadow_color
shadow_color: root.shadow_color
shadow_softness_size: root.shadow_softness_size
padding: "24dp", "24dp", "8dp", "8dp"
md_bg_color: app.theme_cls.bg_normal

View File

@ -37,6 +37,7 @@ from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
BoundedNumericProperty,
ColorProperty,
DictProperty,
ListProperty,
@ -44,6 +45,7 @@ from kivy.properties import (
ObjectProperty,
OptionProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior, FocusBehavior
@ -56,6 +58,11 @@ from kivy.uix.scrollview import ScrollView
from kivymd import uix_path
from kivymd.effects.stiffscroll import StiffScrollEffect
from kivymd.material_resources import (
DATA_TABLE_ELEVATION,
DATA_TABLE_OFFSET,
DATA_TABLE_SOFTNESS,
)
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import HoverBehavior
from kivymd.uix.boxlayout import MDBoxLayout
@ -758,7 +765,7 @@ class TableData(RecycleView):
# instance_pagination.ids.button_forward.disabled = True
class TablePagination(ThemableBehavior, MDBoxLayout):
class TablePagination(MDBoxLayout):
"""Pagination Container."""
table_data = ObjectProperty()
@ -772,8 +779,11 @@ class TablePagination(ThemableBehavior, MDBoxLayout):
class MDDataTable(ThemableBehavior, AnchorLayout):
"""
See :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation for
more information.
Datatable class.
For more information, see in the
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.anchorlayout.AnchorLayout` classes documentation.
:Events:
:attr:`on_row_press`
@ -1297,14 +1307,70 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
and defaults to `False`.
"""
elevation = NumericProperty(4)
elevation = NumericProperty(DATA_TABLE_ELEVATION)
"""
Table elevation.
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
attribute.
:attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
and defaults to `4`.
"""
shadow_radius = VariableListProperty([6], length=4)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_radius`
attribute.
.. versionadded:: 1.2.0
:attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[6]`.
"""
shadow_softness = NumericProperty(DATA_TABLE_SOFTNESS)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
attribute.
.. versionadded:: 1.2.0
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
and defaults to `12`.
"""
shadow_softness_size = BoundedNumericProperty(2, min=2)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness_size`
attribute.
.. versionadded:: 1.2.0
:attr:`shadow_softness_size` is an :class:`~kivy.properties.BoundedNumericProperty`
and defaults to `2`.
"""
shadow_offset = ListProperty(DATA_TABLE_OFFSET)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
attribute.
.. versionadded:: 1.2.0
:attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
and defaults to `(0, 2)`.
"""
shadow_color = ColorProperty([0, 0, 0, 0.6])
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_color`
attribute.
.. versionadded:: 1.2.0
:attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.6]`.
"""
rows_num = NumericProperty(5)
"""
The number of rows displayed on one page of the table.
@ -1626,6 +1692,77 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-add-remove-row.gif
:align: center
Deleting checked rows
---------------------
.. code-block:: python
from kivy.metrics import dp
from kivy.lang import Builder
from kivy.clock import Clock
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivymd.uix.screen import MDScreen
KV = '''
MDBoxLayout:
orientation: "vertical"
padding: "56dp"
spacing: "24dp"
MDData:
id: table_screen
MDRaisedButton:
text: "DELETE CHECKED ROWS"
on_release: table_screen.delete_checked_rows()
'''
class MDData(MDScreen):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.data = [
["1", "Asep Sudrajat", "Male", "Soccer"],
["2", "Egy", "Male", "Soccer"],
["3", "Tanos", "Demon", "Soccer"],
]
self.data_tables = MDDataTable(
use_pagination=True,
check=True,
column_data=[
("No", dp(30)),
("No Urut.", dp(30)),
("Alamat Pengirim", dp(30)),
("No Surat", dp(60)),
]
)
self.data_tables.row_data = self.data
self.add_widget(self.data_tables)
def delete_checked_rows(self):
def deselect_rows(*args):
self.data_tables.table_data.select_all("normal")
for data in self.data_tables.get_row_checks():
self.data_tables.remove_row(data)
Clock.schedule_once(deselect_rows)
class MyApp(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
MyApp().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-deleting-checked-rows.gif
:align: center
.. versionadded:: 1.0.0
"""
@ -1760,7 +1897,6 @@ class MDDataTable(ThemableBehavior, AnchorLayout):
class CellRow(
ThemableBehavior,
RecycleDataViewBehavior,
HoverBehavior,
ButtonBehavior,

View File

@ -32,7 +32,7 @@
orientation: "vertical"
size_hint_y: None
height: self.minimum_height
padding: "24dp", "24dp", "16dp", "8dp"
padding: "24dp", "24dp", "8dp", "8dp"
radius: root.radius
md_bg_color:
root.theme_cls.bg_dark \

View File

@ -89,7 +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.behaviors import CommonElevationBehavior, MotionDialogBehavior
from kivymd.uix.button import BaseButton
from kivymd.uix.card import MDSeparator
from kivymd.uix.list import BaseListItem
@ -100,7 +100,9 @@ with open(
Builder.load_string(kv_file.read())
class BaseDialog(ThemableBehavior, ModalView, CommonElevationBehavior):
class BaseDialog(
ThemableBehavior, MotionDialogBehavior, ModalView, CommonElevationBehavior
):
elevation = NumericProperty(3)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
@ -159,6 +161,16 @@ class BaseDialog(ThemableBehavior, ModalView, CommonElevationBehavior):
class MDDialog(BaseDialog):
"""
Dialog class.
For more information, see in the
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.modalview.ModalView` and
:class:`~kivymd.uix.behaviors.CommonElevationBehavior`
classes documentation.
"""
title = StringProperty()
"""
Title dialog.
@ -286,22 +298,22 @@ class MDDialog(BaseDialog):
class Example(MDApp):
dialog = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
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="kivymd/images/logo/kivymd-icon-128.png"),
Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.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()
@ -422,7 +434,8 @@ class MDDialog(BaseDialog):
content_cls = ObjectProperty()
"""
Custom content class.
Custom content class. This attribute is only available when :attr:`type` is
set to `'custom'`.
.. tabs::
@ -637,6 +650,7 @@ class MDDialog(BaseDialog):
def on_open(self) -> None:
# TODO: Add scrolling text.
self.height = self.ids.container.height
super().on_open()
def get_normal_height(self) -> float:
return (

View File

@ -68,6 +68,17 @@ class _Triangle(Widget):
class MDDropDownItem(
DeclarativeBehavior, ThemableBehavior, ButtonBehavior, BoxLayout
):
"""
Dropdown item class.
For more information, see in the
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivy.uix.boxlayout.BoxLayout`
classes documentation.
"""
text = StringProperty()
"""
Text item.

View File

@ -185,21 +185,39 @@ class MDExpansionChevronRight(IRightBodyTouch, MDIconButton):
class MDExpansionPanelOneLine(OneLineAvatarIconListItem):
"""Single line panel."""
"""
Single line panel.
For more information, see in the
:class:`~kivymd.uix.list.OneLineAvatarIconListItem` class documentation.
"""
class MDExpansionPanelTwoLine(TwoLineAvatarIconListItem):
"""Two-line panel."""
"""
Two-line panel.
For more information, see in the
:class:`~kivymd.uix.list.TwoLineAvatarIconListItem` class documentation.
"""
class MDExpansionPanelThreeLine(ThreeLineAvatarIconListItem):
"""Three-line panel."""
"""
Three-line panel.
For more information, see in the
:class:`~kivymd.uix.list.ThreeLineAvatarIconListItem` class documentation.
"""
class MDExpansionPanelLabel(TwoLineListItem):
"""
Label panel.
For more information, see in the
:class:`~kivymd.uix.list.TwoLineListItem` class documentation.
..warning:: This class is created for use in the
:class:`~kivymd.uix.stepper.MDStepperVertical` and
:class:`~kivymd.uix.stepper.MDStepper` classes, and has not
@ -217,6 +235,11 @@ class MDExpansionPanelLabel(TwoLineListItem):
class MDExpansionPanel(RelativeLayout):
"""
Expansion panel class.
For more information, see in the
:class:`~kivy.uix.relativelayout.RelativeLayout` classes documentation.
:Events:
:attr:`on_open`
Called when a panel is opened.

View File

@ -1,4 +1,6 @@
#:import os os
#:import FILE_MANAGER_TOP_APP_BAR_ELEVATION kivymd.material_resources.FILE_MANAGER_TOP_APP_BAR_ELEVATION
<BodyManager>
icon: "folder"
@ -74,7 +76,7 @@
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: 3
elevation: FILE_MANAGER_TOP_APP_BAR_ELEVATION
md_bg_color:
app.theme_cls.primary_color \
if not root.background_color_toolbar else \

View File

@ -158,7 +158,6 @@ from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.modalview import ModalView
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
@ -197,7 +196,7 @@ class ModifiedOneLineIconListItem(BaseListItem):
self.height = dp(48)
class MDFileManager(MDRelativeLayout, ThemableBehavior):
class MDFileManager(MDRelativeLayout):
"""
Implements a modal dialog with a file manager.
@ -248,7 +247,8 @@ class MDFileManager(MDRelativeLayout, ThemableBehavior):
background_color_selection_button = ColorProperty(None)
"""
Background color of the current directory/path selection button.
Background color in (r, g, b, a) or string format of the current
directory/path selection button.
.. versionadded:: 1.1.0
@ -268,7 +268,7 @@ class MDFileManager(MDRelativeLayout, ThemableBehavior):
background_color_toolbar = ColorProperty(None)
"""
Background color of the file manager toolbar.
Background color in (r, g, b, a) or string format of the file manager toolbar.
.. versionadded:: 1.1.0
@ -307,7 +307,8 @@ class MDFileManager(MDRelativeLayout, ThemableBehavior):
icon_color = ColorProperty(None)
"""
Color of the folder icon when the :attr:`preview` property is set to False.
Color in (r, g, b, a) or string format of the folder icon when the
:attr:`preview` property is set to False.
.. versionadded:: 1.1.0

View File

@ -137,6 +137,14 @@ from kivymd.uix.boxlayout import MDBoxLayout
class FitImage(MDBoxLayout, StencilBehavior):
"""
Fit image class.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDLayout` and
:class:`~kivymd.uix.behaviors.StencilBehavior` classes documentation.
"""
source = ObjectProperty()
"""
Filename/source of your image.

View File

@ -35,11 +35,14 @@ MDFloatLayout
from kivy.uix.floatlayout import FloatLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDFloatLayout(DeclarativeBehavior, FloatLayout, MDAdaptiveWidget):
class MDFloatLayout(
DeclarativeBehavior, ThemableBehavior, FloatLayout, MDAdaptiveWidget
):
"""
Float layout class. For more information, see in the
:class:`~kivy.uix.floatlayout.FloatLayout` class documentation.

View File

@ -85,11 +85,14 @@ Equivalent
from kivy.uix.gridlayout import GridLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDGridLayout(DeclarativeBehavior, GridLayout, MDAdaptiveWidget):
class MDGridLayout(
DeclarativeBehavior, ThemableBehavior, GridLayout, MDAdaptiveWidget
):
"""
Grid layout class. For more information, see in the
:class:`~kivy.uix.gridlayout.GridLayout` class documentation.

View File

@ -13,6 +13,7 @@
(0, 0)
on_release: root.dispatch("on_release")
on_press: root.dispatch("on_press")
_no_ripple_effect: root._no_ripple_effect
SmartTileOverlayBox:
id: box

14
sbapp/kivymd/uix/imagelist/imagelist.py Executable file → Normal file
View File

@ -71,6 +71,7 @@ __all__ = [
import os
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import (
BooleanProperty,
@ -82,7 +83,6 @@ from kivy.properties import (
from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.fitimage import FitImage
@ -103,10 +103,13 @@ class SmartTileOverlayBox(MDBoxLayout):
"""Implements a container for custom widgets to be added to the tile."""
class MDSmartTile(MDRelativeLayout, ThemableBehavior):
class MDSmartTile(MDRelativeLayout):
"""
A tile for more complex needs.
For more information, see in the
:class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation.
Includes an image, a container to place overlays and a box that can act
as a header or a footer, as described in the Material Design specs.
@ -139,7 +142,8 @@ class MDSmartTile(MDRelativeLayout, ThemableBehavior):
box_color = ColorProperty((0, 0, 0, 0.5))
"""
Sets the color and opacity for the information box.
Sets the color in (r, g, b, a) or string format and opacity for the
information box.
.. code-block:: kv
@ -249,6 +253,8 @@ class MDSmartTile(MDRelativeLayout, ThemableBehavior):
and defaults to `False`.
"""
_no_ripple_effect = BooleanProperty(False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.register_event_type("on_release")
@ -270,4 +276,4 @@ class MDSmartTile(MDRelativeLayout, ThemableBehavior):
if isinstance(widget, MDLabel):
widget.shorten = True
widget.shorten_from = "right"
self.ids.box.add_widget(widget)
Clock.schedule_once(lambda x: self.ids.box.add_widget(widget))

View File

@ -3,12 +3,10 @@
<MDLabel>
disabled_color: self.theme_cls.disabled_hint_text_color
# FIXME: Overriding the values of this property greatly affects application
# performance. Especially when the application window is resized and a
# custom font is used. Performance is especially slow when you are using
# `PIL` as your text processing provider - os.environ ['KIVY_TEXT'] = 'pil'.
# Priority - CRITICAL.
text_size: self.width, None
text_size:
(self.width if not self.adaptive_width else None) \
if not self.adaptive_size else None, \
None
<MDIcon>:
@ -16,6 +14,7 @@
Color:
rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0)
Rectangle:
group: "rectangle"
source: self.source if self.source else None
pos:
self.pos \
@ -32,6 +31,7 @@
# Badge icon.
MDLabel:
id: badge
font_style: "Icon"
adaptive_size: True
opposite_icon_color: True
@ -62,6 +62,7 @@
if root.badge_icon else \
(0, 0, 0, 0)
RoundedRectangle:
group: "badge"
radius: [self.width / 2,]
pos: self.pos
size: self.size

View File

@ -18,32 +18,58 @@ Class :class:`MDLabel` inherited from the :class:`~kivy.uix.label.Label` class
but for :class:`MDLabel` the ``text_size`` parameter is ``(self.width, None)``
and default is positioned on the left:
.. code-block:: python
.. tabs::
from kivy.lang import Builder
.. tab:: Declarative KV style
from kivymd.app import MDApp
.. code-block:: python
KV = '''
MDScreen:
from kivy.lang import Builder
MDBoxLayout:
orientation: "vertical"
from kivymd.app import MDApp
MDTopAppBar:
title: "MDLabel"
KV = '''
MDScreen:
MDLabel:
text: "MDLabel"
'''
MDLabel:
text: "MDLabel"
'''
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 kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.label import MDLabel
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDLabel(
text="MDLabel"
)
)
)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-left.png
:align: center
@ -74,20 +100,16 @@ and default is positioned on the left:
from kivymd.uix.label import MDLabel
KV = '''
MDScreen:
MDBoxLayout:
id: box
orientation: "vertical"
MDTopAppBar:
title: "MDLabel"
MDBoxLayout:
orientation: "vertical"
'''
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
screen = Builder.load_string(KV)
# Names of standard color themes.
for name_theme in [
"Primary",
@ -96,7 +118,7 @@ and default is positioned on the left:
"Error",
"ContrastParentBackground",
]:
screen.ids.box.add_widget(
screen.add_widget(
MDLabel(
text=name_theme,
halign="center",
@ -121,7 +143,7 @@ in the ``text_color`` parameter:
text: "Custom color"
halign: "center"
theme_text_color: "Custom"
text_color: 0, 0, 1, 1
text_color: "blue"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-custom-color.png
:align: center
@ -138,26 +160,20 @@ parameter:
from kivymd.uix.label import MDLabel
from kivymd.font_definitions import theme_font_styles
KV = '''
MDScreen:
MDScrollView:
MDBoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "MDLabel"
ScrollView:
MDList:
id: box
MDList:
id: box
spacing: "8dp"
'''
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
screen = Builder.load_string(KV)
# Names of standard font styles.
for name_style in theme_font_styles[:-1]:
screen.ids.box.add_widget(
@ -165,6 +181,7 @@ parameter:
text=f"{name_style} style",
halign="center",
font_style=name_style,
adaptive_height=True,
)
)
return screen
@ -172,7 +189,273 @@ parameter:
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style.gif
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style.png
:align: center
Highlighting and copying labels
===============================
You can highlight labels by double tap on the label:
----------------------------------------------------
.. tabs::
.. tab:: Declarative KV style
.. code-block:: python
from kivy.lang.builder import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDLabel:
adaptive_size: True
pos_hint: {"center_x": .5, "center_y": .5}
text: "MDLabel"
allow_selection: True
padding: "4dp", "4dp"
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
Example().run()
.. tab:: Declarative Python style
.. code-block:: python
from kivy.lang.builder import Builder
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDLabel(
adaptive_size=True,
pos_hint={"center_x": .5, "center_y": .5},
text="MDLabel",
allow_selection=True,
padding=("4dp", "4dp"),
)
)
)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-allow-selection.gif
:align: center
You can copy the label text by double clicking on it:
-----------------------------------------------------
.. tabs::
.. tab:: Declarative KV style
.. code-block:: python
from kivy.lang.builder import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDLabel:
adaptive_size: True
pos_hint: {"center_x": .5, "center_y": .5}
text: "MDLabel"
padding: "4dp", "4dp"
allow_selection: True
allow_copy: True
on_copy: print("The text is copied to the clipboard")
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
Example().run()
.. tab:: Declarative Python style
.. code-block:: python
from kivy.lang.builder import Builder
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDLabel(
id="label",
adaptive_size=True,
pos_hint={"center_x": .5, "center_y": .5},
text="MDLabel",
allow_selection=True,
allow_copy=True,
padding=("4dp", "4dp"),
)
)
)
def on_start(self):
self.root.ids.label.bind(on_copy=self.on_copy)
def on_copy(self, instance_label: MDLabel):
print("The text is copied to the clipboard")
Example().run()
Example of copying/cutting labels using the context menu
--------------------------------------------------------
.. code-block:: python
from kivy.core.clipboard import Clipboard
from kivy.lang.builder import Builder
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.menu import MDDropdownMenu
from kivymd.toast import toast
KV = '''
MDBoxLayout:
orientation: "vertical"
spacing: "12dp"
padding: "24dp"
MDScrollView:
MDBoxLayout:
id: box
orientation: "vertical"
padding: "24dp"
spacing: "12dp"
adaptive_height: True
MDTextField:
max_height: "200dp"
mode: "fill"
multiline: True
MDWidget:
'''
data = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Sed blandit libero volutpat sed cras ornare arcu. Nisl vel pretium "
"lectus quam id leo in. Tincidunt arcu non sodales neque sodales ut etiam.",
"Elit scelerisque mauris pellentesque pulvinar pellentesque habitant. "
"Nisl rhoncus mattis rhoncus urna neque. Orci nulla pellentesque "
"dignissim enim. Ac auctor augue mauris augue neque gravida in fermentum. "
"Lacus suspendisse faucibus interdum posuere."
]
class CopyLabel(MDLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.allow_selection = True
self.adaptive_height = True
self.theme_text_color = "Custom"
self.text_color = self.theme_cls.text_color
class Example(MDApp):
context_menu = None
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
def on_start(self):
for text in data:
copy_label = CopyLabel(text=text)
copy_label.bind(
on_selection=self.open_context_menu,
on_cancel_selection=self.restore_text_color,
)
self.root.ids.box.add_widget(copy_label)
def click_item_context_menu(
self, type_click: str, instance_label: CopyLabel
) -> None:
Clipboard.copy(instance_label.text)
if type_click == "copy":
toast("Copied")
elif type_click == "cut":
self.root.ids.box.remove_widget(instance_label)
toast("Cut")
if self.context_menu:
self.context_menu.dismiss()
def restore_text_color(self, instance_label: CopyLabel) -> None:
instance_label.text_color = self.theme_cls.text_color
def open_context_menu(self, instance_label: CopyLabel) -> None:
instance_label.text_color = "black"
menu_items = [
{
"text": "Copy text",
"viewclass": "OneLineListItem",
"height": dp(48),
"on_release": lambda: self.click_item_context_menu(
"copy", instance_label
),
},
{
"text": "Cut text",
"viewclass": "OneLineListItem",
"height": dp(48),
"on_release": lambda: self.click_item_context_menu(
"cut", instance_label
),
},
]
self.context_menu = MDDropdownMenu(
caller=instance_label, items=menu_items, width_mult=3
)
self.context_menu.open()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/copying-cutting-labels-using-context-menu.gif
:align: center
.. MDIcon:
@ -217,6 +500,8 @@ MDIcon with badge icon
:align: center
"""
from __future__ import annotations
__all__ = ("MDLabel", "MDIcon")
import os
@ -224,6 +509,8 @@ from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.clipboard import Clipboard
from kivy.core.window import Window
from kivy.graphics import Color, Rectangle
from kivy.lang import Builder
from kivy.metrics import sp
@ -243,7 +530,7 @@ from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.theming_dynamic_text import get_contrast_text_color
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
from kivymd.uix.behaviors import DeclarativeBehavior, TouchBehavior
from kivymd.uix.floatlayout import MDFloatLayout
__MDLabel_colors__ = {
@ -264,7 +551,36 @@ with open(
Builder.load_string(kv_file.read())
class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
class MDLabel(
DeclarativeBehavior,
ThemableBehavior,
Label,
MDAdaptiveWidget,
TouchBehavior,
):
"""
Label class.
For more information, see in the
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.label.Label` and
:class:`~kivymd.uix.MDAdaptiveWidget` and
:class:`~kivymd.uix.behaviors.TouchBehavior`
classes documentation.
:Events:
`on_ref_press`
Called when the user clicks on a word referenced with a
``[ref]`` tag in a text markup.
`on_copy`
Called when double-tapping on the label.
`on_selection`
Called when double-tapping on the label.
`on_cancel_selection`
Called when the highlighting is removed from the label text.
"""
font_style = StringProperty("Body1")
"""
Label font style.
@ -316,29 +632,84 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
text_color = ColorProperty(None)
"""
Label text color in (r, g, b, a) format.
Label text color in (r, g, b, a) or string format.
:attr:`text_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
allow_copy = BooleanProperty(False)
"""
Allows you to copy text to the clipboard by double-clicking on the label.
.. versionadded:: 1.2.0
:attr:`allow_copy` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
allow_selection = BooleanProperty(False)
"""
Allows to highlight text by double-clicking on the label.
.. versionadded:: 1.2.0
:attr:`allow_selection` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
color_selection = ColorProperty(None)
"""
The color in (r, g, b, a) or string format of the text selection when the
value of the :attr:`allow_selection` attribute is True.
.. versionadded:: 1.2.0
:attr:`color_selection` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
color_deselection = ColorProperty(None)
"""
The color in (r, g, b, a) or string format of the text deselection when the
value of the :attr:`allow_selection` attribute is True.
.. versionadded:: 1.2.0
:attr:`color_deselection` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
is_selected = BooleanProperty(False)
"""
Is the label text highlighted.
.. versionadded:: 1.2.0
:attr:`is_selected` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
_text_color_str = StringProperty()
parent_background = ColorProperty(None)
can_capitalize = BooleanProperty(True)
canvas_bg = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bind(
font_style=self.update_font_style,
can_capitalize=self.update_font_style,
)
self.theme_cls.bind(theme_style=self._do_update_theme_color)
self.register_event_type("on_copy")
self.register_event_type("on_selection")
self.register_event_type("on_cancel_selection")
self.on_theme_text_color(None, self.theme_text_color)
self.update_font_style(None, "")
self.on_opposite_colors(None, self.opposite_colors)
Clock.schedule_once(self.check_font_styles)
self.theme_cls.bind(theme_style=self._do_update_theme_color)
def check_font_styles(self, interval: Union[int, float] = 0) -> bool:
if self.font_style not in list(self.theme_cls.font_styles.keys()):
@ -353,7 +724,8 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
if self.check_font_styles() is True:
font_info = self.theme_cls.font_styles[self.font_style]
self.font_name = font_info[0]
self.font_size = sp(font_info[1])
if self.font_style in list(self.theme_cls.font_styles.keys())[0:14]:
self.font_size = sp(font_info[1])
if font_info[2] and self.can_capitalize:
self._capitalizing = True
@ -363,6 +735,64 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
# TODO: Add letter spacing change
# self.letter_spacing = font_info[3]
def do_selection(self) -> None:
if not self.is_selected:
self.md_bg_color = (
self.theme_cls.primary_light
if not self.color_selection
else self.color_selection
)
def cancel_selection(self) -> None:
if self.is_selected:
self.md_bg_color = (
self.theme_cls.bg_normal
if not self.color_deselection
else self.color_deselection
)
self.dispatch("on_cancel_selection")
self.is_selected = False
def on_double_tap(self, touch, *args) -> None:
if self.allow_copy and self.collide_point(*touch.pos):
Clipboard.copy(self.text)
self.dispatch("on_copy")
if self.allow_selection and self.collide_point(*touch.pos):
self.do_selection()
self.dispatch("on_selection")
self.is_selected = True
def on_window_touch(self, *args):
if self.is_selected:
self.cancel_selection()
def on_copy(self, *args) -> None:
"""
Called when double-tapping on the label.
.. versionadded:: 1.2.0
"""
def on_selection(self, *args) -> None:
"""
Called when double-tapping on the label.
.. versionadded:: 1.2.0
"""
def on_cancel_selection(self, *args) -> None:
"""
Called when the highlighting is removed from the label text.
.. versionadded:: 1.2.0
"""
def on_allow_selection(self, instance_label, selection: bool) -> None:
if selection:
Window.bind(on_touch_down=self.on_window_touch)
else:
Window.unbind(on_touch_down=self.on_window_touch)
def on_theme_text_color(
self, instance_label, theme_text_color: str
) -> None:
@ -414,6 +844,7 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
def on_md_bg_color(self, instance_label, color: Union[list, str]) -> None:
self.canvas.remove_group("Background_instruction")
self.canvas.before.clear()
with self.canvas.before:
Color(rgba=color)
self.canvas_bg = Rectangle(pos=self.pos, size=self.size)
@ -445,6 +876,13 @@ class MDLabel(DeclarativeBehavior, ThemableBehavior, Label, MDAdaptiveWidget):
class MDIcon(MDFloatLayout, MDLabel):
"""
Icon class.
For more information, see in the :class:`~MDLabel` and
:class:`~kivymd.uix.floatlayout.MDFloatLayout` classes documentation.
"""
icon = StringProperty("android")
"""
Label icon name.
@ -465,7 +903,7 @@ class MDIcon(MDFloatLayout, MDLabel):
badge_icon_color = ColorProperty([1, 1, 1, 1])
"""
Badge icon color in (r, g, b, a) format.
Badge icon color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0
@ -475,7 +913,7 @@ class MDIcon(MDFloatLayout, MDLabel):
badge_bg_color = ColorProperty(None)
"""
Badge icon background color in (r, g, b, a) format.
Badge icon background color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0

View File

@ -984,6 +984,9 @@ class MDList(MDGridLayout):
When adding (or removing) a widget, it will resize itself to fit its
children, plus top and bottom paddings as described by the `MD` spec.
For more information, see in the
:class:`~kivymd.uix.gridlayout.MDGridLayout` classes documentation.
"""
_list_vertical_padding = NumericProperty("8dp")
@ -1002,6 +1005,13 @@ class BaseListItem(
):
"""
Base class to all ListItems. Not supposed to be instantiated on its own.
For more information, see in the
:class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivy.uix.floatlayout.FloatLayout` classes documentation.
"""
text = StringProperty()
@ -1242,7 +1252,12 @@ class IRightBodyTouch:
class OneLineListItem(BaseListItem):
"""A one line list item."""
"""
A one line list item.
For more information, see in the :class:`~BaseListItem`
classes documentation.
"""
_txt_top_pad = NumericProperty("16dp")
_txt_bot_pad = NumericProperty("15dp")
@ -1255,7 +1270,12 @@ class OneLineListItem(BaseListItem):
class TwoLineListItem(BaseListItem):
"""A two line list item."""
"""
A two line list item.
For more information, see in the :class:`~BaseListItem`
classes documentation.
"""
_txt_top_pad = NumericProperty("20dp")
_txt_bot_pad = NumericProperty("15dp")
@ -1267,7 +1287,12 @@ class TwoLineListItem(BaseListItem):
class ThreeLineListItem(BaseListItem):
"""A three line list item."""
"""
A three line list item.
For more information, see in the :class:`~BaseListItem`
classes documentation.
"""
_txt_top_pad = NumericProperty("16dp")
_txt_bot_pad = NumericProperty("15dp")
@ -1280,6 +1305,13 @@ class ThreeLineListItem(BaseListItem):
class OneLineAvatarListItem(BaseListItem):
"""
A one line list item with left image.
For more information, see in the :class:`~BaseListItem`
classes documentation.
"""
_txt_left_pad = NumericProperty("72dp")
_txt_top_pad = NumericProperty("20dp")
_txt_bot_pad = NumericProperty("19dp")
@ -1292,6 +1324,13 @@ class OneLineAvatarListItem(BaseListItem):
class TwoLineAvatarListItem(OneLineAvatarListItem):
"""
A two line list item with left image.
For more information, see in the :class:`~OneLineAvatarListItem`
classes documentation.
"""
_txt_top_pad = NumericProperty("20dp")
_txt_bot_pad = NumericProperty("15dp")
_height = NumericProperty()
@ -1303,6 +1342,13 @@ class TwoLineAvatarListItem(OneLineAvatarListItem):
class ThreeLineAvatarListItem(ThreeLineListItem):
"""
A three line list item with left image.
For more information, see in the :class:`~ThreeLineListItem`
classes documentation.
"""
_txt_left_pad = NumericProperty("72dp")
def __init__(self, *args, **kwargs):
@ -1310,10 +1356,24 @@ class ThreeLineAvatarListItem(ThreeLineListItem):
class OneLineIconListItem(OneLineListItem):
"""
A one line list item with left icon.
For more information, see in the :class:`~OneLineListItem`
classes documentation.
"""
_txt_left_pad = NumericProperty("72dp")
class TwoLineIconListItem(OneLineIconListItem):
"""
A two line list item with left icon.
For more information, see in the :class:`~OneLineIconListItem`
classes documentation.
"""
_txt_top_pad = NumericProperty("20dp")
_txt_bot_pad = NumericProperty("15dp")
_height = NumericProperty()
@ -1325,10 +1385,24 @@ class TwoLineIconListItem(OneLineIconListItem):
class ThreeLineIconListItem(ThreeLineListItem):
"""
A three line list item with left icon.
For more information, see in the :class:`~ThreeLineListItem`
classes documentation.
"""
_txt_left_pad = NumericProperty("72dp")
class OneLineRightIconListItem(OneLineListItem):
"""
A one line list item with right icon/image.
For more information, see in the :class:`~OneLineListItem`
classes documentation.
"""
_txt_right_pad = NumericProperty("40dp")
def __init__(self, *args, **kwargs):
@ -1337,6 +1411,13 @@ class OneLineRightIconListItem(OneLineListItem):
class TwoLineRightIconListItem(OneLineRightIconListItem):
"""
A two line list item with right icon/image.
For more information, see in the :class:`~OneLineRightIconListItem`
classes documentation.
"""
_txt_top_pad = NumericProperty("20dp")
_txt_bot_pad = NumericProperty("15dp")
_height = NumericProperty()
@ -1348,6 +1429,13 @@ class TwoLineRightIconListItem(OneLineRightIconListItem):
class ThreeLineRightIconListItem(ThreeLineListItem):
"""
A three line list item with right icon/image.
For more information, see in the :class:`~ThreeLineRightIconListItem`
classes documentation.
"""
_txt_right_pad = NumericProperty("40dp")
def __init__(self, **kwargs):
@ -1356,6 +1444,13 @@ class ThreeLineRightIconListItem(ThreeLineListItem):
class OneLineAvatarIconListItem(OneLineAvatarListItem):
"""
A one line list item with left/right icon/image/widget.
For more information, see in the :class:`~OneLineAvatarListItem`
classes documentation.
"""
_txt_right_pad = NumericProperty("40dp")
def __init__(self, *args, **kwargs):
@ -1364,6 +1459,13 @@ class OneLineAvatarIconListItem(OneLineAvatarListItem):
class TwoLineAvatarIconListItem(TwoLineAvatarListItem):
"""
A two line list item with left/right icon/image/widget.
For more information, see in the :class:`~TwoLineAvatarListItem`
classes documentation.
"""
_txt_right_pad = NumericProperty("40dp")
def __init__(self, *args, **kwargs):
@ -1372,6 +1474,13 @@ class TwoLineAvatarIconListItem(TwoLineAvatarListItem):
class ThreeLineAvatarIconListItem(ThreeLineAvatarListItem):
"""
A three line list item with left/right icon/image/widget.
For more information, see in the :class:`~ThreeLineAvatarListItem`
classes documentation.
"""
_txt_right_pad = NumericProperty("40dp")
def __init__(self, *args, **kwargs):
@ -1388,13 +1497,31 @@ class TouchBehavior:
class ImageLeftWidget(
CircularRippleBehavior, ButtonBehavior, ILeftBodyTouch, FitImage
):
pass
"""
The widget implements the left image for use in ListItem classes.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~ILeftBodyTouch` and
:class:`~kivymd.uix.fitimage.FitImage` classes documentation.
"""
class ImageLeftWidgetWithoutTouch(
CircularRippleBehavior, TouchBehavior, ButtonBehavior, ILeftBody, FitImage
):
"""
Disables the image event.
The widget implements the left image for use in `ListItem` classes.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~TouchBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~ILeftBody` and
:class:`~kivymd.uix.fitimage.FitImage` classes documentation.
.. versionadded:: 1.0.0
"""
@ -1404,13 +1531,31 @@ class ImageLeftWidgetWithoutTouch(
class ImageRightWidget(
CircularRippleBehavior, ButtonBehavior, IRightBodyTouch, FitImage
):
pass
"""
The widget implements the right image for use in ListItem classes.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~IRightBodyTouch` and
:class:`~kivymd.uix.fitimage.FitImage` classes documentation.
"""
class ImageRightWidgetWithoutTouch(
CircularRippleBehavior, TouchBehavior, ButtonBehavior, IRightBody, FitImage
):
"""
Disables the image event.
The widget implements the right image for use in `ListItem` classes.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~TouchBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~IRightBody` and
:class:`~kivymd.uix.fitimage.FitImage` classes documentation.
.. versionadded:: 1.0.0
"""
@ -1418,11 +1563,29 @@ class ImageRightWidgetWithoutTouch(
class IconRightWidget(IRightBodyTouch, MDIconButton):
"""
The widget implements the right icon for use in ListItem classes.
For more information, see in the
:class:`~IRightBodyTouch` and
:class:`~kivymd.uix.button.MDIconButton`
classes documentation.
"""
pos_hint = {"center_y": 0.5}
class IconRightWidgetWithoutTouch(TouchBehavior, IRightBody, MDIconButton):
"""
Disables the icon event.
The widget implements the right icon for use in ListItem classes.
For more information, see in the
:class:`~TouchBehavior` and
:class:`~IRightBody` and
:class:`~kivymd.uix.button.MDIconButton`
classes documentation.
.. versionadded:: 1.0.0
"""
@ -1431,11 +1594,29 @@ class IconRightWidgetWithoutTouch(TouchBehavior, IRightBody, MDIconButton):
class IconLeftWidget(ILeftBodyTouch, MDIconButton):
"""
The widget implements the left icon for use in ListItem classes.
For more information, see in the
:class:`~ILeftBodyTouch` and
:class:`~kivymd.uix.button.MDIconButton`
classes documentation.
"""
pos_hint = {"center_y": 0.5}
class IconLeftWidgetWithoutTouch(TouchBehavior, ILeftBody, MDIconButton):
"""
Disables the icon event.
The widget implements the left icon for use in ListItem classes.
For more information, see in the
:class:`~TouchBehavior` and
:class:`~ILeftBody` and
:class:`~kivymd.uix.button.MDIconButton`
classes documentation.
.. versionadded:: 1.0.0
"""
@ -1444,4 +1625,11 @@ class IconLeftWidgetWithoutTouch(TouchBehavior, ILeftBody, MDIconButton):
class CheckboxLeftWidget(ILeftBodyTouch, MDCheckbox):
pass
"""
The widget implements the left checkbox element for use in ListItem classes.
For more information, see in the
:class:`~ILeftBodyTouch` and
:class:`~kivymd.uix.selectioncontrol.MDCheckbox`
classes documentation.
"""

View File

@ -1,26 +1,9 @@
#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
<RightContent>
adaptive_width: True
<MDMenuItemIcon>
IconLeftWidget:
id: icon_widget
icon: root.icon
<MDMenu>
size_hint: None, None
width: root.width_mult * STANDARD_INCREMENT
bar_width: 0
key_viewclass: "viewclass"
key_size: "height"
RecycleBoxLayout:
padding: 0, "4dp", 0, "4dp"
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
@ -28,32 +11,478 @@
orientation: "vertical"
<MenuContainer@MDCard>
<MDDropdownTrailingTextItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "12dp", 0, "12dp", 0
MDLabel:
text: root.text
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
size_hint_x: None
width:
root.width - \
( \
+ trailing_container.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
MDTrailingTextContainer:
id: trailing_container
text: root.trailing_text
adaptive_width: True
theme_text_color: "Custom" if root.trailing_text_color else "Primary"
text_color:
root.trailing_text_color \
if root.trailing_text_color else \
app.theme_cls.text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownLeadingIconTrailingTextItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "10dp", 0, "16dp", 0
MDIcon:
id: leading_icon
icon: root.leading_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.leading_icon_color else "Primary"
text_color:
root.leading_icon_color \
if root.leading_icon_color else \
app.theme_cls.text_color
MDLabel:
text: root.text
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
size_hint_x: None
width:
root.width - \
( \
leading_icon.width \
+ trailing_container.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
+ dp(18) \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
Widget:
MDTrailingTextContainer:
id: trailing_container
text: root.trailing_text
adaptive_width: True
theme_text_color: "Custom" if root.trailing_text_color else "Primary"
text_color:
root.trailing_text_color \
if root.trailing_text_color else \
app.theme_cls.text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownTrailingIconItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "12dp", 0, "12dp", 0
MDLabel:
id: label
text: root.text
shorten: True
size_hint_x: None
shorten_from: "right"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
width:
root.width - \
( \
+ trailing_icon.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
+ dp(18) \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
Widget:
MDIcon:
id: trailing_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
icon: root.trailing_icon
theme_text_color: "Custom" if root.trailing_icon_color else "Primary"
text_color:
root.trailing_icon_color \
if root.trailing_icon_color else \
app.theme_cls.text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDTrailingIconTextContainer>
adaptive_width: True
MDIcon:
icon: root.trailing_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.trailing_icon_color else "Primary"
text_color:
root.trailing_icon_color \
if root.trailing_icon_color else \
app.theme_cls.text_color
MDLabel:
text: root.trailing_text
adaptive_size: True
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.trailing_text_color else "Primary"
text_color:
root.trailing_text_color \
if root.trailing_text_color else \
app.theme_cls.text_color
<MDDropdownTrailingIconTextItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "12dp", 0, "12dp", 0
MDLabel:
id: label
text: root.text
shorten: True
size_hint_x: None
shorten_from: "right"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
width:
root.width - \
( \
+ trailing_container.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
MDTrailingIconTextContainer:
id: trailing_container
trailing_icon: root.trailing_icon
trailing_text: root.trailing_text
trailing_text_color: root.trailing_text_color
trailing_icon_color: root.trailing_icon_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownTextItem>
orientation: "vertical"
MDLabel:
text: root.text
valign: "center"
padding_x: "12dp"
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownLeadingTrailingIconTextItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "10dp", 0, "16dp", 0
MDIcon:
id: leading_icon
icon: root.leading_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.leading_icon_color else "Primary"
text_color:
root.leading_icon_color \
if root.leading_icon_color else \
app.theme_cls.text_color
MDLabel:
text: root.text
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
size_hint_x: None
width:
root.width - \
( \
leading_icon.width \
+ trailing_container.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
+ dp(18) \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
Widget:
MDTrailingIconTextContainer:
id: trailing_container
trailing_icon: root.trailing_icon
trailing_text: root.trailing_text
trailing_icon_color: root.trailing_icon_color
trailing_text_color: root.trailing_text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownLeadingTrailingIconItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "10dp", 0, "12dp", 0
MDIcon:
id: leading_icon
icon: root.leading_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.leading_icon_color else "Primary"
text_color:
root.leading_icon_color \
if root.leading_icon_color else \
app.theme_cls.text_color
MDLabel:
id: label
text: root.text
shorten: True
size_hint_x: None
shorten_from: "right"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
width:
root.width - \
( \
leading_icon.width \
+ trailing_icon.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
+ dp(18) \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
Widget:
MDIcon:
id: trailing_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
icon: root.trailing_icon
theme_text_color: "Custom" if root.trailing_icon_color else "Primary"
text_color:
root.trailing_icon_color \
if root.trailing_icon_color else \
app.theme_cls.text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownLeadingIconItem>
orientation: "vertical"
MDBoxLayout:
id: container
spacing: "12dp"
padding: "12dp", 0, "12dp", 0
MDIcon:
id: leading_icon
icon: root.leading_icon
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.leading_icon_color else "Primary"
text_color:
root.leading_icon_color \
if root.leading_icon_color else \
app.theme_cls.text_color
MDLabel:
id: label
text: root.text
shorten: True
size_hint_x: None
shorten_from: "right"
pos_hint: {"center_y": .5}
theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
width:
root.width - \
( \
leading_icon.width \
+ container.padding[0] \
+ container.padding[2] \
+ container.spacing \
)
text_color:
root.text_color \
if root.text_color else \
app.theme_cls.text_color
MDSeparator:
md_bg_color:
( \
self.theme_cls.divider_color \
if not root.divider_color \
else root.divider_color \
) \
if root.divider else \
(0, 0, 0, 0)
<MDDropdownMenu>
orientation: "vertical"
elevation: root.elevation
shadow_radius: root.shadow_radius
shadow_softness: root.shadow_softness
shadow_offset: root.shadow_offset
shadow_color: root.shadow_color
shadow_color: root.shadow_color
radius: root.radius
size_hint: None, None
MenuContainer:
id: card
orientation: "vertical"
elevation: root.elevation
size_hint: None, None
size: md_menu.size[0], md_menu.size[1] + content_header.height
pos: md_menu.pos
opacity: md_menu.opacity
radius: root.radius
md_bg_color:
root.background_color \
if root.background_color else root.theme_cls.bg_dark
MDBoxLayout:
id: content_header
adaptive_size: True
MDBoxLayout:
id: content_header
adaptive_size: True
MDMenu:
id: md_menu
drop_cls: root
width_mult: root.width_mult
size_hint: None, None
size: 0, 0
opacity: 0
MDMenu:
id: md_menu
drop_cls: root

File diff suppressed because it is too large Load Diff

View File

@ -575,8 +575,8 @@ class NavigationDrawerContentError(Exception):
class MDNavigationLayout(MDFloatLayout):
"""
For more information, see in the :class:`~kivymd.uix.floatlayout.MDFloatLayout`
class documentation.
For more information, see in the
:class:`~kivymd.uix.floatlayout.MDFloatLayout` class documentation.
"""
_scrim_color = ObjectProperty(None)
@ -737,7 +737,7 @@ class MDNavigationDrawerDivider(MDBoxLayout):
color = ColorProperty(None)
"""
Divider color in ``rgba`` format.
Divider color in (r, g, b, a) or string format.
:attr:`color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -811,7 +811,7 @@ class MDNavigationDrawerHeader(MDBoxLayout):
title_color = ColorProperty(None)
"""
Title text color.
Title text color in (r, g, b, a) or string format.
:attr:`title_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -851,7 +851,7 @@ class MDNavigationDrawerHeader(MDBoxLayout):
text_color = ColorProperty(None)
"""
Title text color.
Title text color in (r, g, b, a) or string format.
:attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -893,7 +893,9 @@ class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior):
Implements an item for the :class:`~MDNavigationDrawer` menu list.
For more information, see in the
:class:`~kivymd.uix.list.OneLineAvatarIconListItem` class documentation.
:class:`~kivymd.uix.list.OneLineAvatarIconListItem` and
:class:`~kivymd.uix.behaviors.FocusBehavior`
class documentation.
.. versionadded:: 1.0.0
@ -936,7 +938,7 @@ class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior):
icon_color = ColorProperty(None)
"""
Icon color item.
Icon color in (r, g, b, a) or string format item.
:attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -944,7 +946,8 @@ class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior):
selected_color = ColorProperty([0, 0, 0, 1])
"""
The color of the icon and text of the selected item.
The color in (r, g, b, a) or string format of the icon and text of the
selected item.
:attr:`selected_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 1]`.
@ -960,7 +963,7 @@ class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior):
text_right_color = ColorProperty(None)
"""
Right text color item.
Right text color item in (r, g, b, a) or string format.
:attr:`text_right_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -1095,9 +1098,9 @@ class MDNavigationDrawer(MDCard):
# FIXME: Doesn't work in Kivy v2.1.0.
scrim_color = ColorProperty([0, 0, 0, 0.5])
"""
Color for scrim. Alpha channel will be multiplied with
:attr:`_scrim_alpha`. Set fourth channel to 0 if you want to disable
scrim.
Color for scrim in (r, g, b, a) or string format. Alpha channel will be
multiplied with :attr:`_scrim_alpha`. Set fourth channel to 0 if you want
to disable scrim.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-scrim-color.png
:align: center

View File

@ -8,26 +8,14 @@ Components/NavigationRail
`Material Design spec, Navigation rail <https://m3.material.io/components/navigation-rail/specs>`_
.. rubric::
Navigation rails provide access to primary destinations in apps when using
tablet and desktop screens.
.. rubric:: Navigation rails provide access to primary destinations in apps
when using tablet and desktop screens.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail.png
:align: center
Usage
=====
.. code-block:: kv
MDNavigationRail:
MDNavigationRailItem:
MDNavigationRailItem:
MDNavigationRailItem:
-----
.. tabs::
@ -113,6 +101,21 @@ Usage
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-usage.png
:align: center
Anatomy
-------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anatomy.png
:align: center
1. Container
2. Label text (optional)
3. Icon
4. Active indicator
5. Badge (optional)
6. Large badge (optional)
7. Large badge label (optional)
8. Menu icon (optional)
Example
=======
@ -137,9 +140,8 @@ Example
<ExtendedButton>
elevation: 3.5
elevation: 1
shadow_radius: 12
shadow_softness: 4
-height: "56dp"
@ -207,9 +209,9 @@ Example
MDNavigationDrawer:
id: nav_drawer
radius: (0, 16, 16, 0)
radius: 0, 16, 16, 0
md_bg_color: "#fffcf4"
elevation: 4
elevation: 2
width: "240dp"
MDNavigationDrawerMenu:
@ -218,14 +220,18 @@ Example
orientation: "vertical"
adaptive_height: True
spacing: "12dp"
padding: "3dp", 0, 0, "12dp"
padding: 0, 0, 0, "12dp"
MDIconButton:
icon: "menu"
ExtendedButton:
text: "Compose"
icon: "pencil"
MDBoxLayout:
adaptive_height: True
padding: "12dp", 0, 0, 0
ExtendedButton:
text: "Compose"
icon: "pencil"
DrawerClickableItem:
text: "Python"
@ -269,7 +275,9 @@ Example
def set_radius(self, *args):
if self.rounded_button:
self._radius = self.radius = self.height / 4
value = self.height / 4
self.radius = [value, value, value, value]
self._radius = value
class Example(MDApp):
@ -357,9 +365,8 @@ Example
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.padding = "16dp"
self.elevation = 3.5
self.elevation = 1
self.shadow_radius = 12
self.shadow_softness = 4
self.height = dp(56)
Clock.schedule_once(self.set_spacing)
@ -439,9 +446,13 @@ Example
MDIconButton(
icon="menu",
),
ExtendedButton(
text="Compose",
icon="pencil",
MDBoxLayout(
ExtendedButton(
text="Compose",
icon="pencil",
),
adaptive_height=True,
padding=["12dp", 0, 0, 0],
),
orientation="vertical",
adaptive_height=True,
@ -540,6 +551,7 @@ from typing import Union
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
@ -556,7 +568,6 @@ from kivy.properties import (
from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import ScaleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFloatingActionButton, MDIconButton
@ -591,7 +602,12 @@ class RippleWidget(MDWidget, ScaleBehavior):
class MDNavigationRailFabButton(MDFloatingActionButton):
"""Implements an optional floating action button (FAB)."""
"""
Implements an optional floating action button (FAB).
For more information, see in the
:class:`~kivymd.uix.button.MDFloatingActionButton` class documentation.
"""
icon = StringProperty("pencil")
"""
@ -613,7 +629,12 @@ class MDNavigationRailFabButton(MDFloatingActionButton):
class MDNavigationRailMenuButton(MDIconButton):
"""Implements a menu button."""
"""
Implements a menu button.
For more information, see in the
:class:`~kivymd.uix.button.MDIconButton` classes documentation.
"""
icon = StringProperty("menu")
"""
@ -634,8 +655,15 @@ class MDNavigationRailMenuButton(MDIconButton):
"""
class MDNavigationRailItem(ThemableBehavior, ButtonBehavior, MDBoxLayout):
"""Implements a menu item with an icon and text."""
class MDNavigationRailItem(ButtonBehavior, MDBoxLayout):
"""
Implements a menu item with an icon and text.
For more information, see in the
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.boxlayout.MDBoxLayout`
classes documentation.
"""
navigation_rail = ObjectProperty()
"""
@ -814,6 +842,11 @@ class MDNavigationRailItem(ThemableBehavior, ButtonBehavior, MDBoxLayout):
class MDNavigationRail(MDCard):
"""
Navigation rail class.
For more information, see in the
:class:`~kivymd.uix.card.MDCard` class documentation.
:Events:
:attr:`on_item_press`
Called on the `on_press` event of menu item -
@ -941,7 +974,8 @@ class MDNavigationRail(MDCard):
text_color_item_normal = ColorProperty(None)
"""
The text color of the normal menu item (:class:`~MDNavigationRailItem`).
The text color in (r, g, b, a) or string format of the normal menu item
(:class:`~MDNavigationRailItem`).
.. code-block:: kv
@ -960,7 +994,8 @@ class MDNavigationRail(MDCard):
text_color_item_active = ColorProperty(None)
"""
The text color of the active menu item (:class:`~MDNavigationRailItem`).
The text color in (r, g, b, a) or string format of the active menu item
(:class:`~MDNavigationRailItem`).
.. code-block:: kv
@ -979,7 +1014,8 @@ class MDNavigationRail(MDCard):
icon_color_item_normal = ColorProperty(None)
"""
The icon color of the normal menu item (:class:`~MDNavigationRailItem`).
The icon color in (r, g, b, a) or string format of the normal menu item
(:class:`~MDNavigationRailItem`).
.. code-block:: kv
@ -998,7 +1034,8 @@ class MDNavigationRail(MDCard):
icon_color_item_active = ColorProperty(None)
"""
The icon color of the active menu item (:class:`~MDNavigationRailItem`).
The icon color in (r, g, b, a) or string format of the active menu item
(:class:`~MDNavigationRailItem`).
.. code-block:: kv
@ -1110,6 +1147,9 @@ class MDNavigationRail(MDCard):
self.register_event_type("on_item_press")
self.register_event_type("on_item_release")
def on_size(self, *args):
Clock.schedule_once(self.set_pos_menu_fab_buttons)
def on_item_press(self, *args) -> None:
"""
Called on the `on_press` event of menu item -
@ -1188,7 +1228,7 @@ class MDNavigationRail(MDCard):
items[index].dispatch("on_press")
items[index].dispatch("on_release")
def set_pos_menu_fab_buttons(self, interval: Union[int, float]) -> None:
def set_pos_menu_fab_buttons(self, *args) -> None:
"""
Sets the position of the :class:`~MDNavigationRailFabButton` and
:class:`~MDNavigationRailMenuButton` buttons on the panel.

View File

@ -100,7 +100,6 @@ from PIL import ImageDraw
from kivymd import uix_path
from kivymd.color_definitions import colors as _colors
from kivymd.color_definitions import text_colors
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
from kivymd.uix.boxlayout import MDBoxLayout
@ -123,9 +122,11 @@ class TypeColorButton(MDRaisedButton, MDToggleButton):
'RGBA', 'HEX', 'RGB'.
"""
theme_text_color = "Custom"
text_color = (0, 0, 0, 1)
elevation = 0
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.theme_text_color = "Custom"
self.text_color = (0, 0, 0, 1)
self.elevation = 0
class SelectAlphaChannelWidget(MDBoxLayout):
@ -186,7 +187,7 @@ class SliderTab(MDBoxLayout):
"""Basic event handler for changing the slider value."""
class GradientTab(ThemableBehavior, MDBoxLayout):
class GradientTab(MDBoxLayout):
"""
The class implements a tab with a gradient, a color selection scale and
a scale for setting the transparency value of the selected color.
@ -398,8 +399,8 @@ class MDColorPicker(BaseDialog):
default_color = ColorProperty(None, allownone=True)
"""
Default color value The set color value will be used when you open the
dialog.
Default color value in (r, g, b, a) or string format. The set color value
will be used when you open the dialog.
:attr:`default_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -416,7 +417,8 @@ class MDColorPicker(BaseDialog):
background_down_button_selected_type_color = ColorProperty([1, 1, 1, 0.3])
"""
Button background for choosing a color type ('RGBA', 'HEX', 'HSL', 'RGB').
Button background for choosing a color type ('RGBA', 'HEX', 'HSL', 'RGB')
in (r, g, b, a) or string format.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/color-picker-background-down-button-selected-type-color.png
:align: center

View File

@ -31,9 +31,7 @@
canvas:
Color:
rgb:
app.theme_cls.primary_color \
if not root.primary_color else root.primary_color
rgb: root.primary_color or app.theme_cls.primary_color
RoundedRectangle:
size:
(dp(328), dp(120)) \
@ -48,9 +46,7 @@
if root.theme_cls.device_orientation == "portrait" \
else (root.radius[0], dp(0), dp(0), root.radius[3])
Color:
rgba:
app.theme_cls.bg_normal \
if not root.accent_color else root.accent_color
rgba: root.accent_color or app.theme_cls.bg_normal
RoundedRectangle:
size:
(dp(328), dp(512) - dp(120) - root._shift_dialog_height) \
@ -79,9 +75,7 @@
(dp(24), root.height - self.height - dp(18)) \
if root.theme_cls.device_orientation == "portrait" \
else (dp(24), root.height - self.height - dp(24))
text_color:
root.specific_text_color \
if not root.text_toolbar_color else root.text_toolbar_color
text_color: root.text_toolbar_color or root.specific_text_color
MDLabel:
id: label_full_date
@ -100,28 +94,13 @@
dp(24) if not root._input_date_dialog_open else dp(168) + dp(24), \
root.height - self.height - dp(96) \
)
text:
root.set_text_full_date(root.sel_year, root.sel_month, root.sel_day, \
root.theme_cls.device_orientation)
text: root._date_label_text
text_color:
( \
root.specific_text_color \
if not root.text_toolbar_color else root.text_toolbar_color \
) \
if root.theme_cls.device_orientation == "portrait" \
else \
( \
( \
self.theme_cls.primary_color \
if not root.primary_color else root.primary_color \
) \
if root._input_date_dialog_open \
else \
( \
root.specific_text_color \
if not root.text_toolbar_color else root.text_toolbar_color \
) \
)
root.text_toolbar_color or root.specific_text_color \
if root.theme_cls.device_orientation == "portrait" else \
root.primary_color or self.theme_cls.primary_color \
if root._input_date_dialog_open else \
root.text_toolbar_color or root.specific_text_color
RecycleView:
id: _year_layout
@ -164,9 +143,7 @@
(root.height - dp(120) + dp(12)) \
if root.theme_cls.device_orientation == "portrait" \
else dp(12)
text_color:
root.specific_text_color \
if not root.text_toolbar_color else root.text_toolbar_color
text_color: root.text_toolbar_color or root.specific_text_color
MDLabel:
id: label_month_selector
@ -180,9 +157,7 @@
(dp(24), root.height - dp(120) - self.height - dp(20)) \
if root.theme_cls.device_orientation == "portrait" \
else (dp(168) + dp(24), label_title.y)
text_color:
app.theme_cls.text_color \
if not root.text_color else root.text_color
text_color: root.text_color or app.theme_cls.text_color
DatePickerIconTooltipButton:
id: triangle
@ -199,9 +174,7 @@
(label_month_selector.width + dp(14), root.height - dp(123) - self.height) \
if root.theme_cls.device_orientation == "portrait" \
else (dp(180) + label_month_selector.width, label_title.y - dp(14))
text_color:
app.theme_cls.text_color \
if not root.text_color else root.text_color
text_color: root.text_color or app.theme_cls.text_color
md_bg_color_disabled: 0, 0, 0, 0
DatePickerIconTooltipButton:
@ -218,9 +191,7 @@
root.height - dp(120) - self.height / 2 - dp(30) \
if root.theme_cls.device_orientation == "portrait" \
else dp(272)
text_color:
app.theme_cls.text_color \
if not root.text_color else root.text_color
text_color: root.text_color or app.theme_cls.text_color
DatePickerIconTooltipButton:
id: chevron_right
@ -236,9 +207,7 @@
root.height - dp(120) - self.height / 2 - dp(30) \
if root.theme_cls.device_orientation == "portrait" \
else dp(272)
text_color:
app.theme_cls.text_color \
if not root.text_color else root.text_color
text_color: root.text_color or app.theme_cls.text_color
# TODO: Replace the GridLayout with a RecycleView
# if it improves performance.
@ -280,10 +249,7 @@
text: "OK"
theme_text_color: "Custom"
font_name: root.font_name
text_color:
root.theme_cls.primary_color \
if not root.text_button_color else \
root.text_button_color
text_color: root.text_button_color or root.theme_cls.primary_color
on_release: root.on_ok_button_pressed()
MDFlatButton:
@ -293,10 +259,7 @@
theme_text_color: "Custom"
pos: root.width - self.width - ok_button.width - dp(10), dp(10)
font_name: root.font_name
text_color:
root.theme_cls.primary_color \
if not root.text_button_color else \
root.text_button_color
text_color: root.text_button_color or root.theme_cls.primary_color
<DatePickerDaySelectableItem>
@ -312,14 +275,8 @@
canvas.before:
Color:
rgba:
(\
self.owner.selector_color[:-1] + [.3] \
if self.owner.selector_color \
else self.theme_cls.primary_color[:-1] + [.3] \
) \
if not self.disabled \
and self.text \
and self.check_date(self.owner.year, self.owner.month, int(self.text)) \
(self.owner.selector_color or self.theme_cls.primary_color)[:-1] + [.3] \
if self.is_in_range \
else (0, 0, 0, 0)
RoundedRectangle:
size:
@ -327,55 +284,24 @@
if root.theme_cls.device_orientation == "portrait" \
else \
(dp(32), dp(28)) \
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] \
if self.is_range_end or self.is_week_end or self.is_month_end \
else (dp(46), dp(28))
pos:
(self.x - dp(1.5), self.y + dp(5)) \
if root.theme_cls.device_orientation == "portrait" else \
(self.x, self.y + 1)
radius:
[0, 0, 0, 0] if not self.owner._date_range else \
( \
[self.width / 2, 0, 0, self.width / 2] \
if self.text and self.owner._date_range[0] == date( \
self.current_year, \
self.current_month, \
int(self.text) \
) \
or (self.index in [0, 7, 14, 21, 28] and root.is_selected) \
else \
( \
[0, 0, 0, 0] if self.text \
and self.owner._date_range[-1] != date( \
self.current_year, \
self.current_month, \
int(self.text) \
) \
and self.index not in [6, 13, 20, 27, 30] \
else [0, self.width / 2, self.width, 0] \
if root.is_selected or self.text \
and self.owner._date_range[-1] == date( \
self.current_year, \
self.current_month, \
int(self.text) \
) \
else [0, 0, 0, 0]) \
)
[
self.width / 2 if self.is_range_start else 0,
self.width / 2 if self.is_range_end else 0,
self.width / 2 if self.is_range_end else 0,
self.width / 2 if self.is_range_start else 0,
]
# Selection circle.
Color:
rgba:
( \
self.theme_cls.primary_color if not root.owner.selector_color \
else root.owner.selector_color \
) \
root.owner.selector_color or self.theme_cls.primary_color \
if root.is_selected and not self.disabled \
else (0, 0, 0, 0)
Ellipse:
@ -393,25 +319,11 @@
font_name: root.owner.font_name
theme_text_color: "Custom"
text_color:
( \
root.theme_cls.primary_color \
if not root.owner.text_current_color \
else root.owner.text_current_color \
) \
if root.is_today and not root.is_selected \
else ( \
( \
root.theme_cls.text_color \
if not root.is_selected or root.owner.mode == "range" \
else (1, 1, 1, 1) \
) \
if not root.owner.text_color \
else \
( \
root.owner.text_color \
if not root.is_selected else (1, 1, 1, 1)) \
)
root.owner.accent_color or root.theme_cls.bg_normal \
if root.is_selected else \
root.owner.text_current_color or root.theme_cls.primary_color \
if root.is_today else \
root.owner.text_color or root.theme_cls.text_color
<DatePickerWeekdayLabel>
font_style: "Caption"
@ -425,9 +337,7 @@
size:
(dp(40), dp(40)) if root.theme_cls.device_orientation == "portrait" \
else (dp(32), dp(32))
text_color:
app.theme_cls.disabled_hint_text_color \
if not root.owner.text_weekday_color else root.owner.text_weekday_color
text_color: root.owner.text_weekday_color or app.theme_cls.disabled_hint_text_color
<DatePickerYearSelectableItem>
@ -436,13 +346,21 @@
valign: "middle"
halign: "center"
text: root.text
theme_text_color: "Custom"
text_color:
(0, 0, 0, 0) \
if self.owner is None else \
self.owner.accent_color or self.owner.theme_cls.bg_normal \
if self.selected else \
self.owner.text_color or self.owner.theme_cls.text_color
on_text: root.font_name = root.owner.font_name
canvas.before:
Color:
rgba:
root.selected_color if root.selected_color \
else self.theme_cls.primary_color
self.owner.selector_color or self.theme_cls.primary_color \
if self.selected else \
(0, 0, 0, 0)
RoundedRectangle:
pos: self.x + dp(12), self.y
size: self.width - dp(24), self.height
@ -453,6 +371,7 @@
adaptive_height: True
size_hint_x: None
spacing: dp(8)
opacity: 0
width:
self.owner.width - dp(48) \
if root.owner.theme_cls.device_orientation == "portrait" \
@ -468,10 +387,6 @@
<DatePickerInputField>
mode: "fill"
opacity: 0
hint_text: "dd/mm/yyyy"
input_filter: root.input_filter
fill_color:
(0, 0, 0, .15) \
if not self.owner.input_field_background_color \
else root.owner.input_field_background_color
fill_color: root.owner.input_field_background_color or (0, 0, 0, .15)

View File

@ -203,7 +203,6 @@ from datetime import date
from itertools import zip_longest
from typing import Union
from kivy import Logger
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.metrics import dp
@ -253,6 +252,12 @@ class BaseDialogPicker(
Base class for :class:`~kivymd.uix.picker.MDDatePicker` and
:class:`~kivymd.uix.picker.MDTimePicker` classes.
For more information, see in the
:class:`~kivymd.uix.dialog.BaseDialog` and
:class:`~kivymd.uix.behaviors.CommonElevationBehavior` and
:class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior`
classes documentation.
:Events:
`on_save`
Events called when the "OK" dialog box button is clicked.
@ -644,11 +649,30 @@ class DatePickerTypeDateError(Exception):
class DatePickerInputField(MDTextField):
"""Implements date input in dd/mm/yyyy format."""
"""
Implements date input in dd/mm/yyyy format.
For more information, see in the
:class:`~kivymd.uix.textfield.MDTextField` class documentation.
"""
helper_text_mode = StringProperty("on_error")
owner = ObjectProperty() # MDDatePicker object
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bind(text=self._on_text_check_errors)
def _on_text_check_errors(self, widget, text):
if text == "":
self.error = False
return
try:
datetime.datetime.strptime(text, "%d/%m/%Y")
self.error = False
except ValueError:
self.error = True
def set_error(self):
"""Sets a text field to an error state."""
@ -699,54 +723,14 @@ class DatePickerDaySelectableItem(
owner = ObjectProperty()
is_today = BooleanProperty(False)
is_selected = BooleanProperty(False)
current_month = NumericProperty()
current_year = NumericProperty()
index = NumericProperty(0)
def check_date(self, year: int, month: int, day: int):
try:
return date(year, month, day) in self.owner._date_range
except ValueError as error:
if str(error) == "day is out of range for month":
return False
is_in_range = BooleanProperty(False)
is_range_start = BooleanProperty(False)
is_range_end = BooleanProperty(False)
is_month_end = BooleanProperty(False)
is_week_end = BooleanProperty(False)
def on_release(self):
if (
self.owner.mode == "range"
and self.owner._end_range_date
and self.owner._start_range_date
):
return
if (
not self.owner._input_date_dialog_open
and not self.owner._select_year_dialog_open
):
if self.owner.mode == "range" and not self.owner._start_range_date:
self.owner._start_range_date = date(
self.current_year, self.current_month, int(self.text)
)
self.owner.min_date = self.owner._start_range_date
elif (
self.owner.mode == "range"
and not self.owner._end_range_date
and self.owner._start_range_date
):
self.owner._end_range_date = date(
self.current_year, self.current_month, int(self.text)
)
if self.owner._end_range_date <= self.owner.min_date:
toast(self.owner.date_range_text_error)
Logger.error(
"`Data Picker: max_date` value cannot be less than "
"or equal to 'min_date' value."
)
self.owner._start_range_date = 0
self.owner._end_range_date = 0
return
self.owner.max_date = self.owner._end_range_date
self.owner.update_calendar_for_date_range()
self.owner.set_selected_widget(self)
self.owner.set_selected_widget(self)
def on_touch_down(self, touch):
# If year_layout is active don't dispatch on_touch_down events,
@ -760,7 +744,7 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
"""Implements an item for a pick list of the year."""
index = None
selected_color = ColorProperty([0, 0, 0, 0])
selected = BooleanProperty(False)
owner = ObjectProperty()
def refresh_view_attrs(self, rv, index, data):
@ -772,32 +756,10 @@ class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel):
return True
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(
self.owner.sel_year,
self.owner.sel_month,
self.owner.sel_day,
self.owner.theme_cls.device_orientation,
)
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, table_data, index, is_selected):
if is_selected:
self.selected_color = (
self.owner.selector_color
if self.owner.selector_color
else self.theme_cls.primary_color
)
self.text_color = (1, 1, 1, 1)
else:
if int(self.text) == self.owner.sel_year:
self.text_color = (
self.theme_cls.primary_color
if not self.owner.text_current_color
else self.owner.text_current_color
)
self.selected_color = [0, 0, 0, 0]
self.text_color = (0, 0, 0, 1)
self.selected = is_selected
# TODO: Add the feature to embed the `MDDatePicker` class in other layouts
@ -889,7 +851,7 @@ class MDDatePicker(BaseDialogPicker):
and defaults to `picker`.
"""
min_date = ObjectProperty()
min_date = ObjectProperty(allownone=True)
"""
The minimum value of the date range for the `'mode`' parameter.
Must be an object <class 'datetime.date'>.
@ -900,7 +862,7 @@ class MDDatePicker(BaseDialogPicker):
and defaults to `None`.
"""
max_date = ObjectProperty()
max_date = ObjectProperty(allownone=True)
"""
The minimum value of the date range for the `'mode`' parameter.
Must be an object <class 'datetime.date'>.
@ -955,17 +917,13 @@ class MDDatePicker(BaseDialogPicker):
_calendar_layout = ObjectProperty()
_calendar_list = None
_enter_data_field = None
_enter_data_field_two = None
_enter_data_field_container = None
_date_range = []
_fields_container = None
_scale_calendar_layout = NumericProperty(1)
_scale_year_layout = NumericProperty(0)
_shift_dialog_height = NumericProperty(0)
_input_date_dialog_open = BooleanProperty(False)
_select_year_dialog_open = False
_start_range_date = 0
_end_range_date = 0
_date_label_text = StringProperty()
def __init__(
self,
@ -996,7 +954,6 @@ class MDDatePicker(BaseDialogPicker):
"'max_date' must be of class <class 'datetime.date'>"
)
self.compare_date_range()
self._date_range = self.get_date_range()
self.generate_list_widgets_days()
self.update_calendar(self.sel_year, self.sel_month)
@ -1006,6 +963,8 @@ class MDDatePicker(BaseDialogPicker):
) -> None:
"""Called when the device's screen orientation changes."""
# Separators of the label text depend on the orientation.
self._update_date_label_text()
if self._input_date_dialog_open:
if orientation_value == "portrait":
self._shift_dialog_height = dp(250)
@ -1017,21 +976,12 @@ class MDDatePicker(BaseDialogPicker):
Called when the 'OK' button is pressed to confirm the date entered.
"""
if self._enter_data_field and not self.is_date_valaid(
self._enter_data_field.text
):
self._enter_data_field.set_error()
if self._input_date_dialog_open and not self._try_apply_input():
return
if self._enter_data_field_two and not self.is_date_valaid(
self._enter_data_field_two.text
):
self._enter_data_field_two.set_error()
return
self.dispatch(
"on_save",
date(self.sel_year, self.sel_month, self.sel_day),
self._date_range,
self.get_date_range(),
)
def is_date_valaid(self, date: str) -> bool:
@ -1085,56 +1035,25 @@ class MDDatePicker(BaseDialogPicker):
self.ids._year_layout.children[0].clear_selection()
def transformation_to_dialog_input_date(self) -> None:
def set_date_to_input_field():
if not self._enter_data_field_two:
# Date of current day.
self._enter_data_field.text = (
f"{'' if self.sel_day >= 10 else '0'}"
f"{self.sel_day}/"
f"{'' if self.sel_month >= 10 else '0'}"
f"{self.sel_month}/{self.sel_year}"
)
else:
# Range start date.
self._enter_data_field.text = (
f"{'' if self.min_date.day >= 10 else '0'}"
f"{self.min_date.day}/"
f"{'' if self.min_date.month >= 10 else '0'}"
f"{self.min_date.month}/{self.min_date.year}"
)
def set_date_to_input_field_two() -> None:
# Range end date.
self._enter_data_field_two.text = (
f"{'' if self.max_date.day >= 10 else '0'}"
f"{self.max_date.day}/"
f"{'' if self.max_date.month >= 10 else '0'}"
f"{self.max_date.month}/{self.max_date.year}"
)
self.ids.triangle.disabled = True
if self._select_year_dialog_open:
self.transformation_from_dialog_select_year()
self._input_date_dialog_open = True
self._enter_data_field_container = DatePickerInputFieldContainer(
owner=self
)
self._enter_data_field = self.get_field()
if self.min_date and self.max_date:
self._enter_data_field_two = self.get_field()
set_date_to_input_field_two()
set_date_to_input_field()
self._enter_data_field_container.add_widget(self._enter_data_field)
if self._enter_data_field_two:
self._enter_data_field_container.add_widget(
self._enter_data_field_two
)
self.ids.container.add_widget(self._enter_data_field_container)
self.ids.edit_icon.icon = "calendar"
self.ids.label_title.text = self.title_input
self._fields_container = DatePickerInputFieldContainer(owner=self)
if self.mode == "picker":
selected_date = date(self.sel_year, self.sel_month, self.sel_day)
selected_dates = [selected_date]
else:
selected_dates = [self.min_date, self.max_date]
for selected_date in selected_dates:
field = self.get_field(selected_date)
field.bind(text=self._on_date_field_text_changes)
self._fields_container.add_widget(field)
self.ids.container.add_widget(self._fields_container)
Animation(
_shift_dialog_height=dp(250)
if self.theme_cls.device_orientation == "portrait"
@ -1152,28 +1071,22 @@ class MDDatePicker(BaseDialogPicker):
).start(self.ids.chevron_right)
Animation(opacity=0, d=0.15).start(self.ids.label_month_selector)
Animation(opacity=0, d=0.15).start(self.ids.triangle)
Animation(opacity=1, d=0.15).start(self._enter_data_field)
if self._enter_data_field_two:
Animation(opacity=1, d=0.15).start(self._enter_data_field_two)
self.ids.label_full_date.text = self.set_text_full_date(
self.sel_year,
self.sel_month,
self.sel_day,
self.theme_cls.device_orientation,
)
Animation(opacity=1, d=0.15).start(self._fields_container)
# The label text separator in landscape orientation depends on the
# open dialog.
self._update_date_label_text()
def transformation_from_dialog_input_date(
self, interval: Union[int, float]
) -> None:
if not self._try_apply_input():
return
self._input_date_dialog_open = False
self.ids.label_full_date.text = self.set_text_full_date(
self.sel_year,
self.sel_month,
self.sel_day,
self.theme_cls.device_orientation,
)
self.ids.triangle.disabled = False
self.ids.container.remove_widget(self._enter_data_field_container)
self.ids.edit_icon.icon = "pencil"
self.ids.label_title.text = self.title
self.ids.container.remove_widget(self._fields_container)
self._fields_container = None
Animation(
_shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15
).start(self)
@ -1187,41 +1100,67 @@ class MDDatePicker(BaseDialogPicker):
).start(self.ids.chevron_right)
Animation(opacity=1, d=0.15).start(self.ids.label_month_selector)
Animation(opacity=1, d=0.15).start(self.ids.triangle)
Animation(opacity=0, d=0.15).start(self._enter_data_field)
self.ids.edit_icon.icon = "pencil"
self.ids.label_title.text = self.title
# The label text separator in landscape orientation depends on the
# open dialog.
self._update_date_label_text()
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 = 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()
def _get_dates_from_fields(self):
"""
Return a list of dates entered by the user in the input fields.
if len(list_min_date) == 3 and len(list_min_date[2]) == 4:
self.min_date = date(
int(list_min_date[2]),
int(list_min_date[1]),
int(list_min_date[0]),
)
if len(list_max_date) == 3 and len(list_max_date[2]) == 4:
self.max_date = date(
int(list_max_date[2]),
int(list_max_date[1]),
int(list_max_date[0]),
)
If there is an error in the field or the field is empty, None will be
in its place in the list. The length of the list will be 0 if the input
dialog is closed, otherwise 1 in picker mode or 2 in range mode.
"""
self.update_calendar_for_date_range()
self.ids.label_full_date.text = self.set_text_full_date(
int(list_max_date[2]),
int(list_max_date[1]),
int(list_max_date[0]),
self.theme_cls.device_orientation,
)
if not self._fields_container:
return []
dates = []
# Widgets are arranged in the reverse order of their addition.
for field in reversed(self._fields_container.children):
try:
date = datetime.datetime.strptime(field.text, "%d/%m/%Y").date()
except ValueError:
date = None
dates.append(date)
return dates
def _try_apply_input(self) -> bool:
"""
Apply the dates entered by the user, update the calendar and return
True. If there are errors in the fields, do nothing and return False.
"""
dates = self._get_dates_from_fields()
if not dates:
return True
# Widgets are arranged in the reverse order of their addition.
fields = reversed(self._fields_container.children)
if any(d is None and f.text for f, d in zip(fields, dates)):
return False
if self.mode == "picker":
selected_date = date(self.sel_year, self.sel_month, self.sel_day)
selected_date = dates[0] or selected_date
self.sel_year = selected_date.year
self.sel_month = selected_date.month
self.sel_day = selected_date.day
self.update_calendar(self.sel_year, self.sel_month)
elif self.mode == "range":
date1, date2 = dates[0] or self.min_date, dates[1] or self.max_date
ends = list(filter(bool, [date1, date2]))
if ends:
self.min_date = min(ends)
self.max_date = max(ends)
self.update_calendar(self.year, self.month)
return True
def _on_date_field_text_changes(self, *args):
self._update_date_label_text()
def compare_date_range(self) -> None:
# TODO: Add behavior if the minimum date range exceeds the maximum
@ -1233,8 +1172,7 @@ class MDDatePicker(BaseDialogPicker):
)
def update_calendar_for_date_range(self) -> None:
# self.compare_date_range()
self._date_range = self.get_date_range()
# This method is no longer used, use update_calendar instead.
self.update_calendar(self.year, self.month)
def update_text_full_date(self, list_date) -> None:
@ -1243,27 +1181,13 @@ class MDDatePicker(BaseDialogPicker):
in an open date input dialog.
"""
if len(list_date) == 1 and len(list_date[0]) == 2:
self.ids.label_full_date.text = self.set_text_full_date(
self.sel_year,
self.sel_month,
list_date[0],
self.theme_cls.device_orientation,
)
if len(list_date) == 2 and len(list_date[1]) == 2:
self.ids.label_full_date.text = self.set_text_full_date(
self.sel_year,
int(list_date[1]),
int(list_date[0]),
self.theme_cls.device_orientation,
)
if len(list_date) == 3 and len(list_date[2]) == 4:
self.ids.label_full_date.text = self.set_text_full_date(
int(list_date[2]),
int(list_date[1]),
int(list_date[0]),
self.theme_cls.device_orientation,
)
# This method no longer used, use update_calendar instead.
year = int(list_date[2]) if len(list_date) > 2 else self.sel_year
month = int(list_date[1]) if len(list_date) > 1 else self.sel_month
day = int(list_date[0]) if len(list_date) > 0 else self.sel_day
day = min(day, calendar.monthrange(year, month)[1])
self.sel_year, self.sel_month, self.sel_day = year, month, day
self.update_calendar(year, month)
def update_calendar(self, year, month) -> None:
self.year, self.month = year, month
@ -1271,7 +1195,10 @@ class MDDatePicker(BaseDialogPicker):
selected_date = date(self.sel_year, self.sel_month, self.sel_day)
selected_dates = {selected_date}
else:
selected_dates = {self._start_range_date, self._end_range_date}
selected_dates = {self.min_date, self.max_date}
# The label text depends on the selected date or date range.
self._update_date_label_text()
month_end = date(year, month, calendar.monthrange(year, month)[1])
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.
@ -1281,21 +1208,31 @@ class MDDatePicker(BaseDialogPicker):
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
widget.disabled = not visible
widget.is_in_range = (
visible
and self.min_date is not None
and self.max_date is not None
and self.min_date <= widget_date <= self.max_date
)
widget.is_range_start = (
visible
and self.min_date is not None
and widget_date == self.min_date
)
widget.is_range_end = (
visible
and self.max_date is not None
and widget_date == self.max_date
)
widget.is_month_end = widget_date == month_end
def get_field(self) -> MDTextField:
def get_field(self, date=None) -> MDTextField:
"""Creates and returns a text field object used to enter dates."""
if issubclass(self.input_field_cls, MDTextField):
@ -1322,6 +1259,7 @@ class MDDatePicker(BaseDialogPicker):
field = self.input_field_cls(
owner=self,
text=date.strftime("%d/%m/%Y") if date else "",
helper_text=self.helper_text,
fill_color_normal=fill_color_normal,
fill_color_focus=fill_color_focus,
@ -1340,6 +1278,8 @@ class MDDatePicker(BaseDialogPicker):
)
def get_date_range(self) -> list:
if not self.min_date or not self.max_date:
return []
date_range = [
self.min_date + datetime.timedelta(days=x)
for x in range((self.max_date - self.min_date).days + 1)
@ -1353,118 +1293,73 @@ class MDDatePicker(BaseDialogPicker):
a date range.
"""
if 12 < int(month) < 0:
raise ValueError(
"set_text_full_date:\n\t" f"Month [{month}] out of range."
)
if int(day) > calendar.monthrange(int(year), (month))[1]:
return ""
date = datetime.date(int(year), int(month), int(day))
separator = (
"\n"
if (orientation == "landscape" and not self._input_date_dialog_open)
else " "
# In portrait orientation, the label is stretched in width, so we
# should not insert line breaks. When the input dialog is open, the
# label moves to the right and also stretches in width.
horizontal = orientation == "portrait" or self._input_date_dialog_open
def date_repr(date):
return date.strftime("%b").capitalize() + " " + str(date.day)
input_dates = self._get_dates_from_fields()
if self.mode == "picker":
selected_date = date(self.sel_year, self.sel_month, self.sel_day)
if input_dates:
selected_date = input_dates[0] or selected_date
weekday_repr = selected_date.strftime("%a").capitalize()
separator = ", " if horizontal else ",\n"
return weekday_repr + separator + date_repr(selected_date)
elif self.mode == "range":
start, end = self.min_date, self.max_date
if input_dates:
start, end = input_dates[0] or start, input_dates[1] or end
ends = [end for end in (start, end) if end]
if len(ends) == 0:
start_repr, end_repr = "Start", "End"
else:
start, end = min(ends), max(ends)
start_repr, end_repr = date_repr(start), date_repr(end)
separator = "" if horizontal else ",\n"
return start_repr + separator + end_repr
def _update_date_label_text(self):
self._date_label_text = self.set_text_full_date(
self.sel_year,
self.sel_month,
self.sel_day,
self.theme_cls.device_orientation,
)
if self.mode == "picker":
if not self.min_date and not self.max_date:
return (
date.strftime("%a,").capitalize()
+ separator
+ date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
)
else:
return (
self.min_date.strftime("%b ").capitalize()
+ str(self.min_date.day).lstrip("0")
+ (
" - "
if orientation == "portrait"
else (
",\n" if not self._input_date_dialog_open else ", "
)
)
+ self.max_date.strftime("%b ").capitalize()
+ str(self.max_date.day).lstrip("0")
)
elif self.mode == "range":
if self._start_range_date and self._end_range_date:
if (
orientation == "landscape"
and "-" in self.ids.label_full_date.text
):
return (
self.ids.label_full_date.text.split("-")[0].strip()
+ (",\n" if not self._input_date_dialog_open else " - ")
+ date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
)
else:
if (
orientation == "landscape"
and "," in self.ids.label_full_date.text
):
return (
self.ids.label_full_date.text.split(",")[0].strip()
+ (
",\n"
if not self._input_date_dialog_open
else "-"
)
+ date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
)
if (
orientation == "portrait"
and "," in self.ids.label_full_date.text
):
return (
self.ids.label_full_date.text.split(",")[0].strip()
+ "-"
+ date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
)
if (
orientation == "portrait"
and "-" in self.ids.label_full_date.text
):
return (
self.ids.label_full_date.text.split("-")[0].strip()
+ " - "
+ date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
)
elif self._start_range_date and not self._end_range_date:
return (
(
date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
+ " - End"
)
if orientation != "landscape"
else (
date.strftime("%b ").capitalize()
+ str(day).lstrip("0")
+ "{}End".format(
",\n" if not self._input_date_dialog_open else " - "
)
)
)
elif not self._start_range_date and not self._end_range_date:
return (
"Start - End"
if orientation != "landscape"
else "Start{}End".format(
",\n" if not self._input_date_dialog_open else " - "
)
)
def set_selected_widget(self, widget) -> None:
self.sel_year = self.year
self.sel_month = self.month
self.sel_day = int(widget.text)
self.update_calendar(self.sel_year, self.sel_month)
if self._select_year_dialog_open or self._input_date_dialog_open:
return
try:
widget_date = date(self.year, self.month, int(widget.text))
except ValueError:
return
if self.mode == "picker":
self.sel_year = widget_date.year
self.sel_month = widget_date.month
self.sel_day = widget_date.day
self.update_calendar(self.sel_year, self.sel_month)
elif self.mode == "range":
ends = [end for end in (self.min_date, self.max_date) if end]
if widget_date in ends:
ends = [end for end in ends if end != widget_date]
elif len(ends) < 2:
ends.append(widget_date)
else:
start, end = min(ends), max(ends)
if abs(widget_date - start).days < abs(widget_date - end).days:
start = widget_date
else:
end = widget_date
ends = [start, end]
if len(ends) == 0:
self.min_date, self.max_date = None, None
else:
self.min_date, self.max_date = min(ends), max(ends)
self.update_calendar(self.year, self.month)
def set_month_day(self, day) -> None:
# This method is no longer used. The code bellow repeats the behavior
@ -1525,12 +1420,10 @@ class MDDatePicker(BaseDialogPicker):
)
weekday_label.font_name = self.font_name
self._calendar_layout.add_widget(weekday_label)
for i, j in enumerate(range(6 * 7)): # 6 weeks, 7 days a week
for i in range(6 * 7): # 6 weeks, 7 days a week
day_selectable_item = DatePickerDaySelectableItem(
index=i,
is_week_end=i % 7 == 6,
owner=self,
current_month=int(self.month),
current_year=int(self.year),
)
calendar_list.append(day_selectable_item)
self._calendar_layout.add_widget(day_selectable_item)
@ -1541,9 +1434,11 @@ class MDDatePicker(BaseDialogPicker):
Called when "chevron-left" and "chevron-right" buttons are pressed.
Switches the calendar to the previous/next month.
"""
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)

View File

@ -166,7 +166,6 @@ from kivy.uix.behaviors import ButtonBehavior
from kivy.vector import Vector
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.circularlayout import MDCircularLayout
from kivymd.uix.label import MDLabel
@ -185,7 +184,7 @@ class AmPmSelectorLabel(ButtonBehavior, MDLabel):
pass
class AmPmSelector(ThemableBehavior, MDBoxLayout):
class AmPmSelector(MDBoxLayout):
border_radius = NumericProperty()
border_color = ColorProperty()
bg_color = ColorProperty()

View File

@ -6,7 +6,8 @@
self.theme_cls.divider_color \
if not self.back_color else \
self.back_color
Rectangle:
RoundedRectangle:
radius: root.radius
size:
(self.width, self.height) \
if self.orientation == "horizontal" else \
@ -18,7 +19,8 @@
Color:
rgba:
self.theme_cls.primary_color if not self.color else self.color
Rectangle:
RoundedRectangle:
radius: root.radius
size:
(self.width * self.value_normalized, self.height if self.height else dp(4)) \
if self.orientation == "horizontal" else \

View File

@ -145,6 +145,7 @@ from kivy.properties import (
NumericProperty,
OptionProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.progressbar import ProgressBar
@ -158,6 +159,25 @@ with open(
class MDProgressBar(ThemableBehavior, ProgressBar):
"""
Progressbar class.
For more information, see in the
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.progressbar.ProgressBar`
classes documentation.
"""
radius = VariableListProperty([0], length=4)
"""
Progress line radius.
.. versionadded:: 1.2.0
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[0, 0, 0, 0]`.
"""
reversed = BooleanProperty(False)
"""
Reverse the direction the progressbar moves.
@ -179,7 +199,7 @@ class MDProgressBar(ThemableBehavior, ProgressBar):
color = ColorProperty(None)
"""
Progress bar color in ``rgba`` format.
Progress bar color in (r, g, b, a) or string format.
:attr:`color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
@ -187,7 +207,7 @@ class MDProgressBar(ThemableBehavior, ProgressBar):
back_color = ColorProperty(None)
"""
Progress bar back color in ``rgba`` format.
Progress bar back color in (r, g, b, a) or string format.
.. versionadded:: 1.0.0

View File

@ -85,12 +85,13 @@ Equivalent
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDRecycleGridLayout(
DeclarativeBehavior, RecycleGridLayout, MDAdaptiveWidget
DeclarativeBehavior, ThemableBehavior, RecycleGridLayout, MDAdaptiveWidget
):
"""
Recycle grid layout layout class. For more information, see in the

View File

@ -34,10 +34,14 @@ __all__ = ("MDRecycleView",)
from kivy.uix.recycleview import RecycleView
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDRecycleView(DeclarativeBehavior, RecycleView):
class MDRecycleView(
DeclarativeBehavior, ThemableBehavior, RecycleView, MDAdaptiveWidget
):
"""
Recycle view class. For more information, see in the
:class:`~kivy.uix.recycleview.RecycleView` class documentation.

View File

@ -15,7 +15,7 @@
canvas:
Clear
Color:
rgba: root.theme_cls.primary_dark
rgba: root.circle_color
Ellipse:
pos: self.pos
size: self.size
@ -24,4 +24,4 @@
id: spinner
size_hint: None, None
size: dp(30), dp(30)
color: 1, 1, 1, 1
color: root.spinner_color

View File

@ -43,6 +43,8 @@ Example
id: refresh_layout
refresh_callback: app.refresh_callback
root_layout: root
spinner_color: "brown"
circle_color: "white"
MDGridLayout:
id: box
@ -66,6 +68,8 @@ Example
y = 15
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
self.screen = Factory.Example()
self.set_list()
@ -81,8 +85,10 @@ Example
asynckivy.start(set_list())
def refresh_callback(self, *args):
'''A method that updates the state of your application
while the spinner remains on the screen.'''
'''
A method that updates the state of your application
while the spinner remains on the screen.
'''
def refresh_callback(interval):
self.screen.ids.box.clear_widgets()
@ -110,7 +116,12 @@ from kivy.core.window import Window
from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import ColorProperty, NumericProperty, ObjectProperty
from kivy.properties import (
ColorProperty,
NumericProperty,
ObjectProperty,
StringProperty,
)
from kivy.uix.floatlayout import FloatLayout
from kivymd import uix_path
@ -150,7 +161,16 @@ class _RefreshScrollEffect(DampedScrollEffect):
return False
class MDScrollViewRefreshLayout(MDScrollView):
class MDScrollViewRefreshLayout(ThemableBehavior, MDScrollView):
"""
Refresh layout class.
For more information, see in the
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivymd.uix.scrollview.MDScrollView`
class documentation.
"""
root_layout = ObjectProperty()
"""
The spinner will be attached to this layout.
@ -168,8 +188,70 @@ class MDScrollViewRefreshLayout(MDScrollView):
and defaults to `None`.
"""
spinner_color = ColorProperty([1, 1, 1, 1])
"""
Color of the spinner in (r, g, b, a) or string format.
.. versionadded:: 1.2.0
:attr:`spinner_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
"""
circle_color = ColorProperty(None)
"""
Color of the ellipse around the spinner in (r, g, b, a) or string format.
.. versionadded:: 1.2.0
:attr:`circle_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
show_transition = StringProperty("out_elastic")
"""
Transition of the spinner's opening.
.. versionadded:: 1.2.0
:attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_elastic'`.
"""
show_duration = NumericProperty(0.8)
"""
Duration of the spinner display.
.. versionadded:: 1.2.0
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.8`.
"""
hide_transition = StringProperty("out_elastic")
"""
Transition of hiding the spinner.
.. versionadded:: 1.2.0
:attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'out_elastic'`.
"""
hide_duration = NumericProperty(0.8)
"""
Duration of hiding the spinner.
.. versionadded:: 1.2.0
:attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.8`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.circle_color:
self.circle_color = self.theme_cls.primary_dark
self.effect_cls = _RefreshScrollEffect
self._work_spinner = False
self._did_overscroll = False
@ -180,7 +262,15 @@ class MDScrollViewRefreshLayout(MDScrollView):
if self.refresh_callback:
self.refresh_callback()
if not self.refresh_spinner:
self.refresh_spinner = RefreshSpinner(_refresh_layout=self)
self.refresh_spinner = RefreshSpinner(
_refresh_layout=self,
spinner_color=self.spinner_color,
circle_color=self.circle_color,
show_transition=self.show_transition,
show_duration=self.show_duration,
hide_transition=self.hide_transition,
hide_duration=self.hide_duration,
)
self.root_layout.add_widget(self.refresh_spinner)
self.refresh_spinner.start_anim_spinner()
self._work_spinner = True
@ -195,13 +285,18 @@ class MDScrollViewRefreshLayout(MDScrollView):
class RefreshSpinner(ThemableBehavior, FloatLayout):
# Color of the spinner in (r, g, b, a) or string format.
spinner_color = ColorProperty([1, 1, 1, 1])
"""
Color of spinner.
:attr:`spinner_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
"""
# Color of the ellipse around the spinner in (r, g, b, a) or string format.
circle_color = ColorProperty()
# Transition of the spinner's opening.
show_transition = StringProperty()
# The duration of the spinner display.
show_duration = NumericProperty(0.8)
# Transition of hiding the spinner.
hide_transition = StringProperty()
# Duration of hiding the spinner.
hide_duration = NumericProperty(0.8)
# kivymd.refreshlayout.MDScrollViewRefreshLayout object
_refresh_layout = ObjectProperty()
@ -210,13 +305,15 @@ class RefreshSpinner(ThemableBehavior, FloatLayout):
spinner = self.ids.body_spinner
Animation(
y=spinner.y - self.theme_cls.standard_increment * 2 + dp(10),
d=0.8,
t="out_elastic",
d=self.show_duration,
t=self.show_transition,
).start(spinner)
def hide_anim_spinner(self) -> None:
spinner = self.ids.body_spinner
anim = Animation(y=Window.height, d=0.8, t="out_elastic")
anim = Animation(
y=Window.height, d=self.hide_duration, t=self.hide_transition
)
anim.bind(on_complete=self.set_spinner)
anim.start(spinner)

View File

@ -31,11 +31,14 @@ MDRelativeLayout
from kivy.uix.relativelayout import RelativeLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
class MDRelativeLayout(DeclarativeBehavior, RelativeLayout, MDAdaptiveWidget):
class MDRelativeLayout(
DeclarativeBehavior, ThemableBehavior, RelativeLayout, MDAdaptiveWidget
):
"""
Relative layout class. For more information, see in the
:class:`~kivy.uix.relativelayout.RelativeLayout` class documentation.

View File

@ -32,12 +32,13 @@ MDScreen
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.screenmanager import Screen
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
from kivymd.uix.behaviors import DeclarativeBehavior
from kivymd.uix.hero import MDHeroTo
class MDScreen(DeclarativeBehavior, Screen, MDAdaptiveWidget):
class MDScreen(DeclarativeBehavior, ThemableBehavior, Screen, MDAdaptiveWidget):
"""
Screen is an element intended to be used with a
:class:`~kivymd.uix.screenmanager.MDScreenManager`. For more information,

View File

@ -0,0 +1,4 @@
from .segmentedbutton import ( # NOQA F401
MDSegmentedButton,
MDSegmentedButtonItem,
)

View File

@ -0,0 +1,32 @@
<MDSegmentedButton>
size_hint: None, None
height: "40dp"
opacity: 0
<MDSegmentedButtonItem>
size_hint: None, None
height: self.parent.height
line_color:
self.theme_cls.disabled_hint_text_color \
if self.parent.line_color == [0, 0, 0, 0] else \
self.parent.line_color
SegmentButtonIcon:
id: scale_icon
icon: root.icon
size_hint: None, None
size: "24dp", "24dp"
pos_hint: {"center_y": .5}
scale_value_x: 1 if root.icon else 0
scale_value_y: 1 if root.icon else 0
x: label_text.x - dp(32)
MDLabel:
id: label_text
text: root.text
adaptive_size: True
pos_hint: {"center_y": .5}
x:
root.center_x - (self.texture_size[0] / 2) \
+ (dp(16) if root.icon else 0)

View File

@ -0,0 +1,653 @@
"""
Components/SegmentedButton
==========================
.. versionadded:: 1.2.0
.. seealso::
`Material Design spec, Segmented buttons <https://m3.material.io/components/segmented-buttons/overview>`_
`Segmented control <https://kivymd.readthedocs.io/en/latest/components/segmentedcontrol/>`_
.. rubric:: Segmented buttons help people select options, switch views,
or sort elements.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-preview.png
:align: center
Usage
-----
.. code-block:: kv
MDScreen:
MDSegmentedButton:
MDSegmentedButtonItem:
icon: ...
text: ...
MDSegmentedButtonItem:
icon: ...
text: ...
MDSegmentedButtonItem:
icon: ...
text: ...
Example
-------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDSegmentedButton:
pos_hint: {"center_x": .5, "center_y": .5}
MDSegmentedButtonItem:
text: "Walking"
MDSegmentedButtonItem:
text: "Transit"
MDSegmentedButtonItem:
text: "Driving"
'''
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
Example().run()
By default, segmented buttons support single marking of elements:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-false.gif
:align: center
For multiple marking of elements, use the
:attr:`kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButton.multiselect`
parameter:
.. code-block:: kv
MDSegmentedButton:
multiselect: True
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-true.gif
:align: center
Control width
-------------
The width of the panel of segmented buttons will be equal to the width
of the texture of the widest button multiplied by the number of buttons:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-width-by-default.png
:align: center
But you can use the `size_hint_x` parameter to specify the relative width:
.. code-block:: kv
MDSegmentedButton:
size_hint_x: .9
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-width-size-hint-x.png
:align: center
Customization
-------------
You can see below in the documentation from which classes the
:class:`~kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButton` and
:class:`~kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButtonItem`
classes are inherited and use all their attributes such as
`md_bg_color`, `md_bg_color` etc. for additional customization of segments.
Events
------
- on_marked
The method is called when a segment is marked.
- on_unmarked
The method is called when a segment is unmarked.
.. code-block:: kv
MDSegmentedButton:
on_marked: app.on_marked(*args)
.. code-block:: python
def on_marked(
self,
segment_button: MDSegmentedButton,
segment_item: MDSegmentedButtonItem,
marked: bool,
) -> None:
print(segment_button)
print(segment_item)
print(marked)
A practical example
-------------------
.. code-block:: python
import os
from faker import Faker
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.segmentedbutton import MDSegmentedButton, MDSegmentedButtonItem
from kivymd.utils import asynckivy
KV = '''
<UserCard>
adaptive_height: True
md_bg_color: "#343930"
radius: 16
TwoLineAvatarListItem:
id: item
divider: None
_no_ripple_effect: True
text: root.name
secondary_text: root.path_to_file
theme_text_color: "Custom"
text_color: "#8A8D79"
secondary_theme_text_color: self.theme_text_color
secondary_text_color: self.text_color
on_size:
self.ids._left_container.size = (item.height, item.height)
self.ids._left_container.x = dp(6)
self._txt_right_pad = item.height + dp(12)
ImageLeftWidget:
source: root.album
radius: root.radius
MDScreen:
md_bg_color: "#151514"
MDBoxLayout:
orientation: "vertical"
padding: "12dp"
spacing: "12dp"
MDLabel:
adaptive_height: True
text: "Your downloads"
font_style: "H5"
theme_text_color: "Custom"
text_color: "#8A8D79"
MDSegmentedButton:
size_hint_x: 1
selected_color: "#303A29"
line_color: "#343930"
on_marked: app.on_marked(*args)
MDSegmentedButtonItem:
text: "Songs"
active: True
MDSegmentedButtonItem:
text: "Albums"
MDSegmentedButtonItem:
text: "Podcasts"
RecycleView:
id: card_list
viewclass: "UserCard"
bar_width: 0
RecycleBoxLayout:
orientation: 'vertical'
spacing: "16dp"
padding: "16dp"
default_size: None, dp(72)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
'''
class UserCard(MDBoxLayout):
name = StringProperty()
path_to_file = StringProperty()
album = StringProperty()
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
def on_marked(
self,
segment_button: MDSegmentedButton,
segment_item: MDSegmentedButtonItem,
marked: bool,
) -> None:
self.generate_card()
def generate_card(self):
async def generate_card():
for i in range(10):
await asynckivy.sleep(0)
self.root.ids.card_list.data.append(
{
"name": fake.name(),
"path_to_file": f"{os.path.splitext(fake.file_path())[0]}.mp3",
"album": fake.image_url(),
}
)
fake = Faker()
self.root.ids.card_list.data = []
Clock.schedule_once(lambda x: asynckivy.start(generate_card()))
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-practical-example.gif
:align: center
"""
from __future__ import annotations
__all__ = ("MDSegmentedButton", "MDSegmentedButtonItem")
import os
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
ColorProperty,
ListProperty,
NumericProperty,
StringProperty,
VariableListProperty,
)
from kivy.uix.behaviors import ButtonBehavior
from kivymd import uix_path
from kivymd.uix.behaviors import RectangularRippleBehavior, ScaleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.label import MDIcon
with open(
os.path.join(uix_path, "segmentedbutton", "segmentedbutton.kv"),
encoding="utf-8",
) as kv_file:
Builder.load_string(kv_file.read())
class MDSegmentedButtonItem(
RectangularRippleBehavior, ButtonBehavior, MDFloatLayout
):
"""
Segment button item.
For more information, see in the
:class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivymd.uix.boxlayout.MDBoxLayout`
class documentation.
"""
icon = StringProperty()
"""
Icon segment.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
text = StringProperty()
"""
Text segment.
:attr:`text` is an :class:`~kivy.properties.StringProperty`
and defaults to `''`.
"""
active = BooleanProperty(False)
"""
Background color of an disabled segment.
:attr:`active` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
disabled_color = ColorProperty(None)
"""
Is active segment.
:attr:`active` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
_no_ripple_effect = BooleanProperty(True)
_current_icon = ""
_current_md_bg_color = None
def on_disabled(self, instance, value: bool) -> None:
def on_disabled(*args):
if value:
if not self._current_md_bg_color:
self._current_md_bg_color = self.md_bg_color
self.md_bg_color = (
self.theme_cls.disabled_hint_text_color
if not self.disabled_color
else self.disabled_color
)
else:
if self._current_md_bg_color:
self.md_bg_color = self._current_md_bg_color
self._current_md_bg_color = None
Clock.schedule_once(on_disabled)
def on_icon(self, instance, icon_name: str):
if icon_name != "check":
self._current_icon = icon_name
# TODO:
# Add the feature to use both text and icons in segments -
# https://m3.material.io/components/segmented-buttons/guidelines#26abac1c-c6bd-44c1-a969-8c910c880b98
# Icons: optional check icon to indicate selected state -
# https://m3.material.io/components/segmented-buttons/overview#7b80f313-7d3a-4865-b26c-1f7ec98ba694
# Hovered: add a color for the hovered segment -
# https://m3.material.io/components/segmented-buttons/specs#d730b3ba-c59e-4ef8-b652-20979fe20b67
# Density: Each step down in density removes 4dp from the height -
# https://m3.material.io/components/segmented-buttons/specs#2d5cab36-1deb-40bd-9e37-bc2bb1657009
class MDSegmentedButton(MDBoxLayout):
"""
Segment button panel.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
:Events:
`on_marked`
The method is called when a segment is marked.
`on_unmarked`
The method is called when a segment is unmarked.
"""
radius = VariableListProperty([20], length=4)
"""
Panel radius.
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
and defaults to `[20, 20, 20, 20]`.
"""
multiselect = BooleanProperty(False)
"""
Do I allow multiple segment selection.
:attr:`multiselect` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
hiding_icon_transition = StringProperty("linear")
"""
Name of the transition hiding the current icon.
:attr:`hiding_icon_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'linear'`.
"""
hiding_icon_duration = NumericProperty(0.05)
"""
Duration of hiding the current icon.
:attr:`hiding_icon_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.05`.
"""
opening_icon_transition = StringProperty("linear")
"""
The name of the transition that opens a new icon of the "marked" type.
:attr:`opening_icon_transition` is a :class:`~kivy.properties.StringProperty`
and defaults to `'linear'`.
"""
opening_icon_duration = NumericProperty(0.05)
"""
The duration of opening a new icon of the "marked" type.
:attr:`opening_icon_duration` is a :class:`~kivy.properties.NumericProperty`
and defaults to `0.05`.
"""
selected_items = ListProperty()
"""
The list of :class:`~MDSegmentedButtonItem` objects that are currently
marked.
:attr:`selected_items` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
selected_color = ColorProperty(None)
"""
Color of the marked segment.
:attr:`selected_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.register_event_type("on_marked")
self.register_event_type("on_unmarked")
Clock.schedule_once(self.mark_segment)
Clock.schedule_once(self.adjust_segment_radius)
Clock.schedule_once(self.adjust_segment_panel_width, 2)
def mark_segment(self, *args) -> None:
"""Programmatically marks a segment."""
for widget in self.children:
if widget.active:
widget.active = False
widget.dispatch("on_release")
if not self.multiselect:
break
def adjust_segment_radius(self, *args) -> None:
"""Rounds off the first and last elements."""
if self.children[0].radius == [0, 0, 0, 0]:
self.children[0].radius = (0, self.height / 2, self.height / 2, 0)
if self.children[-1].radius == [0, 0, 0, 0]:
self.children[-1].radius = (self.height / 2, 0, 0, self.height / 2)
def adjust_segment_panel_width(self, *args) -> None:
"""
Sets the width of all segments and the width of the panel
by the widest segment.
"""
if not self.size_hint_x:
width_list = [
widget.ids.label_text.texture_size[0]
+ (dp(72) if widget.icon else dp(48))
for widget in self.children
]
max_width = max(width_list)
self.width = max_width * len(width_list)
else:
max_width = self.width / len(self.children)
for widget in self.children:
widget.width = max_width
self.opacity = 1
for widget in self.children:
if widget.active:
widget.dispatch("on_release")
def shift_segment_text(self, segment_item: MDSegmentedButtonItem) -> None:
"""
Shifts the segment text to the right, thus freeing up space
for the icon (when the segment is marked).
"""
Animation(
x=(
segment_item.ids.label_text.x
+ (
dp(16)
if not segment_item.icon and not segment_item.active
else 0
)
)
if not segment_item.active
else (
segment_item.ids.label_text.x
- (
dp(16)
if not segment_item.icon and segment_item.active
else 0
)
),
d=0.2,
).start(segment_item.ids.label_text)
def show_icon_marked_segment(
self, segment_item: MDSegmentedButtonItem
) -> None:
"""
Sets the icon for the marked segment and changes the icon scale
to the normal scale.
"""
segment_item.ids.scale_icon.icon = "check"
if segment_item.ids.scale_icon.icon == "check" and segment_item.active:
segment_item.ids.scale_icon.icon = segment_item._current_icon
Animation(
scale_value_x=1,
scale_value_y=1,
d=self.opening_icon_duration,
t=self.opening_icon_transition,
).start(segment_item.ids.scale_icon)
self.shift_segment_text(segment_item)
self.set_selected_segment_list(segment_item)
self.set_bg_marked_segment(segment_item)
def hide_icon_marked_segment(
self, segment_item: MDSegmentedButtonItem
) -> None:
"""Changes the scale of the icon of the marked segment to zero."""
anim = Animation(
scale_value_x=0,
scale_value_y=0,
d=self.hiding_icon_duration,
t=self.hiding_icon_transition,
)
anim.bind(
on_complete=lambda x, y: self.show_icon_marked_segment(segment_item)
)
anim.start(segment_item.ids.scale_icon)
def restore_bg_segment(self, segment_item) -> None:
Animation(md_bg_color=self.md_bg_color, d=0.2).start(segment_item)
def set_bg_marked_segment(self, segment_item) -> None:
if segment_item.active:
Animation(
md_bg_color=self.selected_color
if self.selected_color
else self.theme_cls.primary_color,
d=0.2,
).start(segment_item)
def set_selected_segment_list(self, segment_item) -> None:
segment_item.active = not segment_item.active
if segment_item.active:
self.selected_items.append(segment_item)
self.dispatch("on_marked", segment_item, segment_item.active)
else:
if segment_item in self.selected_items:
self.selected_items.remove(segment_item)
self.dispatch("on_unmarked", segment_item, segment_item.active)
def mark_item(self, segment_item: MDSegmentedButtonItem) -> None:
if segment_item.active and not self.multiselect:
return
if not self.multiselect and self.selected_items:
self.uncheck_item()
else:
if segment_item.active:
self.restore_bg_segment(segment_item)
self.hide_icon_marked_segment(segment_item)
def uncheck_item(self) -> None:
for item in self.children:
if item.active:
self.hide_icon_marked_segment(item)
self.restore_bg_segment(item)
break
def add_widget(self, widget, *args, **kwargs):
if isinstance(widget, MDSegmentedButtonItem):
widget.bind(on_release=self.mark_item)
return super().add_widget(widget)
def on_size(self, instance_segment_button, size: list) -> None:
"""Called when the root screen is resized."""
if self.size_hint_x:
max_width = size[0] / len(self.children)
for widget in self.children:
widget.width = max_width
def on_marked(self, *args):
"""The method is called when a segment is marked."""
def on_unmarked(self, *args):
"""The method is called when a segment is unmarked."""
class SegmentButtonIcon(MDIcon, ScaleBehavior):
"""Implements an icon with scaling behavior."""

View File

@ -1,3 +1,6 @@
#:import SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION kivymd.material_resources.SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION
<MDSegmentedControlItem>
adaptive_height: True
halign: "center"
@ -15,8 +18,9 @@
pos_hint: {"center_y": .5}
x: root._segment_switch_x
md_bg_color: root.segment_color
elevation: 2
elevation: SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION
_radius: root.radius[0] - 4
shadow_radius: self._radius
width:
segment_panel.width / segment_panel.children_number \
- segment_panel.spacing

View File

@ -121,7 +121,6 @@ from kivy.properties import (
)
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.card import MDSeparator
@ -145,12 +144,12 @@ class MDSegmentedControlItem(MDLabel):
# TODO: Add an attribute for the color of the active segment label.
class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
class MDSegmentedControl(MDRelativeLayout):
"""
Implements a segmented control panel.
Relative layout class. For more information, see in the
:class:`~kivy.uix.relativelayout.RelativeLayout` class documentation.
For more information, see in the
:class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation.
:Events:
`on_active`
@ -159,7 +158,7 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
md_bg_color = ColorProperty([0, 0, 0, 0])
"""
Background color of the segment panel.
Background color of the segment panel in (r, g, b, a) or string format.
.. code-block:: kv
@ -175,7 +174,7 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
segment_color = ColorProperty([0, 0, 0, 0])
"""
Color of the active segment.
Color of the active segment in (r, g, b, a) or string format.
.. code-block:: kv
@ -220,7 +219,8 @@ class MDSegmentedControl(MDRelativeLayout, ThemableBehavior):
separator_color = ColorProperty(None)
"""
The color of the separator between the segments.
The color of the separator between the segments in (r, g, b, a) or string
format.
.. code-block:: kv

View File

@ -276,7 +276,6 @@ from kivy.properties import (
)
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import TouchBehavior
from kivymd.uix.button import MDIconButton
from kivymd.uix.list import MDList
@ -295,7 +294,7 @@ class SelectionIconCheck(MDIconButton):
icon_check_color = ColorProperty([0, 0, 0, 1])
class SelectionItem(ThemableBehavior, MDRelativeLayout, TouchBehavior):
class SelectionItem(MDRelativeLayout, TouchBehavior):
selected = BooleanProperty(False)
"""
Whether or not an item is checked.
@ -514,6 +513,11 @@ class SelectionItem(ThemableBehavior, MDRelativeLayout, TouchBehavior):
class MDSelectionList(MDList):
"""
Selection list class.
For more information, see in the
:class:`~kivymd.uix.list.MDList` classes documentation.
:Events:
`on_selected`
Called when a list item is selected.
@ -548,7 +552,8 @@ class MDSelectionList(MDList):
icon_bg_color = ColorProperty([1, 1, 1, 1])
"""
Background color of the icon that will mark the selected list item.
Background color in (r, g, b, a) or string format of the icon that will
mark the selected list item.
:attr:`icon_bg_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
@ -556,7 +561,8 @@ class MDSelectionList(MDList):
icon_check_color = ColorProperty([0, 0, 0, 1])
"""
Color of the icon that will mark the selected list item.
Color in (r, g, b, a) or string format of the icon that will mark the
selected list item.
:attr:`icon_check_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[1, 1, 1, 1]`.
@ -564,7 +570,7 @@ class MDSelectionList(MDList):
overlay_color = ColorProperty([0, 0, 0, 0.2])
"""
The overlay color of the selected list item..
The overlay color in (r, g, b, a) or string format of the selected list item.
:attr:`overlay_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.2]]`.
@ -580,7 +586,8 @@ class MDSelectionList(MDList):
progress_round_color = ColorProperty(None)
"""
Color of the spinner for switching of `selected_mode` mode.
Color in (r, g, b, a) or string format of the spinner for switching of
`selected_mode` mode.
:attr:`progress_round_color` is an :class:`~kivy.properties.NumericProperty`
and defaults to `None`.

View File

@ -4,13 +4,12 @@ Components/SelectionControls
.. seealso::
`Material Design spec, Selection controls <https://material.io/components/selection-controls>`_
`Material Design spec, Checkbox <https://m3.material.io/components/checkbox/overview>`_
`Material Design spec, Switch <https://m3.material.io/components/switch/overview>`_
.. rubric:: Selection controls allow the user to select options.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-controll.png
:align: center
`KivyMD` provides the following selection controls classes for use:
- MDCheckbox_
@ -20,6 +19,12 @@ Components/SelectionControls
MDCheckbox
----------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox.png
:align: center
Usage
-----
.. code-block:: python
from kivy.lang import Builder
@ -37,18 +42,20 @@ MDCheckbox
'''
class Test(MDApp):
class Example(MDApp):
def build(self):
self.theme_cls.primary_palette = "Green"
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
Test().run()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox.gif
:align: center
.. Note:: Be sure to specify the size of the checkbox. By default, it is
``(dp(48), dp(48))``, but the ripple effect takes up all the available
`(dp(48), dp(48))`, but the ripple effect takes up all the available
space.
Control state
@ -94,20 +101,138 @@ MDCheckbox with group
'''
class Test(MDApp):
class Example(MDApp):
def build(self):
self.theme_cls.primary_palette = "Green"
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
Test().run()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-group.gif
:align: center
Parent and child checkboxes
---------------------------
Checkboxes can have a parent-child relationship with other checkboxes. When
the parent checkbox is checked, all child checkboxes are checked. If a parent
checkbox is unchecked, all child checkboxes are unchecked. If some, but not all,
child checkboxes are checked, the parent checkbox becomes an indeterminate
checkbox.
Usage
-----
.. code-block:: kv
MDCheckbox:
group: "root" # this is a required name for the parent checkbox group
MDCheckbox:
group: "child" # this is a required name for a group of child checkboxes
MDCheckbox:
group: "child" # this is a required name for a group of child checkboxes
Example
-------
.. code-block:: python
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
KV = '''
<CheckItem>
adaptive_height: True
MDCheckbox:
size_hint: None, None
size: "48dp", "48dp"
group: root.group
MDLabel:
text: root.text
adaptive_height: True
theme_text_color: "Custom"
text_color: "#B2B6AE"
pos_hint: {"center_y": .5}
MDBoxLayout:
orientation: "vertical"
md_bg_color: "#141612"
MDTopAppBar:
md_bg_color: "#21271F"
specific_text_color: "#B2B6AE"
elevation: 0
title: "Meal options"
left_action_items: [["arrow-left", lambda x: x]]
anchor_title: "left"
MDBoxLayout:
orientation: "vertical"
adaptive_height: True
padding: "12dp", "36dp", 0, 0
CheckItem:
text: "Recieve emails"
group: "root"
MDBoxLayout:
orientation: "vertical"
adaptive_height: True
padding: "24dp", 0, 0, 0
CheckItem:
text: "Daily"
group: "child"
CheckItem:
text: "Weekly"
group: "child"
CheckItem:
text: "Monthly"
group: "child"
MDWidget:
'''
class CheckItem(MDBoxLayout):
text = StringProperty()
group = StringProperty()
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Teal"
return Builder.load_string(KV)
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-parent-child.gif
:align: center
.. MDSwitch:
MDSwitch
--------
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch.png
:align: center
Usage
-----
.. code-block:: python
from kivy.lang import Builder
@ -122,58 +247,20 @@ MDSwitch
'''
class Test(MDApp):
class Example(MDApp):
def build(self):
self.theme_cls.primary_palette = "Green"
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
Test().run()
Example().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch.gif
:align: center
.. Note:: For :class:`~MDSwitch` size is not required. By default it is
``(dp(36), dp(48))``, but you can increase the width if you want.
.. code-block:: kv
MDSwitch:
width: dp(64)
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-switch_width.png
:align: center
.. Note:: Control state of :class:`~MDSwitch` same way as in
:class:`~MDCheckbox`.
MDSwitch in M3 style
--------------------
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
MDScreen:
MDSwitch:
pos_hint: {'center_x': .5, 'center_y': .5}
active: True
'''
class Test(MDApp):
def build(self):
self.theme_cls.material_style = "M3"
return Builder.load_string(KV)
Test().run()
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-m3.gif
:align: center
"""
__all__ = ("MDCheckbox", "MDSwitch")
@ -195,9 +282,14 @@ from kivy.uix.floatlayout import FloatLayout
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import CircularRippleBehavior, CommonElevationBehavior
from kivymd.uix.behaviors import (
CircularRippleBehavior,
CommonElevationBehavior,
ScaleBehavior,
)
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.label import MDIcon
from kivymd.utils import asynckivy
with open(
os.path.join(uix_path, "selectioncontrol", "selectioncontrol.kv"),
@ -206,7 +298,22 @@ with open(
Builder.load_string(kv_file.read())
class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
class MDCheckbox(
CircularRippleBehavior, ScaleBehavior, ToggleButtonBehavior, MDIcon
):
"""
Checkbox class.
For more information, see in the
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
:class:`~kivy.uix.behaviors.ToggleButtonBehavior` and
:class:`~kivymd.uix.label.MDIcon`
classes documentation.
"""
__allow_child_checkboxes_active = True
__allow_root_checkbox_active = True
active = BooleanProperty(False)
"""
Indicates if the checkbox is active or inactive.
@ -235,7 +342,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
radio_icon_normal = StringProperty("checkbox-blank-circle-outline")
"""
Background icon (when using the ``group`` option) of the checkbox used for
Background icon (when using the `group` option) of the checkbox used for
the default graphical representation when the checkbox is not pressed.
:attr:`radio_icon_normal` is a :class:`~kivy.properties.StringProperty`
@ -244,7 +351,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
radio_icon_down = StringProperty("checkbox-marked-circle")
"""
Background icon (when using the ``group`` option) of the checkbox used for
Background icon (when using the `group` option) of the checkbox used for
the default graphical representation when the checkbox is pressed.
:attr:`radio_icon_down` is a :class:`~kivy.properties.StringProperty`
@ -253,7 +360,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
color_active = ColorProperty(None)
"""
Color when the checkbox is in the active state.
Color in (r, g, b, a) or string format when the checkbox is in the active state.
.. versionadded:: 1.0.0
@ -271,7 +378,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
color_inactive = ColorProperty(None)
"""
Color when the checkbox is in the inactive state.
Color in (r, g, b, a) or string format when the checkbox is in the inactive state.
.. versionadded:: 1.0.0
@ -289,7 +396,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
disabled_color = ColorProperty(None)
"""
Color when the checkbox is in the disabled state.
Color in (r, g, b, a) or string format when the checkbox is in the disabled state.
.. code-block:: kv
@ -309,7 +416,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
selected_color = ColorProperty(None, deprecated=True)
"""
Color when the checkbox is in the active state.
Color in (r, g, b, a) or string format when the checkbox is in the active state.
.. deprecated:: 1.0.0
Use :attr:`color_active` instead.
@ -320,7 +427,7 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
unselected_color = ColorProperty(None, deprecated=True)
"""
Color when the checkbox is in the inactive state.
Color in (r, g, b, a) or string format when the checkbox is in the inactive state.
.. deprecated:: 1.0.0
Use :attr:`color_inactive` instead.
@ -332,9 +439,11 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
_current_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
def __init__(self, **kwargs):
self.check_anim_out = Animation(font_size=0, duration=0.1, t="out_quad")
self.check_anim_out = Animation(
scale_value_x=0, scale_value_y=0, duration=0.1, t="out_quad"
)
self.check_anim_in = Animation(
font_size=sp(24), duration=0.1, t="out_quad"
scale_value_x=1, scale_value_y=1, duration=0.1, t="out_quad"
)
super().__init__(**kwargs)
self.color_active = self.theme_cls.primary_color
@ -364,6 +473,13 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
self.update_color()
def update_primary_color(self, instance, value) -> None:
"""
Called when the values of
:attr:`kivymd.theming.ThemableBehavior.theme_cls.theme_style` and
:attr:`kivymd.theming.ThemableBehavior.theme_cls.primary_color`
change.
"""
if value in ("Dark", "Light"):
if not self.disabled:
self.color = self.theme_cls.primary_color
@ -373,18 +489,41 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
self.color_active = value
def update_icon(self, *args) -> None:
"""
Called when the values of
:attr:`checkbox_icon_normal` and
:attr:`checkbox_icon_down` and
:attr:`radio_icon_normal` and
:attr:`group`
change.
"""
if self.state == "down":
self.icon = (
self.radio_icon_down if self.group else self.checkbox_icon_down
self.radio_icon_down
if self.group and self.group not in ["root", "child"]
else self.checkbox_icon_down
if self.group != "root"
else "minus-box"
)
else:
self.icon = (
self.radio_icon_normal
if self.group
if self.group and self.group not in ["root", "child"]
else self.checkbox_icon_normal
)
def update_color(self, *args) -> None:
"""
Called when the values of
:attr:`color_active` and
:attr:`color_inactive` and
:attr:`disabled_color` and
:attr:`disabled` and
:attr:`state`
change.
"""
if self.disabled:
self._current_color = self.disabled_color
elif self.state == "down":
@ -393,6 +532,8 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
self._current_color = self.color_inactive
def on_state(self, *args) -> None:
"""Called when the values of :attr:`state` change."""
if self.state == "down":
self.check_anim_in.cancel(self)
self.check_anim_out.start(self)
@ -408,8 +549,45 @@ class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
self.active = False
def on_active(self, *args) -> None:
"""Called when the values of :attr:`active` change."""
self.state = "down" if self.active else "normal"
if (
self.group
and self.group == "root"
and MDCheckbox.__allow_root_checkbox_active
):
self.set_child_active(self.active)
elif self.group and self.group == "child":
if MDCheckbox.__allow_child_checkboxes_active:
self.set_root_active()
def set_root_active(self) -> None:
root_checkbox = self.get_widgets("root")
if root_checkbox:
MDCheckbox.__allow_root_checkbox_active = False
root_checkbox[0].active = True in [
child.active for child in self.get_widgets("child")
]
MDCheckbox.__allow_root_checkbox_active = True
def set_child_active(self, active: bool):
for child in self.get_widgets("child"):
child.active = active
MDCheckbox.__allow_child_checkboxes_active = True
def on_touch_down(self, touch):
if self.collide_point(touch.x, touch.y):
if self.group and self.group == "root":
MDCheckbox.__allow_child_checkboxes_active = False
return super().on_touch_down(touch)
def _release_group(self, current):
if self.group and self.group in ["root", "child"]:
return
super()._release_group(current)
class ThumbIcon(MDIcon):
"""
@ -419,14 +597,8 @@ class ThumbIcon(MDIcon):
"""
class Thumb(
CommonElevationBehavior,
CircularRippleBehavior,
MDFloatLayout,
):
"""
Implements a thumb for the :class:`~MDSwitch` widget.
"""
class Thumb(CommonElevationBehavior, CircularRippleBehavior, MDFloatLayout):
"""Implements a thumb for the :class:`~MDSwitch` widget."""
def _set_ellipse(self, instance, value):
self.ellipse.size = (self._ripple_rad, self._ripple_rad)
@ -443,6 +615,14 @@ class Thumb(
class MDSwitch(ThemableBehavior, FloatLayout):
"""
Switch class.
For more information, see in the
:class:`~kivymd.theming.ThemableBehavior` and
:class:`~kivy.uix.floatlayout.FloatLayout` classes documentation.
"""
active = BooleanProperty(False)
"""
Indicates if the switch is active or inactive.
@ -490,7 +670,8 @@ class MDSwitch(ThemableBehavior, FloatLayout):
icon_active_color = ColorProperty(None)
"""
Thumb icon color when the switch is in the active state (only M3 style).
Thumb icon color in (r, g, b, a) or string format when the switch is in the
active state (only M3 style).
.. versionadded:: 1.0.0
@ -510,7 +691,8 @@ class MDSwitch(ThemableBehavior, FloatLayout):
icon_inactive_color = ColorProperty(None)
"""
Thumb icon color when the switch is in an inactive state (only M3 style).
Thumb icon color in (r, g, b, a) or string format when the switch is in an
inactive state (only M3 style).
.. versionadded:: 1.0.0
@ -529,7 +711,7 @@ class MDSwitch(ThemableBehavior, FloatLayout):
thumb_color_active = ColorProperty(None)
"""
The color of the thumb when the switch is active.
The color in (r, g, b, a) or string format of the thumb when the switch is active.
.. versionadded:: 1.0.0
@ -548,7 +730,7 @@ class MDSwitch(ThemableBehavior, FloatLayout):
thumb_color_inactive = ColorProperty(None)
"""
The color of the thumb when the switch is inactive.
The color in (r, g, b, a) or string format of the thumb when the switch is inactive.
.. versionadded:: 1.0.0
@ -566,7 +748,8 @@ class MDSwitch(ThemableBehavior, FloatLayout):
thumb_color_disabled = ColorProperty(None)
"""
The color of the thumb when the switch is in the disabled state.
The color in (r, g, b, a) or string format of the thumb when the switch is
in the disabled state.
.. code-block:: kv
@ -584,7 +767,7 @@ class MDSwitch(ThemableBehavior, FloatLayout):
track_color_active = ColorProperty(None)
"""
The color of the track when the switch is active.
The color in (r, g, b, a) or string format of the track when the switch is active.
.. code-block:: kv
@ -601,7 +784,7 @@ class MDSwitch(ThemableBehavior, FloatLayout):
track_color_inactive = ColorProperty(None)
"""
The color of the track when the switch is inactive.
The color in (r, g, b, a) or string format of the track when the switch is inactive.
.. versionadded:: 1.0.0
@ -619,7 +802,8 @@ class MDSwitch(ThemableBehavior, FloatLayout):
track_color_disabled = ColorProperty(None)
"""
The color of the track when the switch is in the disabled state.
The color in (r, g, b, a) or string format of the track when the switch is
in the disabled state.
.. code-block:: kv
@ -646,6 +830,11 @@ class MDSwitch(ThemableBehavior, FloatLayout):
Clock.schedule_once(lambda x: self.on_active(self, self.active))
def set_icon(self, instance_switch, icon_value: str) -> None:
"""
Called when the values of
:attr:`icon_active` and :attr:`icon_inactive` change.
"""
def set_icon(*args):
icon = icon_value if icon_value else "blank"
self.ids.thumb.ids.icon.icon = icon
@ -653,6 +842,8 @@ class MDSwitch(ThemableBehavior, FloatLayout):
Clock.schedule_once(set_icon, 0.2)
def on_active(self, instance_switch, active_value: bool) -> None:
"""Called when the values of :attr:`active` change."""
if self.theme_cls.material_style == "M3" and self.widget_style != "ios":
size = (
(

Some files were not shown because too many files have changed in this diff Show More