diff --git a/.idea/KivyMD.iml b/.idea/KivyMD.iml
index 8e5446ac9..e9f6a42c1 100644
--- a/.idea/KivyMD.iml
+++ b/.idea/KivyMD.iml
@@ -4,7 +4,7 @@
-
+
diff --git a/docs/sources/changelog/unreleased.rst b/docs/sources/changelog/unreleased.rst
index 17d725f26..44d46909f 100644
--- a/docs/sources/changelog/unreleased.rst
+++ b/docs/sources/changelog/unreleased.rst
@@ -62,3 +62,4 @@ Unreleased
* `MDDropdownMenu `_ `API break <>https://kivymd.readthedocs.io/en/latest/components/menu/#api-break`_;
* Added the `motion_behavior `_ module to the `behaviors` package to control the display/hide behavior of widgets;
* `Fixed `_ scaling and rotation of widgets with elevation behavior;
+* Removed `MDBanner` widget;
\ No newline at end of file
diff --git a/examples/appbar.py b/examples/appbar.py
new file mode 100644
index 000000000..bc94576f1
--- /dev/null
+++ b/examples/appbar.py
@@ -0,0 +1,91 @@
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.secondaryContainerColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ size_hint_x: .8
+ spacing: "12dp"
+ pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDTopAppBar:
+ id: appbar
+ type: "small"
+
+ MDTopAppBarLeadingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "arrow-left"
+
+ MDTopAppBarTitle:
+ text: "AppBar small"
+
+ MDTopAppBarTrailingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "attachment"
+
+ MDActionTopAppBarButton:
+ icon: "calendar"
+
+ MDActionTopAppBarButton:
+ icon: "dots-vertical"
+
+ MDTopAppBar:
+ id: appbar_custom
+ type: "small"
+
+ MDTopAppBarLeadingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "arrow-left"
+ theme_icon_color: "Custom"
+ icon_color: "green"
+
+ MDTopAppBarTitle:
+ text: "AppBar small"
+ theme_text_color: "Custom"
+ text_color: "green"
+
+ MDTopAppBarTrailingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "attachment"
+ theme_icon_color: "Custom"
+ icon_color: "green"
+
+ MDActionTopAppBarButton:
+ icon: "calendar"
+ theme_icon_color: "Custom"
+ icon_color: "green"
+
+ MDActionTopAppBarButton:
+ icon: "dots-vertical"
+ theme_icon_color: "Custom"
+ icon_color: "green"
+"""
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ self.root.ids.appbar.disabled = not self.root.ids.appbar.disabled
+ self.root.ids.appbar_custom.disabled = self.root.ids.appbar.disabled
+
+
+Example().run()
diff --git a/examples/badge.py b/examples/badge.py
new file mode 100644
index 000000000..6e531190a
--- /dev/null
+++ b/examples/badge.py
@@ -0,0 +1,35 @@
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp
+
+KV = '''
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDIcon:
+ id: icon
+ icon: "gmail"
+ pos_hint: {'center_x': .5, 'center_y': .5}
+
+ MDBadge:
+ text: "12"
+'''
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ self.root.ids.icon.disabled = not self.root.ids.icon.disabled
+
+
+Example().run()
diff --git a/examples/button.py b/examples/button.py
new file mode 100644
index 000000000..b6c71eea4
--- /dev/null
+++ b/examples/button.py
@@ -0,0 +1,359 @@
+from kivy.clock import Clock
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+from kivymd.uix.button import (
+ MDIconButton,
+ MDButton,
+ MDFabButton,
+ MDButtonText,
+ MDButtonIcon,
+ MDExtendedFabButton,
+ MDExtendedFabButtonIcon,
+ MDExtendedFabButtonText,
+)
+
+from examples.common_app import CommonApp
+
+KV = """
+MDScreen:
+ md_bg_color: app.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ ScrollView:
+ size_hint_y: None
+ height: root.height - dp(68)
+
+ MDBoxLayout:
+ orientation: "vertical"
+ padding: "24dp", 0, "24dp", 0
+ adaptive_height: True
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+
+ MDLabel:
+ text: "MDIconButton"
+ bold: True
+ adaptive_height: True
+
+ MDBoxLayout:
+ id: icon_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+ md_bg_color: app.theme_cls.secondaryContainerColor
+ radius: "12dp"
+
+ MDLabel:
+ text: "MDIconButton (custom color)"
+ bold: True
+ adaptive_height: True
+
+ MDBoxLayout:
+ id: custom_icon_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+
+ MDLabel:
+ text: "MDFabButton"
+ bold: True
+ adaptive_height: True
+
+ MDBoxLayout:
+ id: fab_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+ md_bg_color: app.theme_cls.secondaryContainerColor
+ radius: "12dp"
+
+ MDLabel:
+ text: "MDFabButton (custom color)"
+ bold: True
+ adaptive_height: True
+
+ MDBoxLayout:
+ id: custom_fab_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+
+ MDLabel:
+ text: "MDButton"
+ adaptive_height: True
+ bold: True
+
+ MDBoxLayout:
+ id: md_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp", 0, "24dp", "24dp"
+
+ MDLabel:
+ text: "MDButton (with icon)"
+ adaptive_height: True
+ bold: True
+
+ MDBoxLayout:
+ id: md_button_icon_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+ md_bg_color: app.theme_cls.secondaryContainerColor
+ radius: "12dp"
+
+ MDLabel:
+ text: "MDButton (custom color)"
+ bold: True
+ adaptive_height: True
+
+ MDBoxLayout:
+ id: custom_md_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ adaptive_height: True
+ spacing: "24dp"
+ padding: "24dp"
+ radius: "12dp"
+
+ MDLabel:
+ text: "MDExtendedFabButton"
+ bold: True
+ adaptive_height: True
+
+ MDBoxLayout:
+ id: extended_fab_button_box
+ adaptive_height: True
+ spacing: "12dp"
+
+ Widget:
+"""
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ self.theme_cls.dynamic_color = True
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ for button in (
+ self.root.ids.fab_button_box.children
+ + self.root.ids.custom_fab_button_box.children
+ + self.root.ids.custom_icon_button_box.children
+ + self.root.ids.md_button_box.children
+ + self.root.ids.icon_button_box.children
+ + self.root.ids.md_button_icon_box.children
+ + self.root.ids.custom_md_button_box.children
+ + self.root.ids.extended_fab_button_box.children
+ ):
+ button.disabled = not button.disabled
+
+ def on_start(self):
+ styles = ["standard", "filled", "outlined", "tonal"]
+ color_disabled = [
+ 0.4627450980392157,
+ 0.47058823529411764,
+ 0.07450980392156863,
+ 0.38,
+ ]
+
+ for style in styles:
+ self.root.ids.icon_button_box.add_widget(
+ MDIconButton(style=style, icon="heart")
+ )
+ if style in ["filled", "tonal"]:
+ self.root.ids.custom_icon_button_box.add_widget(
+ MDIconButton(
+ style=style,
+ icon="heart",
+ theme_bg_color="Custom",
+ theme_icon_color="Custom",
+ md_bg_color={"filled": "brown", "tonal": "green"}[
+ style
+ ],
+ icon_color={"filled": "green", "tonal": "brown"}[style],
+ icon_color_disabled="black",
+ md_bg_color_disabled=color_disabled,
+ )
+ )
+ elif style == "outlined":
+ self.root.ids.custom_icon_button_box.add_widget(
+ MDIconButton(
+ style=style,
+ icon="heart",
+ theme_icon_color="Custom",
+ theme_line_color="Custom",
+ line_color="brown",
+ icon_color="green",
+ icon_color_disabled="black",
+ md_bg_color_disabled=color_disabled,
+ )
+ )
+ elif style == "standard":
+ self.root.ids.custom_icon_button_box.add_widget(
+ MDIconButton(
+ style=style,
+ icon="heart",
+ theme_icon_color="Custom",
+ icon_color="green",
+ icon_color_disabled="black",
+ md_bg_color_disabled=color_disabled,
+ )
+ )
+
+ styles = ["filled", "outlined", "tonal", "elevated", "text"]
+ for style in styles:
+ text = style.capitalize()
+ self.root.ids.md_button_box.add_widget(
+ MDButton(
+ MDButtonText(
+ text=text,
+ ),
+ style=style,
+ )
+ )
+ self.root.ids.md_button_icon_box.add_widget(
+ MDButton(
+ MDButtonIcon(
+ icon="heart",
+ ),
+ MDButtonText(
+ text=text,
+ ),
+ style=style,
+ )
+ )
+ self.root.ids.custom_md_button_box.add_widget(
+ MDButton(
+ MDButtonIcon(
+ icon="heart",
+ theme_icon_color="Custom",
+ icon_color="yellow",
+ icon_color_disabled="black",
+ ),
+ MDButtonText(
+ text=text,
+ theme_text_color="Custom",
+ text_color={
+ "filled": "white",
+ "tonal": "white",
+ "outlined": "green",
+ "text": "green",
+ "elevated": "white",
+ }[style],
+ ),
+ style=style,
+ theme_bg_color="Custom",
+ theme_line_color="Custom"
+ if style == "outlined"
+ else "Primary",
+ md_bg_color={
+ "filled": "brown",
+ "tonal": "brown",
+ "outlined": self.theme_cls.transparentColor,
+ "text": self.theme_cls.transparentColor,
+ "elevated": "red",
+ }[style],
+ line_color="green",
+ md_bg_color_disabled=color_disabled,
+ )
+ )
+
+ styles = {
+ "standard": "surface",
+ "small": "secondary",
+ "large": "tertiary",
+ }
+ for style in styles.keys():
+ self.root.ids.fab_button_box.add_widget(
+ MDFabButton(
+ style=style, icon="pencil-outline", color_map=styles[style]
+ )
+ )
+ self.root.ids.custom_fab_button_box.add_widget(
+ MDFabButton(
+ style=style,
+ icon="heart",
+ theme_bg_color="Custom",
+ md_bg_color="brown",
+ theme_icon_color="Custom",
+ icon_color="yellow",
+ icon_color_disabled="lightgrey",
+ md_bg_color_disabled=color_disabled,
+ )
+ )
+ button = MDExtendedFabButton(
+ MDExtendedFabButtonIcon(
+ icon="pencil-outline",
+ ),
+ MDExtendedFabButtonText(
+ text="Compose",
+ ),
+ fab_state="expand",
+ )
+ button.bind(on_release=self.fab_button_expand)
+ self.root.ids.extended_fab_button_box.add_widget(
+ MDExtendedFabButton(
+ MDExtendedFabButtonText(
+ text="Compose",
+ theme_text_color="Custom",
+ text_color="red",
+ ),
+ fab_state="expand",
+ )
+ )
+ self.root.ids.extended_fab_button_box.add_widget(button)
+
+ def fab_button_expand(self, instance):
+ def fab_button_expand(*args):
+ instance.fab_state = (
+ "expand" if instance.fab_state == "collapse" else "collapse"
+ )
+
+ Clock.schedule_once(fab_button_expand, 0.3)
+
+
+Example().run()
diff --git a/examples/card.py b/examples/card.py
new file mode 100644
index 000000000..575884cbb
--- /dev/null
+++ b/examples/card.py
@@ -0,0 +1,106 @@
+from kivy.lang import Builder
+from kivy.properties import StringProperty
+
+from kivymd.app import MDApp
+from kivymd.uix.card import MDCard
+
+from examples.common_app import CommonApp, KV
+
+Builder.load_string(
+ """
+
+ padding: "4dp"
+ size_hint_y: None
+ height: "100dp"
+
+ MDRelativeLayout:
+
+ MDIconButton:
+ icon: "dots-vertical"
+ pos_hint: {"top": 1, "right": 1}
+ focus_behavior: False
+
+ MDLabel:
+ id: label
+ text: root.text
+ adaptive_size: True
+ color: "grey"
+ pos: "12dp", "12dp"
+ bold: True
+"""
+)
+
+
+class MyCustomCard(MDCard):
+ text = StringProperty()
+
+
+class MyCard(MDCard):
+ text = StringProperty()
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ self.theme_cls.dynamic_color = True
+ self.theme_cls.primary_palette = "Red"
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ color_disabled = [
+ 0.4627450980392157,
+ 0.47058823529411764,
+ 0.07450980392156863,
+ 0.38,
+ ]
+ for style in ("elevated", "filled", "outlined"):
+ self.root.ids.widget_box.add_widget(
+ MyCard(
+ style=style,
+ text=style.capitalize(),
+ ripple_behavior=True,
+ )
+ )
+ if style == "elevated":
+ card = MyCard(
+ style=style,
+ text=style.capitalize(),
+ theme_shadow_color="Custom",
+ shadow_color="red",
+ theme_bg_color="Custom",
+ md_bg_color="white",
+ md_bg_color_disabled=color_disabled,
+ theme_shadow_offset="Custom",
+ shadow_offset=(1, -2),
+ theme_focus_color="Custom",
+ focus_color=[1, 0, 0, 0.2],
+ theme_shadow_softness="Custom",
+ shadow_softness=1,
+ theme_elevation_level="Custom",
+ elevation_level=2,
+ ripple_behavior=True,
+ )
+ self.root.ids.custom_widget_box.add_widget(card)
+ elif style == "filled":
+ self.root.ids.custom_widget_box.add_widget(
+ MyCard(
+ style=style,
+ text=style.capitalize(),
+ ripple_behavior=True,
+ theme_bg_color="Custom",
+ md_bg_color="brown",
+ )
+ )
+ elif style == "outlined":
+ self.root.ids.custom_widget_box.add_widget(
+ MyCard(
+ style=style,
+ text=style.capitalize(),
+ ripple_behavior=True,
+ theme_line_color="Custom",
+ line_color="brown",
+ md_bg_color_disabled=color_disabled,
+ )
+ )
+
+
+Example().run()
diff --git a/examples/checkbox.py b/examples/checkbox.py
new file mode 100644
index 000000000..29f9c2e98
--- /dev/null
+++ b/examples/checkbox.py
@@ -0,0 +1,24 @@
+from kivy.lang import Builder
+
+from kivymd.uix.selectioncontrol import MDCheckbox
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ self.root.ids.widget_box.add_widget(MDCheckbox())
+ self.root.ids.custom_widget_box.add_widget(
+ MDCheckbox(
+ color_active="lightgreen",
+ color_inactive="red",
+ color_disabled="brown",
+ )
+ )
+
+
+Example().run()
diff --git a/examples/chip.py b/examples/chip.py
new file mode 100644
index 000000000..9983f6922
--- /dev/null
+++ b/examples/chip.py
@@ -0,0 +1,51 @@
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+from kivymd.uix.chip import MDChip, MDChipLeadingIcon, MDChipText
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ self.theme_cls.primary_palette = "Olive"
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ for chip_type in ["assist", "input", "suggestion", "filter"]:
+ self.root.ids.widget_box.add_widget(
+ MDChip(
+ MDChipLeadingIcon(
+ icon="account",
+ ),
+ MDChipText(
+ text=chip_type.capitalize(),
+ ),
+ type=chip_type,
+ )
+ )
+
+ for chip_type in ["assist", "input", "suggestion", "filter"]:
+ self.root.ids.custom_widget_box.add_widget(
+ MDChip(
+ MDChipLeadingIcon(
+ icon="account",
+ theme_icon_color="Custom",
+ icon_color="brown",
+ icon_color_disabled="black",
+ ),
+ MDChipText(
+ text=chip_type.capitalize(),
+ theme_text_color="Custom",
+ text_color="red",
+ text_color_disabled="black",
+ ),
+ type=chip_type,
+ theme_line_color="Custom",
+ line_color="green",
+ line_color_disabled="black",
+ )
+ )
+
+
+Example().run()
diff --git a/examples/common_app.py b/examples/common_app.py
new file mode 100644
index 000000000..275cd21ec
--- /dev/null
+++ b/examples/common_app.py
@@ -0,0 +1,119 @@
+from kivy.utils import hex_colormap
+
+from kivymd.uix.menu import MDDropdownMenu
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDBoxLayout:
+ id: root_box
+ orientation: "vertical"
+ spacing: "12dp"
+ padding: "12dp"
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ icon: "menu"
+
+ ScrollView:
+
+ MDBoxLayout:
+ orientation: "vertical"
+ padding: "32dp", 0, "32dp", "32dp"
+ spacing: "24dp"
+ adaptive_height: True
+
+ MDLabel:
+ adaptive_height: True
+ text: "Standard widget"
+
+ MDBoxLayout:
+ id: widget_box
+ adaptive_height: True
+ spacing: "24dp"
+
+ Widget:
+ size_hint_y: None
+ height: "12dp"
+
+ MDLabel:
+ adaptive_height: True
+ text: "Custom widget"
+
+ MDBoxLayout:
+ id: custom_widget_box
+ adaptive_height: True
+ spacing: "24dp"
+"""
+
+
+class CommonApp:
+ menu: MDDropdownMenu = None
+
+ def open_menu(self, menu_button):
+ menu_items = []
+ for item, method in {
+ "Set palette": lambda: self.set_palette(),
+ "Switch theme style": lambda: self.theme_cls.switch_theme(),
+ "Disabled widgets": lambda: self.disabled_widgets(),
+ }.items():
+ menu_items.append(
+ {
+ "text": item,
+ "on_release": method,
+ }
+ )
+ self.menu = MDDropdownMenu(
+ caller=menu_button,
+ items=menu_items,
+ )
+ self.menu.open()
+
+ def switch_palette(self, selected_palette):
+ self.theme_cls.primary_palette = selected_palette
+
+ def switch_palette(self, selected_palette):
+ self.theme_cls.primary_palette = selected_palette
+
+ def set_palette(self):
+ instance_from_menu = self.get_instance_from_menu("Set palette")
+ available_palettes = [
+ name_color.capitalize() for name_color in hex_colormap.keys()
+ ]
+
+ menu_items = []
+ for name_palette in available_palettes:
+ menu_items.append(
+ {
+ "text": name_palette,
+ "on_release": lambda x=name_palette: self.switch_palette(x),
+ }
+ )
+ MDDropdownMenu(
+ caller=instance_from_menu,
+ items=menu_items,
+ ).open()
+
+ def get_instance_from_menu(self, name_item):
+ index = 0
+ rv = self.menu.ids.md_menu
+ opts = rv.layout_manager.view_opts
+ datas = rv.data[0]
+
+ for data in rv.data:
+ if data["text"] == name_item:
+ index = rv.data.index(data)
+ break
+
+ instance = rv.view_adapter.get_view(
+ index, datas, opts[index]["viewclass"]
+ )
+ return instance
+
+ def disabled_widgets(self):
+ for widget in self.root.ids.widget_box.children:
+ widget.disabled = not widget.disabled
+
+ for widget in self.root.ids.custom_widget_box.children:
+ widget.disabled = not widget.disabled
diff --git a/examples/dialog.py b/examples/dialog.py
new file mode 100644
index 000000000..8cb55b220
--- /dev/null
+++ b/examples/dialog.py
@@ -0,0 +1,97 @@
+from kivy.lang import Builder
+from kivy.uix.widget import Widget
+
+from kivymd.app import MDApp
+from kivymd.uix.button import MDButton, MDButtonText
+from kivymd.uix.dialog import (
+ MDDialog,
+ MDDialogIcon,
+ MDDialogHeadlineText,
+ MDDialogSupportingText,
+ MDDialogButtonContainer,
+ MDDialogContentContainer,
+)
+from kivymd.uix.divider import MDDivider
+from kivymd.uix.list import (
+ MDListItem,
+ MDListItemLeadingIcon,
+ MDListItemSupportingText,
+)
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDButton:
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ on_release: app.show_alert_dialog()
+
+ MDButtonText:
+ text: "Show dialog"
+"""
+
+
+class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def show_alert_dialog(self):
+ MDDialog(
+ # ----------------------------Icon-----------------------------
+ MDDialogIcon(
+ icon="refresh",
+ ),
+ # -----------------------Headline text-------------------------
+ MDDialogHeadlineText(
+ text="Reset settings?",
+ ),
+ # -----------------------Supporting text-----------------------
+ MDDialogSupportingText(
+ text="This will reset your app preferences back to their "
+ "default settings. The following accounts will also "
+ "be signed out:",
+ ),
+ # -----------------------Custom content------------------------
+ MDDialogContentContainer(
+ MDDivider(),
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="gmail",
+ ),
+ MDListItemSupportingText(
+ text="KivyMD-library@yandex.com",
+ ),
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.transparentColor,
+ ),
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="gmail",
+ ),
+ MDListItemSupportingText(
+ text="kivydevelopment@gmail.com",
+ ),
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.transparentColor,
+ ),
+ MDDivider(),
+ orientation="vertical",
+ ),
+ # ---------------------Button container------------------------
+ MDDialogButtonContainer(
+ Widget(),
+ MDButton(
+ MDButtonText(text="Cancel"),
+ style="text",
+ ),
+ MDButton(
+ MDButtonText(text="Accept"),
+ style="text",
+ ),
+ spacing="8dp",
+ ),
+ # -------------------------------------------------------------
+ ).open()
+
+
+Example().run()
diff --git a/examples/dynamic_color_image.py b/examples/dynamic_color_image.py
new file mode 100644
index 000000000..782b5ed5f
--- /dev/null
+++ b/examples/dynamic_color_image.py
@@ -0,0 +1,91 @@
+# Drag the image to the test application window.
+
+import os
+
+from kivy.clock import Clock
+from kivy.core.window import Window
+from kivy.core.window.window_sdl2 import WindowSDL
+from kivy.lang import Builder
+from kivy.properties import StringProperty, ColorProperty
+
+from kivymd.uix.boxlayout import MDBoxLayout
+from kivymd.app import MDApp
+
+KV = """
+
+ orientation: "vertical"
+
+ MDLabel:
+ text: root.text
+ color: "grey"
+ adaptive_height: True
+
+ MDCard:
+ theme_bg_color: "Custom"
+ md_bg_color: root.bg_color
+
+
+MDScreen:
+ md_bg_color: app.theme_cls.backgroundColor
+
+ MDRecycleView:
+ id: card_list
+ viewclass: "ColorCard"
+ bar_width: 0
+
+ RecycleGridLayout:
+ cols: 3
+ spacing: "16dp"
+ padding: "16dp"
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+"""
+
+
+class ColorCard(MDBoxLayout):
+ text = StringProperty()
+ bg_color = ColorProperty()
+
+
+class Example(MDApp):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ Window.bind(on_dropfile=self.on_drop_file)
+
+ def on_drop_file(self, sdl: WindowSDL, path_to_file: str) -> None:
+ ext = os.path.splitext(path_to_file)[1]
+ if isinstance(path_to_file, bytes):
+ path_to_file = path_to_file.decode()
+ if isinstance(ext, bytes):
+ ext = ext.decode()
+ if ext in [".png", ".jpg"]:
+ self.theme_cls.path_to_wallpaper = path_to_file
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def build(self):
+ self.theme_cls.dynamic_color = True
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
+
+ def theme_switch(self) -> None:
+ self.theme_cls.switch_theme()
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def generate_cards(self, *args):
+ self.root.ids.card_list.data = []
+ for color in self.theme_cls.schemes_name_colors:
+ value = f"{color}Color"
+ self.root.ids.card_list.data.append(
+ {
+ "bg_color": getattr(self.theme_cls, value),
+ "text": value,
+ }
+ )
+
+ def on_start(self):
+ Clock.schedule_once(self.generate_cards)
+
+
+Example().run()
diff --git a/examples/dynamic_color_schemes.py b/examples/dynamic_color_schemes.py
new file mode 100644
index 000000000..9bd4283d2
--- /dev/null
+++ b/examples/dynamic_color_schemes.py
@@ -0,0 +1,136 @@
+from kivy.clock import Clock
+from kivy.lang import Builder
+from kivy.properties import StringProperty, ColorProperty
+from kivy.uix.boxlayout import BoxLayout
+from kivy.utils import hex_colormap
+
+from kivymd.uix.menu import MDDropdownMenu
+from kivymd.app import MDApp
+
+
+KV = """
+
+ orientation: "vertical"
+
+ MDLabel:
+ text: root.text
+ color: "grey"
+ adaptive_height: True
+
+ MDCard:
+ theme_bg_color: "Custom"
+ md_bg_color: root.bg_color
+
+
+MDScreen:
+ md_bg_color: app.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDRecycleView:
+ id: card_list
+ viewclass: "ColorCard"
+ bar_width: 0
+ size_hint_y: None
+ height: root.height - dp(68)
+
+ RecycleGridLayout:
+ cols: 3
+ spacing: "16dp"
+ padding: "16dp"
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+"""
+
+
+class ColorCard(BoxLayout):
+ text = StringProperty()
+ bg_color = ColorProperty()
+
+
+class Example(MDApp):
+ menu: MDDropdownMenu = None
+
+ def build(self):
+ self.theme_cls.dynamic_color = True
+ return Builder.load_string(KV)
+
+ def get_instance_from_menu(self, name_item):
+ index = 0
+ rv = self.menu.ids.md_menu
+ opts = rv.layout_manager.view_opts
+ datas = rv.data[0]
+
+ for data in rv.data:
+ if data["text"] == name_item:
+ index = rv.data.index(data)
+ break
+
+ instance = rv.view_adapter.get_view(
+ index, datas, opts[index]["viewclass"]
+ )
+
+ return instance
+
+ def open_menu(self, menu_button):
+ menu_items = []
+ for item, method in {
+ "Set palette": lambda: self.set_palette(),
+ "Switch theme style": lambda: self.theme_switch(),
+ }.items():
+ menu_items.append({"text": item, "on_release": method})
+ self.menu = MDDropdownMenu(
+ caller=menu_button,
+ items=menu_items,
+ )
+ self.menu.open()
+
+ def set_palette(self):
+ instance_from_menu = self.get_instance_from_menu("Set palette")
+ available_palettes = [
+ name_color.capitalize() for name_color in hex_colormap.keys()
+ ]
+
+ menu_items = []
+ for name_palette in available_palettes:
+ menu_items.append(
+ {
+ "text": name_palette,
+ "on_release": lambda x=name_palette: self.switch_palette(x),
+ }
+ )
+ MDDropdownMenu(
+ caller=instance_from_menu,
+ items=menu_items,
+ ).open()
+
+ def switch_palette(self, selected_palette):
+ self.theme_cls.primary_palette = selected_palette
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def theme_switch(self) -> None:
+ self.theme_cls.switch_theme()
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def generate_cards(self, *args):
+ self.root.ids.card_list.data = []
+ for color in self.theme_cls.schemes_name_colors:
+ value = f"{color}Color"
+ self.root.ids.card_list.data.append(
+ {
+ "bg_color": getattr(self.theme_cls, value),
+ "text": value,
+ }
+ )
+
+ def on_start(self):
+ Clock.schedule_once(self.generate_cards)
+
+
+Example().run()
diff --git a/examples/label.py b/examples/label.py
new file mode 100644
index 000000000..446016a3a
--- /dev/null
+++ b/examples/label.py
@@ -0,0 +1,67 @@
+from kivy.lang import Builder
+
+from kivymd.font_definitions import theme_font_styles
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDRecycleView:
+ id: rv
+ key_viewclass: 'viewclass'
+ key_size: 'height'
+ size_hint_y: None
+ height: root.height - dp(48)
+
+ RecycleBoxLayout:
+ padding: dp(18)
+ spacing: dp(10)
+ default_size: None, dp(48)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+ orientation: "vertical"
+"""
+
+
+class Example(MDApp, CommonApp):
+ _disabled = False
+
+ def build(self):
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ self._disabled = not self._disabled
+ self.create_widgets()
+
+ def create_widgets(self):
+ self.root.ids.rv.data = []
+ for style in theme_font_styles:
+ if style != "Icon":
+ for role in theme_font_styles[style]:
+ font_size = int(theme_font_styles[style][role]["font-size"])
+ self.root.ids.rv.data.append(
+ {
+ "viewclass": "MDLabel",
+ "text": f"{style} {role} {font_size} sp",
+ "adaptive_height": "True",
+ "font_style": style,
+ "role": role,
+ "disabled": self._disabled,
+ }
+ )
+
+ def on_start(self):
+ self.create_widgets()
+
+
+Example().run()
diff --git a/examples/list.py b/examples/list.py
new file mode 100644
index 000000000..81a09df98
--- /dev/null
+++ b/examples/list.py
@@ -0,0 +1,67 @@
+from kivy.lang import Builder
+
+from kivymd import images_path
+from kivymd.uix.list import (
+ MDListItem,
+ MDListItemHeadlineText,
+ MDListItemSupportingText,
+ MDListItemTrailingCheckbox,
+ MDListItemLeadingAvatar,
+ MDListItemTertiaryText,
+ MDListItemLeadingIcon,
+)
+
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def on_tap_list_item(self, list_item: MDListItem):
+ print("on_tap_list_item")
+
+ def on_start(self):
+ self.root.ids.widget_box.orientation = "vertical"
+ self.root.ids.widget_box.add_widget(
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="account-outline",
+ ),
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemSupportingText(
+ text="Supporting text",
+ ),
+ MDListItemTertiaryText(
+ text="Tertiary text",
+ ),
+ MDListItemTrailingCheckbox(),
+ on_release=self.on_tap_list_item,
+ )
+ )
+ # Custom.
+ self.root.ids.custom_widget_box.add_widget(
+ MDListItem(
+ MDListItemLeadingAvatar(
+ source=f"{images_path}/logo/kivymd-icon-256.png",
+ ),
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemTrailingCheckbox(
+ color_disabled="red",
+ ),
+ divider=True,
+ theme_divider_color="Custom",
+ divider_color="red",
+ theme_bg_color="Custom",
+ md_bg_color=[1, 1, 0, 0.3],
+ )
+ )
+
+
+Example().run()
diff --git a/examples/navigation_bar.py b/examples/navigation_bar.py
new file mode 100644
index 000000000..0de30fc80
--- /dev/null
+++ b/examples/navigation_bar.py
@@ -0,0 +1,111 @@
+from kivy.lang import Builder
+from kivy.metrics import dp
+
+from kivymd.uix.navigationbar import (
+ MDNavigationBar,
+ MDNavigationItem,
+ MDNavigationItemLabel,
+ MDNavigationItemIcon,
+)
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def on_switch_tabs(
+ self,
+ bar: MDNavigationBar,
+ item: MDNavigationItem,
+ item_icon: str,
+ item_text: str,
+ ):
+ ...
+
+ def on_start(self):
+ self.root.ids.widget_box.height = dp(80)
+ self.root.ids.widget_box.add_widget(
+ MDNavigationBar(
+ MDNavigationItem(
+ MDNavigationItemIcon(
+ icon="gmail",
+ ),
+ MDNavigationItemLabel(
+ text="Mail",
+ ),
+ ),
+ MDNavigationItem(
+ MDNavigationItemIcon(
+ icon="twitter",
+ ),
+ MDNavigationItemLabel(
+ text="Twitter",
+ ),
+ ),
+ MDNavigationItem(
+ MDNavigationItemIcon(
+ icon="linkedin",
+ ),
+ MDNavigationItemLabel(
+ text="LinkedIN",
+ ),
+ ),
+ on_switch_tabs=self.on_switch_tabs,
+ )
+ )
+
+ self.root.ids.custom_widget_box.add_widget(
+ MDNavigationBar(
+ MDNavigationItem(
+ MDNavigationItemIcon(
+ icon="gmail",
+ theme_icon_color="Custom",
+ icon_color_normal="brown",
+ icon_color_active="white",
+ ),
+ MDNavigationItemLabel(
+ text="Mail",
+ theme_text_color="Custom",
+ text_color_active="white",
+ ),
+ indicator_color="grey",
+ ),
+ MDNavigationItem(
+ MDNavigationItemIcon(
+ icon="twitter",
+ theme_icon_color="Custom",
+ icon_color_normal="brown",
+ icon_color_active="white",
+ ),
+ MDNavigationItemLabel(
+ text="Twitter",
+ theme_text_color="Custom",
+ text_color_active="white",
+ ),
+ indicator_color="grey",
+ ),
+ MDNavigationItem(
+ MDNavigationItemIcon(
+ icon="linkedin",
+ theme_icon_color="Custom",
+ icon_color_normal="brown",
+ icon_color_active="white",
+ ),
+ MDNavigationItemLabel(
+ text="LinkedIN",
+ theme_text_color="Custom",
+ text_color_active="white",
+ ),
+ indicator_color="grey",
+ ),
+ theme_bg_color="Custom",
+ md_bg_color="silver",
+ on_switch_tabs=self.on_switch_tabs,
+ )
+ )
+
+
+Example().run()
diff --git a/examples/navigation_rail.py b/examples/navigation_rail.py
new file mode 100644
index 000000000..e9faabaeb
--- /dev/null
+++ b/examples/navigation_rail.py
@@ -0,0 +1,61 @@
+from kivy.lang import Builder
+from kivy.properties import StringProperty
+
+from kivymd.app import MDApp
+from kivymd.uix.navigationrail import MDNavigationRailItem
+
+from examples.common_app import CommonApp
+
+KV = """
+
+
+ MDNavigationRailItemIcon:
+ icon: root.icon
+
+ MDNavigationRailItemLabel:
+ text: root.text
+
+
+MDBoxLayout:
+
+ MDNavigationRail:
+ id: rail
+
+ MDNavigationRailMenuButton:
+ icon: "menu"
+ on_release: app.open_menu(self)
+
+ MDNavigationRailFabButton:
+ icon: "home"
+
+ CommonNavigationRailItem:
+ icon: "folder-outline"
+ text: "Files"
+
+ CommonNavigationRailItem:
+ icon: "bookmark-outline"
+ text: "Bookmark"
+
+ CommonNavigationRailItem:
+ icon: "library-outline"
+ text: "Library"
+
+ MDScreen:
+ md_bg_color: self.theme_cls.secondaryContainerColor
+"""
+
+
+class CommonNavigationRailItem(MDNavigationRailItem):
+ text = StringProperty()
+ icon = StringProperty()
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ self.root.ids.rail.disabled = not self.root.ids.rail.disabled
+
+
+Example().run()
diff --git a/examples/navigationdrawer.py b/examples/navigationdrawer.py
new file mode 100644
index 000000000..c73b99607
--- /dev/null
+++ b/examples/navigationdrawer.py
@@ -0,0 +1,133 @@
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: root.width - (self.width + dp(24))
+ icon: "menu"
+
+ MDNavigationLayout:
+
+ MDScreenManager:
+
+ MDScreen:
+
+ MDButton:
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: nav_drawer.set_state("toggle")
+
+ MDButtonText:
+ text: "Open Drawer"
+
+ MDNavigationDrawer:
+ id: nav_drawer
+ radius: 0, dp(16), dp(16), 0
+
+ MDNavigationDrawerMenu:
+
+ MDNavigationDrawerHeader:
+ padding: "16dp", 0, "16dp", "16dp"
+ spacing: "16dp"
+
+ MDIcon:
+ icon: "card-account-mail-outline"
+ theme_font_size: "Custom"
+ font_size: "56sp"
+ theme_icon_color: "Custom"
+ icon_color: self.theme_cls.primaryColor
+
+ MDLabel:
+ text: "Gmail"
+ font_style: "Display"
+ role: "medium"
+ theme_line_height: "Custom"
+ line_height: 0
+ adaptive_height: True
+ theme_text_color: "Custom"
+ text_color: self.theme_cls.primaryColor
+
+ MDNavigationDrawerLabel:
+ text: "accaount@gmail.com"
+ theme_text_color: "Custom"
+ text_color: self.theme_cls.primaryColor
+
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "inbox"
+
+ MDNavigationDrawerItemText:
+ text: "Inbox"
+
+ MDNavigationDrawerItemTrailingText:
+ text: "24"
+ theme_text_color: "Custom"
+ text_color: self.theme_cls.primaryColor
+
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "send-outline"
+
+ MDNavigationDrawerItemText:
+ text: "Outbox"
+
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "heart-outline"
+
+ MDNavigationDrawerItemText:
+ text: "Favorites"
+
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "trash-can-outline"
+
+ MDNavigationDrawerItemText:
+ text: "Trash"
+
+ MDNavigationDrawerDivider:
+
+ MDNavigationDrawerLabel:
+ text: "Personal folders"
+ theme_text_color: "Custom"
+ text_color: self.theme_cls.primaryColor
+ padding: "16dp", "8dp", 0, 0
+
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "folder-outline"
+
+ MDNavigationDrawerItemText:
+ text: "Family"
+
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "folder-outline"
+
+ MDNavigationDrawerItemText:
+ text: "Wedding"
+"""
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ ...
+
+
+Example().run()
diff --git a/examples/segmented_button.py b/examples/segmented_button.py
new file mode 100644
index 000000000..fe17c9be1
--- /dev/null
+++ b/examples/segmented_button.py
@@ -0,0 +1,100 @@
+from kivy.lang import Builder
+
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDBoxLayout:
+ orientation: "vertical"
+ padding: "32dp", "72dp", "32dp", "32dp"
+ spacing: "24dp"
+
+ MDLabel:
+ adaptive_height: True
+ text: "Segmented button"
+
+ MDSegmentedButton:
+ id: segmented_button
+
+ MDSegmentedButtonItem:
+
+ MDSegmentButtonIcon:
+ icon: "language-python"
+
+ MDSegmentButtonLabel:
+ text: "Python"
+
+ MDSegmentedButtonItem:
+
+ MDSegmentButtonIcon:
+ icon: "language-javascript"
+
+ MDSegmentButtonLabel:
+ text: "Java-Script"
+
+ MDLabel:
+ adaptive_height: True
+ text: "Custom Segmented button"
+
+ MDSegmentedButton:
+ id: segmented_button_custom
+ selected_icon_color: "red"
+
+ MDSegmentedButtonItem:
+ theme_line_color: "Custom"
+ line_color: "red"
+ selected_color: "#a655f240"
+
+ MDSegmentButtonIcon:
+ icon: "language-python"
+ theme_icon_color: "Custom"
+ icon_color: "red"
+
+ MDSegmentButtonLabel:
+ text: "Python"
+ theme_text_color: "Custom"
+ text_color: "red"
+
+ MDSegmentedButtonItem:
+ theme_line_color: "Custom"
+ line_color: "red"
+ selected_color: "#a655f240"
+
+ MDSegmentButtonIcon:
+ icon: "language-javascript"
+ theme_icon_color: "Custom"
+ icon_color: "red"
+
+ MDSegmentButtonLabel:
+ text: "Java-Script"
+ theme_text_color: "Custom"
+ text_color: "red"
+
+ MDWidget:
+"""
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ self.theme_cls.primary_palette = "Olive"
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ for item in (
+ self.root.ids.segmented_button.get_items()
+ + self.root.ids.segmented_button_custom.get_items()
+ ):
+ item.disabled = not item.disabled
+
+
+Example().run()
diff --git a/examples/slider.py b/examples/slider.py
new file mode 100644
index 000000000..c88d298a9
--- /dev/null
+++ b/examples/slider.py
@@ -0,0 +1,40 @@
+from kivy.lang import Builder
+
+from kivymd.uix.slider import MDSlider, MDSliderHandle, MDSliderValueLabel
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ self.theme_cls.primary_palette = "Red"
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ self.root.ids.widget_box.add_widget(
+ MDSlider(
+ MDSliderHandle(),
+ MDSliderValueLabel(),
+ step=10,
+ value=50,
+ )
+ )
+ self.root.ids.custom_widget_box.add_widget(
+ MDSlider(
+ MDSliderHandle(
+ theme_bg_color="Custom",
+ md_bg_color="teal",
+ state_layer_color="black",
+ ),
+ step=10,
+ value=50,
+ track_active_color="green",
+ track_inactive_color="lightgreen",
+ track_active_step_point_color="white",
+ track_inactive_step_point_color="green",
+ )
+ )
+
+
+Example().run()
diff --git a/examples/snackbar.py b/examples/snackbar.py
new file mode 100644
index 000000000..a4ad67c00
--- /dev/null
+++ b/examples/snackbar.py
@@ -0,0 +1,164 @@
+from kivy.lang import Builder
+from kivy.metrics import dp
+from kivy.uix.widget import Widget
+
+from kivymd.uix.button import MDButton, MDButtonText
+from kivymd.uix.snackbar import (
+ MDSnackbar,
+ MDSnackbarSupportingText,
+ MDSnackbarButtonContainer,
+ MDSnackbarActionButton,
+ MDSnackbarActionButtonText,
+ MDSnackbarCloseButton,
+ MDSnackbarText,
+)
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp
+
+KV = """
+MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDBoxLayout:
+ id: box
+ orientation: "vertical"
+ adaptive_size: True
+ spacing: "8dp"
+ pos_hint: {'center_x': .5, 'center_y': .5}
+"""
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def disabled_widgets(self):
+ ...
+
+ def on_start(self):
+ data = {
+ "Single-line snackbar":
+ self.show_snack_single_line,
+ "Single-line snackbar with action":
+ self.show_snack_single_line_with_action,
+ "Single-line snackbar with action and close buttons":
+ self.show_snack_single_line_with_action_and_close_button,
+ "Two-line snackbar with action and close buttons at the bottom":
+ self.show_snack_two_line_with_action_and_close_button_at_botton,
+ }
+
+ for text_button, function in data.items():
+ self.root.ids.box.add_widget(
+ MDButton(
+ MDButtonText(
+ text=text_button,
+ ),
+ on_release=lambda x, f=function: f(),
+ )
+ )
+
+ def show_snack_single_line(self):
+ MDSnackbar(
+ MDSnackbarText(
+ text="Single-line snackbar",
+ ),
+ y=dp(24),
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
+ ).open()
+
+ def show_snack_single_line_with_action(self):
+ MDSnackbar(
+ MDSnackbarSupportingText(
+ text="Single-line snackbar with action",
+ ),
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(text="Action button"),
+ ),
+ pos_hint={"center_y": 0.5},
+ ),
+ y=dp(24),
+ orientation="horizontal",
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
+ ).open()
+
+ def show_snack_single_line_with_action_and_close_button(self):
+ MDSnackbar(
+ MDSnackbarSupportingText(
+ text="Single-line snackbar with action and close buttons",
+ ),
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(
+ text="Action button",
+ ),
+ ),
+ MDSnackbarCloseButton(
+ icon="close",
+ ),
+ pos_hint={"center_y": 0.5},
+ ),
+ y=dp(24),
+ orientation="horizontal",
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.8,
+ ).open()
+
+ def show_snack_two_line_with_action_and_close_button(self):
+ MDSnackbar(
+ MDSnackbarText(
+ text="Single-line snackbar",
+ ),
+ MDSnackbarSupportingText(
+ text="with action and close buttons",
+ ),
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(text="Action button"),
+ ),
+ MDSnackbarCloseButton(
+ icon="close",
+ ),
+ pos_hint={"center_y": 0.5},
+ ),
+ y=dp(24),
+ orientation="horizontal",
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
+ ).open()
+
+ def show_snack_two_line_with_action_and_close_button_at_botton(self):
+ MDSnackbar(
+ MDSnackbarText(
+ text="Single-line snackbar with action",
+ ),
+ MDSnackbarSupportingText(
+ text="and close buttons at the bottom",
+ padding=[0, 0, 0, dp(56)],
+ ),
+ MDSnackbarButtonContainer(
+ Widget(),
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(text="Action button"),
+ ),
+ MDSnackbarCloseButton(
+ icon="close",
+ ),
+ ),
+ y=dp(124),
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
+ padding=[0, 0, "8dp", "8dp"],
+ ).open()
+
+
+Example().run()
diff --git a/examples/switch.py b/examples/switch.py
new file mode 100644
index 000000000..21405e130
--- /dev/null
+++ b/examples/switch.py
@@ -0,0 +1,40 @@
+from kivy.lang import Builder
+
+from kivymd.uix.selectioncontrol import MDSwitch
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ self.root.ids.widget_box.add_widget(
+ MDSwitch(
+ icon_active="check",
+ icon_inactive="close",
+ )
+ )
+
+ self.root.ids.custom_widget_box.add_widget(
+ MDSwitch(
+ icon_active="check",
+ icon_inactive="close",
+ md_bg_color_disabled="#b5b8b166",
+ thumb_color_disabled=[1, 0, 1, 0.4],
+ icon_active_color="white",
+ icon_inactive_color="red",
+ thumb_color_active="red",
+ thumb_color_inactive="white",
+ track_color_active="brown",
+ track_color_inactive="teal",
+ line_color_disabled="darkgrey",
+ theme_line_color="Custom",
+ line_color="red",
+ )
+ )
+
+
+Example().run()
diff --git a/examples/tab.py b/examples/tab.py
new file mode 100644
index 000000000..f37b18460
--- /dev/null
+++ b/examples/tab.py
@@ -0,0 +1,34 @@
+# from kivymd.icon_definitions import md_icons
+#
+# from kivymd.uix.screen import MDScreen
+# from kivymd.uix.tab import MDTabs, MDTabsItem, MDTabsIcon
+# from kivymd.app import MDApp
+# from kivymd.uix.tab.tab import MDTabsText
+#
+#
+# class Example(MDApp):
+# def build(self):
+# screen = MDScreen(
+# MDTabs(
+# id="tabs",
+# pos_hint={"top": 1},
+# ),
+# md_bg_color=self.theme_cls.backgroundColor,
+# )
+# for i, icon in enumerate(list(md_icons.keys())[0:30]):
+# screen.get_ids()["tabs"].add_widget(
+# MDTabsItem(
+# MDTabsIcon(
+# icon=icon,
+# text_color="red",
+# ),
+# MDTabsText(
+# text=f"Tab {i + 1}",
+# ),
+# theme_text_color="Custom",
+# )
+# )
+# return screen
+#
+#
+# Example().run()
diff --git a/examples/textfield.py b/examples/textfield.py
new file mode 100644
index 000000000..1c4a54314
--- /dev/null
+++ b/examples/textfield.py
@@ -0,0 +1,89 @@
+from kivy.lang import Builder
+
+from kivymd.uix.textfield import (
+ MDTextField,
+ MDTextFieldHelperText,
+ MDTextFieldHintText,
+ MDTextFieldLeadingIcon,
+ MDTextFieldTrailingIcon,
+ MDTextFieldMaxLengthText,
+)
+from kivymd.app import MDApp
+
+from examples.common_app import CommonApp, KV
+
+
+class Example(MDApp, CommonApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+ def on_start(self):
+ for mode in ["outlined", "filled", "required"]:
+ self.root.ids.widget_box.add_widget(
+ MDTextField(
+ MDTextFieldLeadingIcon(
+ icon="account",
+ ),
+ MDTextFieldHintText(
+ text="Hint text",
+ ),
+ MDTextFieldHelperText(
+ text="Helper text",
+ mode="persistent",
+ ),
+ MDTextFieldTrailingIcon(
+ icon="information",
+ ),
+ MDTextFieldMaxLengthText(
+ max_text_length=10,
+ ),
+ mode="filled" if mode == "required" else mode,
+ text=mode.capitalize() if mode != "required" else "",
+ required=mode == "required",
+ )
+ )
+ for mode in ["outlined", "filled", "required"]:
+ self.root.ids.custom_widget_box.add_widget(
+ MDTextField(
+ MDTextFieldLeadingIcon(
+ icon="account",
+ theme_icon_color="Custom",
+ icon_color_normal="mediumaquamarine",
+ icon_color_focus="tan",
+ ),
+ MDTextFieldHintText(
+ text="Hint text",
+ text_color_normal="mediumaquamarine",
+ text_color_focus="tan",
+ ),
+ MDTextFieldHelperText(
+ text="Helper text",
+ mode="persistent",
+ text_color_normal="mediumaquamarine",
+ text_color_focus="tan",
+ ),
+ MDTextFieldTrailingIcon(
+ icon="information",
+ theme_icon_color="Custom",
+ icon_color_normal="mediumaquamarine",
+ icon_color_focus="tan",
+ ),
+ MDTextFieldMaxLengthText(
+ max_text_length=10,
+ text_color_normal="mediumaquamarine",
+ text_color_focus="tan",
+ ),
+ mode="filled" if mode == "required" else mode,
+ text=mode.capitalize() if mode != "required" else "DDDD",
+ required=mode == "required",
+ theme_bg_color="Custom",
+ fill_color_normal="lightcyan",
+ fill_color_focus="lightsteelblue",
+ theme_line_color="Custom",
+ line_color_normal="mediumaquamarine",
+ line_color_focus="tan",
+ )
+ )
+
+
+Example().run()
diff --git a/kivymd/__init__.py b/kivymd/__init__.py
index bb05e0f6d..53f093299 100644
--- a/kivymd/__init__.py
+++ b/kivymd/__init__.py
@@ -26,6 +26,7 @@
import kivy
from kivy.logger import Logger
+
__version__ = "1.2.0.dev0"
"""KivyMD version."""
diff --git a/kivymd/color_definitions.py b/kivymd/color_definitions.py
deleted file mode 100755
index e4a7de462..000000000
--- a/kivymd/color_definitions.py
+++ /dev/null
@@ -1,954 +0,0 @@
-"""
-Themes/Color Definitions
-========================
-
-.. seealso::
-
- `Material Design spec, The color system `_
-
- `Material Design spec, The color tool `_
-
-Material colors palette to use in :class:`kivymd.theming.ThemeManager`.
-:data:`~colors` is a dict-in-dict where the first key is a value from
-:data:`~palette` and the second key is a value from :data:`~hue`. Color is a hex
-value, a string of 6 characters (0-9, A-F) written in uppercase.
-
-For example, ``colors["Red"]["900"]`` is ``"B71C1C"``.
-"""
-
-colors = {
- "Red": {
- "50": "FFEBEE",
- "100": "FFCDD2",
- "200": "EF9A9A",
- "300": "E57373",
- "400": "EF5350",
- "500": "F44336",
- "600": "E53935",
- "700": "D32F2F",
- "800": "C62828",
- "900": "B71C1C",
- "A100": "FF8A80",
- "A200": "FF5252",
- "A400": "FF1744",
- "A700": "D50000",
- },
- "Pink": {
- "50": "FCE4EC",
- "100": "F8BBD0",
- "200": "F48FB1",
- "300": "F06292",
- "400": "EC407A",
- "500": "E91E63",
- "600": "D81B60",
- "700": "C2185B",
- "800": "AD1457",
- "900": "880E4F",
- "A100": "FF80AB",
- "A200": "FF4081",
- "A400": "F50057",
- "A700": "C51162",
- },
- "Purple": {
- "50": "F3E5F5",
- "100": "E1BEE7",
- "200": "CE93D8",
- "300": "BA68C8",
- "400": "AB47BC",
- "500": "9C27B0",
- "600": "8E24AA",
- "700": "7B1FA2",
- "800": "6A1B9A",
- "900": "4A148C",
- "A100": "EA80FC",
- "A200": "E040FB",
- "A400": "D500F9",
- "A700": "AA00FF",
- },
- "DeepPurple": {
- "50": "EDE7F6",
- "100": "D1C4E9",
- "200": "B39DDB",
- "300": "9575CD",
- "400": "7E57C2",
- "500": "673AB7",
- "600": "5E35B1",
- "700": "512DA8",
- "800": "4527A0",
- "900": "311B92",
- "A100": "B388FF",
- "A200": "7C4DFF",
- "A400": "651FFF",
- "A700": "6200EA",
- },
- "Indigo": {
- "50": "E8EAF6",
- "100": "C5CAE9",
- "200": "9FA8DA",
- "300": "7986CB",
- "400": "5C6BC0",
- "500": "3F51B5",
- "600": "3949AB",
- "700": "303F9F",
- "800": "283593",
- "900": "1A237E",
- "A100": "8C9EFF",
- "A200": "536DFE",
- "A400": "3D5AFE",
- "A700": "304FFE",
- },
- "Blue": {
- "50": "E3F2FD",
- "100": "BBDEFB",
- "200": "90CAF9",
- "300": "64B5F6",
- "400": "42A5F5",
- "500": "2196F3",
- "600": "1E88E5",
- "700": "1976D2",
- "800": "1565C0",
- "900": "0D47A1",
- "A100": "82B1FF",
- "A200": "448AFF",
- "A400": "2979FF",
- "A700": "2962FF",
- },
- "LightBlue": {
- "50": "E1F5FE",
- "100": "B3E5FC",
- "200": "81D4FA",
- "300": "4FC3F7",
- "400": "29B6F6",
- "500": "03A9F4",
- "600": "039BE5",
- "700": "0288D1",
- "800": "0277BD",
- "900": "01579B",
- "A100": "80D8FF",
- "A200": "40C4FF",
- "A400": "00B0FF",
- "A700": "0091EA",
- },
- "Cyan": {
- "50": "E0F7FA",
- "100": "B2EBF2",
- "200": "80DEEA",
- "300": "4DD0E1",
- "400": "26C6DA",
- "500": "00BCD4",
- "600": "00ACC1",
- "700": "0097A7",
- "800": "00838F",
- "900": "006064",
- "A100": "84FFFF",
- "A200": "18FFFF",
- "A400": "00E5FF",
- "A700": "00B8D4",
- },
- "Teal": {
- "50": "E0F2F1",
- "100": "B2DFDB",
- "200": "80CBC4",
- "300": "4DB6AC",
- "400": "26A69A",
- "500": "009688",
- "600": "00897B",
- "700": "00796B",
- "800": "00695C",
- "900": "004D40",
- "A100": "A7FFEB",
- "A200": "64FFDA",
- "A400": "1DE9B6",
- "A700": "00BFA5",
- },
- "Green": {
- "50": "E8F5E9",
- "100": "C8E6C9",
- "200": "A5D6A7",
- "300": "81C784",
- "400": "66BB6A",
- "500": "4CAF50",
- "600": "43A047",
- "700": "388E3C",
- "800": "2E7D32",
- "900": "1B5E20",
- "A100": "B9F6CA",
- "A200": "69F0AE",
- "A400": "00E676",
- "A700": "00C853",
- },
- "LightGreen": {
- "50": "F1F8E9",
- "100": "DCEDC8",
- "200": "C5E1A5",
- "300": "AED581",
- "400": "9CCC65",
- "500": "8BC34A",
- "600": "7CB342",
- "700": "689F38",
- "800": "558B2F",
- "900": "33691E",
- "A100": "CCFF90",
- "A200": "B2FF59",
- "A400": "76FF03",
- "A700": "64DD17",
- },
- "Lime": {
- "50": "F9FBE7",
- "100": "F0F4C3",
- "200": "E6EE9C",
- "300": "DCE775",
- "400": "D4E157",
- "500": "CDDC39",
- "600": "C0CA33",
- "700": "AFB42B",
- "800": "9E9D24",
- "900": "827717",
- "A100": "F4FF81",
- "A200": "EEFF41",
- "A400": "C6FF00",
- "A700": "AEEA00",
- },
- "Yellow": {
- "50": "FFFDE7",
- "100": "FFF9C4",
- "200": "FFF59D",
- "300": "FFF176",
- "400": "FFEE58",
- "500": "FFEB3B",
- "600": "FDD835",
- "700": "FBC02D",
- "800": "F9A825",
- "900": "F57F17",
- "A100": "FFFF8D",
- "A200": "FFFF00",
- "A400": "FFEA00",
- "A700": "FFD600",
- },
- "Amber": {
- "50": "FFF8E1",
- "100": "FFECB3",
- "200": "FFE082",
- "300": "FFD54F",
- "400": "FFCA28",
- "500": "FFC107",
- "600": "FFB300",
- "700": "FFA000",
- "800": "FF8F00",
- "900": "FF6F00",
- "A100": "FFE57F",
- "A200": "FFD740",
- "A400": "FFC400",
- "A700": "FFAB00",
- },
- "Orange": {
- "50": "FFF3E0",
- "100": "FFE0B2",
- "200": "FFCC80",
- "300": "FFB74D",
- "400": "FFA726",
- "500": "FF9800",
- "600": "FB8C00",
- "700": "F57C00",
- "800": "EF6C00",
- "900": "E65100",
- "A100": "FFD180",
- "A200": "FFAB40",
- "A400": "FF9100",
- "A700": "FF6D00",
- },
- "DeepOrange": {
- "50": "FBE9E7",
- "100": "FFCCBC",
- "200": "FFAB91",
- "300": "FF8A65",
- "400": "FF7043",
- "500": "FF5722",
- "600": "F4511E",
- "700": "E64A19",
- "800": "D84315",
- "900": "BF360C",
- "A100": "FF9E80",
- "A200": "FF6E40",
- "A400": "FF3D00",
- "A700": "DD2C00",
- },
- "Brown": {
- "50": "EFEBE9",
- "100": "D7CCC8",
- "200": "BCAAA4",
- "300": "A1887F",
- "400": "8D6E63",
- "500": "795548",
- "600": "6D4C41",
- "700": "5D4037",
- "800": "4E342E",
- "900": "3E2723",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Gray": {
- "50": "FAFAFA",
- "100": "F5F5F5",
- "200": "EEEEEE",
- "300": "E0E0E0",
- "400": "BDBDBD",
- "500": "9E9E9E",
- "600": "757575",
- "700": "616161",
- "800": "424242",
- "900": "212121",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "BlueGray": {
- "50": "ECEFF1",
- "100": "CFD8DC",
- "200": "B0BEC5",
- "300": "90A4AE",
- "400": "78909C",
- "500": "607D8B",
- "600": "546E7A",
- "700": "455A64",
- "800": "37474F",
- "900": "263238",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Light": {
- "StatusBar": "E0E0E0",
- "AppBar": "F5F5F5",
- "Background": "FAFAFA",
- "CardsDialogs": "FFFFFF",
- "FlatButtonDown": "cccccc",
- },
- "Dark": {
- "StatusBar": "000000",
- "AppBar": "1f1f1f",
- "Background": "121212",
- "CardsDialogs": "212121",
- "FlatButtonDown": "999999",
- },
-}
-"""
-Color palette. Taken from `2014 Material Design color palettes
-`_.
-
-To demonstrate the shades of the palette, you can run the following code:
-
-.. code-block:: python
-
- from kivy.lang import Builder
- from kivy.properties import ListProperty, StringProperty
-
- from kivymd.color_definitions import colors
- from kivymd.uix.tab import MDTabsBase
- from kivymd.uix.boxlayout import MDBoxLayout
-
- demo = '''
-
- orientation: 'vertical'
-
- MDTopAppBar:
- title: app.title
-
- MDTabs:
- id: android_tabs
- on_tab_switch: app.on_tab_switch(*args)
- size_hint_y: None
- height: "48dp"
- tab_indicator_anim: False
-
- RecycleView:
- id: rv
- key_viewclass: "viewclass"
- key_size: "height"
-
- RecycleBoxLayout:
- default_size: None, dp(48)
- default_size_hint: 1, None
- size_hint_y: None
- height: self.minimum_height
- orientation: "vertical"
-
-
-
- size_hint_y: None
- height: "42dp"
-
- MDLabel:
- text: root.text
- halign: "center"
-
-
-
- '''
-
- from kivy.factory import Factory
-
- from kivymd.app import MDApp
-
-
- class Tab(MDBoxLayout, MDTabsBase):
- pass
-
-
- class ItemColor(MDBoxLayout):
- text = StringProperty()
- color = ListProperty()
-
-
- class Palette(MDApp):
- title = "Colors definitions"
-
- def build(self):
- Builder.load_string(demo)
- self.screen = Factory.Root()
-
- for name_tab in colors.keys():
- tab = Tab(title=name_tab)
- self.screen.ids.android_tabs.add_widget(tab)
- return self.screen
-
- def on_tab_switch(
- self, instance_tabs, instance_tab, instance_tabs_label, tab_text
- ):
- self.screen.ids.rv.data = []
- if not tab_text:
- tab_text = 'Red'
- for value_color in colors[tab_text]:
- self.screen.ids.rv.data.append(
- {
- "viewclass": "ItemColor",
- "md_bg_color": colors[tab_text][value_color],
- "title": value_color,
- }
- )
-
- def on_start(self):
- self.on_tab_switch(
- None,
- None,
- None,
- self.screen.ids.android_tabs.ids.layout.children[-1].text,
- )
-
-
- Palette().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/palette.gif
- :align: center
-"""
-
-palette = [
- "Red",
- "Pink",
- "Purple",
- "DeepPurple",
- "Indigo",
- "Blue",
- "LightBlue",
- "Cyan",
- "Teal",
- "Green",
- "LightGreen",
- "Lime",
- "Yellow",
- "Amber",
- "Orange",
- "DeepOrange",
- "Brown",
- "Gray",
- "BlueGray",
-]
-"""Valid values for color palette selecting."""
-
-hue = [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "700",
- "800",
- "900",
- "A100",
- "A200",
- "A400",
- "A700",
-]
-"""Valid values for color hue selecting."""
-
-
-light_colors = {
- "Red": ["50", "100", "200", "300", "A100"],
- "Pink": ["50", "100", "200", "A100"],
- "Purple": ["50", "100", "200", "A100"],
- "DeepPurple": ["50", "100", "200", "A100"],
- "Indigo": ["50", "100", "200", "A100"],
- "Blue": ["50", "100", "200", "300", "400", "A100"],
- "LightBlue": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "A100",
- "A200",
- "A400",
- ],
- "Cyan": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "Teal": ["50", "100", "200", "300", "400", "A100", "A200", "A400", "A700"],
- "Green": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "LightGreen": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "Lime": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "700",
- "800",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "Yellow": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "700",
- "800",
- "900",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "Amber": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "700",
- "800",
- "900",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "Orange": [
- "50",
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "700",
- "A100",
- "A200",
- "A400",
- "A700",
- ],
- "DeepOrange": ["50", "100", "200", "300", "400", "A100", "A200"],
- "Brown": ["50", "100", "200"],
- "Gray": ["50", "100", "200", "300", "400", "500"],
- "BlueGray": ["50", "100", "200", "300"],
- "Dark": [],
- "Light": ["White", "MainBackground", "DialogBackground"],
-}
-"""Which colors are light. Other are dark."""
-
-text_colors = {
- "Red": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "Pink": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "FFFFFF",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "Purple": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "FFFFFF",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "DeepPurple": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "FFFFFF",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "Indigo": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "FFFFFF",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "Blue": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "LightBlue": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "FFFFFF",
- },
- "Cyan": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "000000",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Teal": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Green": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "LightGreen": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "000000",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Lime": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "000000",
- "700": "000000",
- "800": "000000",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Yellow": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "000000",
- "700": "000000",
- "800": "000000",
- "900": "000000",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Amber": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "000000",
- "700": "000000",
- "800": "000000",
- "900": "000000",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "Orange": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "000000",
- "700": "000000",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "000000",
- "A700": "000000",
- },
- "DeepOrange": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "000000",
- "A200": "000000",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "Brown": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "FFFFFF",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "FFFFFF",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "Gray": {
- "50": "FFFFFF",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "000000",
- "500": "000000",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "FFFFFF",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
- "BlueGray": {
- "50": "000000",
- "100": "000000",
- "200": "000000",
- "300": "000000",
- "400": "FFFFFF",
- "500": "FFFFFF",
- "600": "FFFFFF",
- "700": "FFFFFF",
- "800": "FFFFFF",
- "900": "FFFFFF",
- "A100": "FFFFFF",
- "A200": "FFFFFF",
- "A400": "FFFFFF",
- "A700": "FFFFFF",
- },
-}
-"""
-Text colors generated from :data:`~light_colors`. "000000" for light and
-"FFFFFF" for dark.
-
-How to generate text_colors dict
-
-.. code-block:: python
-
- text_colors = {}
- for p in palette:
- text_colors[p] = {}
- for h in hue:
- if h in light_colors[p]:
- text_colors[p][h] = "000000"
- else:
- text_colors[p][h] = "FFFFFF"
-"""
-
-theme_colors = [
- "Primary",
- "Secondary",
- "Background",
- "Surface",
- "Error",
- "On_Primary",
- "On_Secondary",
- "On_Background",
- "On_Surface",
- "On_Error",
-]
-"""Valid theme colors."""
diff --git a/kivymd/dynamic_color.py b/kivymd/dynamic_color.py
new file mode 100644
index 000000000..bbe8b2679
--- /dev/null
+++ b/kivymd/dynamic_color.py
@@ -0,0 +1,632 @@
+"""
+Components/Dynamic color
+========================
+
+.. seealso::
+
+ `Material Design spec, Dynamic color
+ `_
+
+.. rubric:: Dynamic color can create accessible UI color schemes based on
+ content or user settings
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color.png
+ :align: center
+
+Dynamic color experiences are built with M3 color schemes. Beginning with
+Android 12, users can generate individualized schemes through wallpaper
+selection and other customization settings. With M3 as a foundation,
+user-generated colors can coexist with app colors, putting a range of
+customizable visual experiences in the hands of users.
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color-preview.png
+ :align: center
+
+1. Baseline scheme
+2. Colors extracted from a wallpaper
+3. Colors extracted from content
+
+Example of dynamic color from the list of standard color schemes
+----------------------------------------------------------------
+
+.. code-block:: python
+
+ from kivy.clock import Clock
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty, ColorProperty
+ from kivy.uix.boxlayout import BoxLayout
+ from kivy.utils import hex_colormap
+
+ from kivymd.uix.menu import MDDropdownMenu
+ from kivymd.app import MDApp
+
+
+ KV = '''
+
+ orientation: "vertical"
+
+ MDLabel:
+ text: root.text
+ color: "grey"
+ adaptive_height: True
+
+ MDCard:
+ theme_bg_color: "Custom"
+ md_bg_color: root.bg_color
+
+
+ MDScreen:
+ md_bg_color: app.theme_cls.backgroundColor
+
+ MDIconButton:
+ on_release: app.open_menu(self)
+ pos_hint: {"top": .98}
+ x: "12dp"
+ icon: "menu"
+
+ MDRecycleView:
+ id: card_list
+ viewclass: "ColorCard"
+ bar_width: 0
+ size_hint_y: None
+ height: root.height - dp(68)
+
+ RecycleGridLayout:
+ cols: 3
+ spacing: "16dp"
+ padding: "16dp"
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+ '''
+
+
+ class ColorCard(BoxLayout):
+ text = StringProperty()
+ bg_color = ColorProperty()
+
+
+ class Example(MDApp):
+ menu: MDDropdownMenu = None
+
+ def build(self):
+ self.theme_cls.dynamic_color = True
+ return Builder.load_string(KV)
+
+ def get_instance_from_menu(self, name_item):
+ index = 0
+ rv = self.menu.ids.md_menu
+ opts = rv.layout_manager.view_opts
+ datas = rv.data[0]
+
+ for data in rv.data:
+ if data["text"] == name_item:
+ index = rv.data.index(data)
+ break
+
+ instance = rv.view_adapter.get_view(
+ index, datas, opts[index]["viewclass"]
+ )
+
+ return instance
+
+ def open_menu(self, menu_button):
+ menu_items = []
+ for item, method in {
+ "Set palette": lambda: self.set_palette(),
+ "Switch theme style": lambda: self.theme_switch(),
+ }.items():
+ menu_items.append({"text": item, "on_release": method})
+ self.menu = MDDropdownMenu(
+ caller=menu_button,
+ items=menu_items,
+ )
+ self.menu.open()
+
+ def set_palette(self):
+ instance_from_menu = self.get_instance_from_menu("Set palette")
+ available_palettes = [
+ name_color.capitalize() for name_color in hex_colormap.keys()
+ ]
+
+ menu_items = []
+ for name_palette in available_palettes:
+ menu_items.append(
+ {
+ "text": name_palette,
+ "on_release": lambda x=name_palette: self.switch_palette(x),
+ }
+ )
+ MDDropdownMenu(
+ caller=instance_from_menu,
+ items=menu_items,
+ ).open()
+
+ def switch_palette(self, selected_palette):
+ self.theme_cls.primary_palette = selected_palette
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def theme_switch(self) -> None:
+ self.theme_cls.switch_theme()
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def generate_cards(self, *args):
+ self.root.ids.card_list.data = []
+ for color in self.theme_cls.schemes_name_colors:
+ value = f"{color}Color"
+ self.root.ids.card_list.data.append(
+ {
+ "bg_color": getattr(self.theme_cls, value),
+ "text": value,
+ }
+ )
+
+ def on_start(self):
+ Clock.schedule_once(self.generate_cards)
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color.gif
+ :align: center
+
+Example of a dynamic color from an image
+----------------------------------------
+
+.. seealso::
+
+ :attr:`kivymd.theming.ThemeManager.path_to_wallpaper`
+
+.. code-block:: python
+
+ import os
+
+ from kivy.clock import Clock
+ from kivy.core.window import Window
+ from kivy.core.window.window_sdl2 import WindowSDL
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty, ColorProperty
+
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.app import MDApp
+
+
+ KV = '''
+
+ orientation: "vertical"
+
+ MDLabel:
+ text: root.text
+ color: "grey"
+ adaptive_height: True
+
+ MDCard:
+ theme_bg_color: "Custom"
+ md_bg_color: root.bg_color
+
+
+ MDScreen:
+ md_bg_color: app.theme_cls.backgroundColor
+
+ MDRecycleView:
+ id: card_list
+ viewclass: "ColorCard"
+ bar_width: 0
+
+ RecycleGridLayout:
+ cols: 3
+ spacing: "16dp"
+ padding: "16dp"
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+ '''
+
+
+ class ColorCard(MDBoxLayout):
+ text = StringProperty()
+ bg_color = ColorProperty()
+
+
+ class Example(MDApp):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ Window.bind(on_dropfile=self.on_drop_file)
+
+ def on_drop_file(self, sdl: WindowSDL, path_to_file: str) -> None:
+ ext = os.path.splitext(path_to_file)[1]
+ if isinstance(path_to_file, bytes):
+ path_to_file = path_to_file.decode()
+ if isinstance(ext, bytes):
+ ext = ext.decode()
+ if ext in [".png", ".jpg"]:
+ self.theme_cls.path_to_wallpaper = path_to_file
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def build(self):
+ self.theme_cls.dynamic_color = True
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
+
+ def theme_switch(self) -> None:
+ self.theme_cls.switch_theme()
+ Clock.schedule_once(self.generate_cards, 0.5)
+
+ def generate_cards(self, *args):
+ self.root.ids.card_list.data = []
+ for color in self.theme_cls.schemes_name_colors:
+ value = f"{color}Color"
+ self.root.ids.card_list.data.append(
+ {
+ "bg_color": getattr(self.theme_cls, value),
+ "text": value,
+ }
+ )
+
+ def on_start(self):
+ Clock.schedule_once(self.generate_cards)
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color-path-to_wallpapper.gif
+ :align: center
+"""
+
+from kivy.properties import ColorProperty
+
+
+class DynamicColor:
+ """
+ Dynamic color class.
+
+ .. versionadded:: 2.0.0
+ """
+
+ # Primary.
+ primaryColor = ColorProperty()
+ """
+ Primary color.
+
+ :attr:`primaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ primaryContainerColor = ColorProperty()
+ """
+ Primary container color.
+
+ :attr:`primaryContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # On Primary.
+ onPrimaryColor = ColorProperty()
+ """
+ On primary color.
+
+ :attr:`onPrimaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ onPrimaryContainerColor = ColorProperty()
+ """
+ On primary container color.
+
+ :attr:`onPrimaryContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Secondary.
+ secondaryColor = ColorProperty()
+ """
+ Secondary color.
+
+ :attr:`secondaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ secondaryContainerColor = ColorProperty()
+ """
+ Secondary container color.
+
+ :attr:`secondaryContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # On Secondary.
+ onSecondaryColor = ColorProperty()
+ """
+ On secondary color.
+
+ :attr:`onSecondaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ onSecondaryContainerColor = ColorProperty()
+ """
+ On secondary container color.
+
+ :attr:`onSecondaryContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Tertiary.
+ tertiaryColor = ColorProperty()
+ """
+ Tertiary color.
+
+ :attr:`tertiaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ tertiaryContainerColor = ColorProperty()
+ """
+ Tertiary container color.
+
+ :attr:`tertiaryContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # On Tertiary.
+ onTertiaryColor = ColorProperty()
+ """
+ On tertiary color.
+
+ :attr:`onTertiaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ onTertiaryContainerColor = ColorProperty()
+ """
+ On tertiary container color.
+
+ :attr:`onTertiaryContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Surface.
+ surfaceColor = ColorProperty()
+ """
+ Surface color.
+
+ :attr:`surfaceColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceDimColor = ColorProperty()
+ """
+ Surface dim color.
+
+ :attr:`surfaceDimColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceBrightColor = ColorProperty()
+ """
+ Surface bright color.
+
+ :attr:`surfaceBrightColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceContainerLowestColor = ColorProperty()
+ """
+ Surface container lowest color.
+
+ :attr:`surfaceContainerLowestColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceContainerLowColor = ColorProperty()
+ """
+ Surface container low color.
+
+ :attr:`surfaceContainerLowColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceContainerColor = ColorProperty()
+ """
+ Surface container color.
+
+ :attr:`surfaceContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceContainerHighColor = ColorProperty()
+ """
+ Surface container high color.
+
+ :attr:`surfaceContainerHighColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceContainerHighestColor = ColorProperty()
+ """
+ Surface container highest color.
+
+ :attr:`surfaceContainerHighestColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceVariantColor = ColorProperty()
+ """
+ Surface variant color.
+
+ :attr:`surfaceVariantColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ surfaceTintColor = ColorProperty()
+ """
+ Surface tint color.
+
+ :attr:`surfaceTintColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # On Surface.
+ onSurfaceColor = ColorProperty()
+ """
+ On surface color.
+
+ :attr:`onSurfaceColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ onSurfaceLightColor = ColorProperty()
+ """
+ On surface light color.
+
+ :attr:`onSurfaceLightColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ onSurfaceVariantColor = ColorProperty()
+ """
+ On surface variant color.
+
+ :attr:`onSurfaceVariantColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Inverse.
+ inverseSurfaceColor = ColorProperty()
+ """
+ Inverse surface color.
+
+ :attr:`inverseSurfaceColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ inverseOnSurfaceColor = ColorProperty()
+ """
+ Inverse on surface color.
+
+ :attr:`inverseOnSurfaceColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ inversePrimaryColor = ColorProperty()
+ """
+ Inverse primary color.
+
+ :attr:`inversePrimaryColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Background.
+ backgroundColor = ColorProperty()
+ """
+ Background color.
+
+ :attr:`backgroundColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # On Background.
+ onBackgroundColor = ColorProperty()
+ """
+ On background color.
+
+ :attr:`onBackgroundColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Error.
+ errorColor = ColorProperty()
+ """
+ Error color.
+
+ :attr:`errorColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ errorContainerColor = ColorProperty()
+ """
+ Error container color.
+
+ :attr:`errorContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # On Error.
+ onErrorColor = ColorProperty()
+ """
+ On error color.
+
+ :attr:`onErrorColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ onErrorContainerColor = ColorProperty()
+ """
+ On error container color.
+
+ :attr:`onErrorContainerColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Outline.
+ outlineColor = ColorProperty()
+ """
+ Outline color.
+
+ :attr:`outlineColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ outlineVariantColor = ColorProperty()
+ """
+ Outline variant color.
+
+ :attr:`outlineVariantColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Shadow/scrim.
+ shadowColor = ColorProperty()
+ """
+ Shadow color.
+
+ :attr:`shadowColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ scrimColor = ColorProperty()
+ """
+ Scrim color.
+
+ :attr:`scrimColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Disabled.
+ disabledTextColor = ColorProperty()
+ """
+ Disabled text color.
+
+ :attr:`disabledTextColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ # Transparent.
+ transparentColor = ColorProperty([0, 0, 0, 0])
+ """
+ Transparent color.
+
+ :attr:`transparentColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0, 0, 0, 0]`.
+ """
+
+ # Ripple.
+ rippleColor = ColorProperty("#BDBDBD")
+ """
+ Ripple color.
+
+ :attr:`rippleColor` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `'#BDBDBD'`.
+ """
diff --git a/kivymd/factory_registers.py b/kivymd/factory_registers.py
index 4497bfe55..03b55750f 100644
--- a/kivymd/factory_registers.py
+++ b/kivymd/factory_registers.py
@@ -5,8 +5,11 @@
from kivy.factory import Factory
register = Factory.register
+# register("MDDataTable", module="kivymd.uix.datatables")
register("MDSegmentedButton", module="kivymd.uix.segmentedbutton")
register("MDSegmentedButtonItem", module="kivymd.uix.segmentedbutton")
+register("MDSegmentButtonIcon", module="kivymd.uix.segmentedbutton")
+register("MDSegmentButtonLabel", module="kivymd.uix.segmentedbutton")
register("MDScrollView", module="kivymd.uix.scrollview")
register("MDRecycleView", module="kivymd.uix.recycleview")
register("MDResponsiveLayout", module="kivymd.uix.responsivelayout")
@@ -19,6 +22,8 @@
register("MDNavigationRail", module="kivymd.uix.navigationrail")
register("MDNavigationRailFabButton", module="kivymd.uix.navigationrail")
register("MDNavigationRailMenuButton", module="kivymd.uix.navigationrail")
+register("MDNavigationRailItemIcon", module="kivymd.uix.navigationrail")
+register("MDNavigationRailItemLabel", module="kivymd.uix.navigationrail")
register("MDSwiper", module="kivymd.uix.swiper")
register("MDCarousel", module="kivymd.uix.carousel")
register("MDWidget", module="kivymd.uix.widget")
@@ -37,61 +42,56 @@
register("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel")
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("MDNavigationBar", module="kivymd.uix.navigationbar")
+register("MDNavigationItem", module="kivymd.uix.navigationbar")
+register("MDNavigationItemLabel", module="kivymd.uix.navigationbar")
+register("MDNavigationItemIcon", module="kivymd.uix.navigationbar")
register("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior")
-register("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button")
+register("MDButton", module="kivymd.uix.button")
+register("MDButtonText", module="kivymd.uix.button")
+register("MDButtonIcon", module="kivymd.uix.button")
+register("MDFabButton", module="kivymd.uix.button")
register("MDIconButton", module="kivymd.uix.button")
-register("MDRoundImageButton", module="kivymd.uix.button")
-register("MDFlatButton", module="kivymd.uix.button")
-register("MDRaisedButton", module="kivymd.uix.button")
-register("MDFloatingActionButton", module="kivymd.uix.button")
-register("MDRectangleFlatButton", module="kivymd.uix.button")
-register("MDTextButton", module="kivymd.uix.button")
-register("MDCustomRoundIconButton", module="kivymd.uix.button")
-register("MDRoundFlatButton", module="kivymd.uix.button")
-register("MDFillRoundFlatButton", module="kivymd.uix.button")
-register("MDRectangleFlatIconButton", module="kivymd.uix.button")
-register("MDRoundFlatIconButton", module="kivymd.uix.button")
-register("MDFillRoundFlatIconButton", module="kivymd.uix.button")
+register("MDExtendedFabButton", module="kivymd.uix.button")
+register("MDExtendedFabButtonIcon", module="kivymd.uix.button")
+register("MDExtendedFabButtonText", module="kivymd.uix.button")
register("MDCard", module="kivymd.uix.card")
-register("MDSeparator", module="kivymd.uix.card")
-register("MDSelectionList", module="kivymd.uix.selection")
+register("MDDivider", module="kivymd.uix.divider")
register("MDChip", module="kivymd.uix.chip")
+register("MDChipLeadingAvatar", module="kivymd.uix.chip")
+register("MDChipLeadingIcon", module="kivymd.uix.chip")
+register("MDChipTrailingIcon", module="kivymd.uix.chip")
+register("MDChipText", module="kivymd.uix.chip")
register("MDSmartTile", module="kivymd.uix.imagelist")
register("MDLabel", module="kivymd.uix.label")
register("MDIcon", module="kivymd.uix.label")
+register("MDBadge", module="kivymd.uix.badge")
register("MDList", module="kivymd.uix.list")
-register("ILeftBody", module="kivymd.uix.list")
-register("ILeftBodyTouch", module="kivymd.uix.list")
-register("IRightBody", module="kivymd.uix.list")
-register("IRightBodyTouch", module="kivymd.uix.list")
-register("OneLineListItem", module="kivymd.uix.list")
-register("TwoLineListItem", module="kivymd.uix.list")
-register("ThreeLineListItem", module="kivymd.uix.list")
-register("OneLineAvatarListItem", module="kivymd.uix.list")
-register("TwoLineAvatarListItem", module="kivymd.uix.list")
-register("ThreeLineAvatarListItem", module="kivymd.uix.list")
-register("OneLineIconListItem", module="kivymd.uix.list")
-register("TwoLineIconListItem", module="kivymd.uix.list")
-register("ThreeLineIconListItem", module="kivymd.uix.list")
-register("OneLineRightIconListItem", module="kivymd.uix.list")
-register("TwoLineRightIconListItem", module="kivymd.uix.list")
-register("ThreeLineRightIconListItem", module="kivymd.uix.list")
-register("OneLineAvatarIconListItem", module="kivymd.uix.list")
-register("TwoLineAvatarIconListItem", module="kivymd.uix.list")
-register("ThreeLineAvatarIconListItem", module="kivymd.uix.list")
+register("MDListItem", module="kivymd.uix.list")
+register("MDListItemHeadlineText", module="kivymd.uix.list")
+register("MDListItemSupportingText", module="kivymd.uix.list")
+register("MDListItemTrailingSupportingText", module="kivymd.uix.list")
+register("MDListItemLeadingIcon", module="kivymd.uix.list")
+register("MDListItemTrailingIcon", module="kivymd.uix.list")
+register("MDListItemTrailingCheckbox", module="kivymd.uix.list")
+register("MDListItemTertiaryText", module="kivymd.uix.list")
register("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior")
register("FocusBehavior", module="kivymd.uix.behaviors.focus_behavior")
register("MagicBehavior", module="kivymd.uix.behaviors.magic_behavior")
register("MDNavigationDrawer", module="kivymd.uix.navigationdrawer")
register("MDNavigationLayout", module="kivymd.uix.navigationdrawer")
register("MDNavigationDrawerMenu", module="kivymd.uix.navigationdrawer")
-register("MDNavigationDrawerHeader", module="kivymd.uix.navigationdrawer")
register("MDNavigationDrawerItem", module="kivymd.uix.navigationdrawer")
+register(
+ "MDNavigationDrawerItemLeadingIcon", module="kivymd.uix.navigationdrawer"
+)
+register(
+ "MDNavigationDrawerItemTrailingText", module="kivymd.uix.navigationdrawer"
+)
+register("MDNavigationDrawerHeader", module="kivymd.uix.navigationdrawer")
+register("MDNavigationDrawerItemText", module="kivymd.uix.navigationdrawer")
register("MDNavigationDrawerLabel", module="kivymd.uix.navigationdrawer")
register("MDNavigationDrawerDivider", module="kivymd.uix.navigationdrawer")
register("MDProgressBar", module="kivymd.uix.progressbar")
@@ -102,9 +102,19 @@
register("MDSpinner", module="kivymd.uix.spinner")
register("MDTabs", module="kivymd.uix.tab")
register("MDTextField", module="kivymd.uix.textfield")
-register("MDTextFieldRect", module="kivymd.uix.textfield")
-register("MDTopAppBar", module="kivymd.uix.toolbar")
-register("MDBottomAppBar", module="kivymd.uix.toolbar")
+register("MDTextFieldHelperText", module="kivymd.uix.textfield")
+register("MDTextFieldMaxLengthText", module="kivymd.uix.textfield")
+register("MDTextFieldHintText", module="kivymd.uix.textfield")
+register("MDTextFieldLeadingIcon", module="kivymd.uix.textfield")
+register("MDTextFieldTrailingIcon", module="kivymd.uix.textfield")
+register("MDTopAppBarTrailingButtonContainer", module="kivymd.uix.appbar")
+register("MDTopAppBarLeadingButtonContainer", module="kivymd.uix.appbar")
+register("MDFabBottomAppBarButton", module="kivymd.uix.appbar")
+register("MDActionBottomAppBarButton", module="kivymd.uix.appbar")
+register("MDTopAppBarTitle", module="kivymd.uix.appbar")
+register("MDTopAppBar", module="kivymd.uix.appbar")
+register("MDBottomAppBar", module="kivymd.uix.appbar")
+register("MDActionTopAppBarButton", module="kivymd.uix.appbar")
register("MDDropDownItem", module="kivymd.uix.dropdownitem")
register("MDCircularLayout", module="kivymd.uix.circularlayout")
register("MDHeroFrom", module="kivymd.uix.hero")
diff --git a/kivymd/font_definitions.py b/kivymd/font_definitions.py
index e7cc8e2b1..c3637e4df 100755
--- a/kivymd/font_definitions.py
+++ b/kivymd/font_definitions.py
@@ -1,5 +1,5 @@
"""
-Themes/Font Definitions
+Themes/Font definitions
=======================
.. seealso::
@@ -8,6 +8,7 @@
"""
from kivy.core.text import LabelBase
+from kivy.metrics import sp
from kivymd import fonts_path
@@ -48,22 +49,102 @@
for font in fonts:
LabelBase.register(**font)
-theme_font_styles = [
- "H1",
- "H2",
- "H3",
- "H4",
- "H5",
- "H6",
- "Subtitle1",
- "Subtitle2",
- "Body1",
- "Body2",
- "Button",
- "Caption",
- "Overline",
- "Icon",
-]
+# TODO: Add `weight` properties.
+theme_font_styles = {
+ "Icon": {
+ "large": {
+ "line-height": 1,
+ "font-name": "Icons",
+ "font-size": sp(24),
+ },
+ },
+ "Display": {
+ "large": {
+ "line-height": 1.64,
+ "font-name": "Roboto",
+ "font-size": sp(57),
+ },
+ "medium": {
+ "line-height": 1.52,
+ "font-name": "Roboto",
+ "font-size": sp(45),
+ },
+ "small": {
+ "line-height": 1.44,
+ "font-name": "Roboto",
+ "font-size": sp(36),
+ },
+ },
+ "Headline": {
+ "large": {
+ "line-height": 1.40,
+ "font-name": "Roboto",
+ "font-size": sp(32),
+ },
+ "medium": {
+ "line-height": 1.36,
+ "font-name": "Roboto",
+ "font-size": sp(28),
+ },
+ "small": {
+ "line-height": 1.32,
+ "font-name": "Roboto",
+ "font-size": sp(24),
+ },
+ },
+ "Title": {
+ "large": {
+ "line-height": 1.28,
+ "font-name": "Roboto",
+ "font-size": sp(22),
+ },
+ "medium": {
+ "line-height": 1.24,
+ "font-name": "Roboto",
+ "font-size": sp(16),
+ },
+ "small": {
+ "line-height": 1.20,
+ "font-name": "Roboto",
+ "font-size": sp(14),
+ },
+ },
+ "Body": {
+ "large": {
+ "line-height": 1.24,
+ "font-name": "Roboto",
+ "font-size": sp(16),
+ },
+ "medium": {
+ "line-height": 1.20,
+ "font-name": "Roboto",
+ "font-size": sp(14),
+ },
+ "small": {
+ "line-height": 1.16,
+ "font-name": "Roboto",
+ "font-size": sp(12),
+ },
+ },
+ "Label": {
+ "large": {
+ "line-height": 1.20,
+ "font-name": "Roboto",
+ "font-size": sp(14),
+ },
+ "medium": {
+ "line-height": 1.16,
+ "font-name": "Roboto",
+ "font-size": sp(12),
+ },
+ "small": {
+ "line-height": 1.16,
+ "font-name": "Roboto",
+ "font-size": sp(11),
+ },
+ },
+}
"""
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles-2.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label-font-style-preview.png
+ :align: center
"""
diff --git a/kivymd/fonts/materialdesignicons-webfont.ttf b/kivymd/fonts/materialdesignicons-webfont.ttf
index b00c684d3..53061f16f 100644
Binary files a/kivymd/fonts/materialdesignicons-webfont.ttf and b/kivymd/fonts/materialdesignicons-webfont.ttf differ
diff --git a/kivymd/icon_definitions.py b/kivymd/icon_definitions.py
index 53cb54072..9553457c4 100755
--- a/kivymd/icon_definitions.py
+++ b/kivymd/icon_definitions.py
@@ -12,7 +12,7 @@
List of icons from materialdesignicons.com. These expanded material design
icons are maintained by Austin Andrews (Templarian on Github).
-LAST UPDATED: Version 7.2.96
+LAST UPDATED: Version 7.1.96
To preview the icons and their names, you can use the following application:
----------------------------------------------------------------------------
@@ -779,7 +779,6 @@ def on_start(self):
"aurora": "\U000F1BB9",
"auto-download": "\U000F137E",
"auto-fix": "\U000F0068",
- "auto-mode": "\U000F1C20",
"auto-upload": "\U000F0069",
"autorenew": "\U000F006A",
"autorenew-off": "\U000F19E7",
@@ -879,7 +878,6 @@ def on_start(self):
"baseball-bat": "\U000F0853",
"baseball-diamond": "\U000F15EC",
"baseball-diamond-outline": "\U000F15ED",
- "baseball-outline": "\U000F1C5A",
"bash": "\U000F1183",
"basket": "\U000F0076",
"basket-check": "\U000F18E5",
@@ -1046,8 +1044,6 @@ def on_start(self):
"bell-ring-outline": "\U000F009F",
"bell-sleep": "\U000F00A0",
"bell-sleep-outline": "\U000F0A93",
- "bench": "\U000F1C21",
- "bench-back": "\U000F1C22",
"beta": "\U000F00A1",
"betamax": "\U000F09CB",
"biathlon": "\U000F0E14",
@@ -1058,9 +1054,6 @@ def on_start(self):
"bicycle-penny-farthing": "\U000F15E9",
"bike": "\U000F00A3",
"bike-fast": "\U000F111F",
- "bike-pedal": "\U000F1C23",
- "bike-pedal-clipless": "\U000F1C24",
- "bike-pedal-mountain": "\U000F1C25",
"billboard": "\U000F1010",
"billiards": "\U000F0B61",
"billiards-rack": "\U000F0B62",
@@ -1417,7 +1410,6 @@ def on_start(self):
"calendar-search": "\U000F094C",
"calendar-search-outline": "\U000F1B6E",
"calendar-star": "\U000F09D3",
- "calendar-star-four-points": "\U000F1C1F",
"calendar-star-outline": "\U000F1B53",
"calendar-start": "\U000F166D",
"calendar-start-outline": "\U000F1B6F",
@@ -1645,7 +1637,6 @@ def on_start(self):
"cash-marker": "\U000F0DB8",
"cash-minus": "\U000F1260",
"cash-multiple": "\U000F0116",
- "cash-off": "\U000F1C79",
"cash-plus": "\U000F1261",
"cash-refund": "\U000F0A9C",
"cash-register": "\U000F0CF4",
@@ -1764,8 +1755,6 @@ def on_start(self):
"check-underline-circle": "\U000F0E20",
"check-underline-circle-outline": "\U000F0E21",
"checkbook": "\U000F0A9D",
- "checkbook-arrow-left": "\U000F1C1D",
- "checkbook-arrow-right": "\U000F1C1E",
"checkbox-blank": "\U000F012E",
"checkbox-blank-badge": "\U000F1176",
"checkbox-blank-badge-outline": "\U000F0117",
@@ -1778,8 +1767,6 @@ def on_start(self):
"checkbox-intermediate-variant": "\U000F1B54",
"checkbox-marked": "\U000F0132",
"checkbox-marked-circle": "\U000F0133",
- "checkbox-marked-circle-auto-outline": "\U000F1C26",
- "checkbox-marked-circle-minus-outline": "\U000F1C27",
"checkbox-marked-circle-outline": "\U000F0134",
"checkbox-marked-circle-plus-outline": "\U000F1927",
"checkbox-marked-outline": "\U000F0135",
@@ -1875,7 +1862,6 @@ def on_start(self):
"circle-small": "\U000F09DF",
"circular-saw": "\U000F0E22",
"city": "\U000F0146",
- "city-switch": "\U000F1C28",
"city-variant": "\U000F0A36",
"city-variant-outline": "\U000F0A37",
"clipboard": "\U000F0147",
@@ -1956,8 +1942,6 @@ def on_start(self):
"clock-plus-outline": "\U000F1862",
"clock-remove": "\U000F1865",
"clock-remove-outline": "\U000F1866",
- "clock-star-four-points": "\U000F1C29",
- "clock-star-four-points-outline": "\U000F1C2A",
"clock-start": "\U000F0155",
"clock-time-eight": "\U000F1446",
"clock-time-eight-outline": "\U000F1452",
@@ -2058,7 +2042,6 @@ def on_start(self):
"cloud-upload-outline": "\U000F0B7E",
"clouds": "\U000F1B95",
"clover": "\U000F0816",
- "clover-outline": "\U000F1C62",
"coach-lamp": "\U000F1020",
"coach-lamp-variant": "\U000F1A37",
"coat-rack": "\U000F109E",
@@ -2265,7 +2248,6 @@ def on_start(self):
"cradle-outline": "\U000F1991",
"crane": "\U000F0862",
"creation": "\U000F0674",
- "creation-outline": "\U000F1C2B",
"creative-commons": "\U000F0D6B",
"credit-card": "\U000F0FEF",
"credit-card-check": "\U000F13D0",
@@ -2578,7 +2560,6 @@ def on_start(self):
"domain-off": "\U000F0D6F",
"domain-plus": "\U000F10AD",
"domain-remove": "\U000F10AE",
- "domain-switch": "\U000F1C2C",
"dome-light": "\U000F141E",
"domino-mask": "\U000F1023",
"donkey": "\U000F07C2",
@@ -2694,7 +2675,6 @@ def on_start(self):
"email-edit-outline": "\U000F0EE4",
"email-fast": "\U000F186F",
"email-fast-outline": "\U000F1870",
- "email-heart-outline": "\U000F1C5B",
"email-lock": "\U000F01F1",
"email-lock-outline": "\U000F1B61",
"email-mark-as-unread": "\U000F0B92",
@@ -2706,7 +2686,6 @@ def on_start(self):
"email-off": "\U000F13E3",
"email-off-outline": "\U000F13E4",
"email-open": "\U000F01EF",
- "email-open-heart-outline": "\U000F1C5C",
"email-open-multiple": "\U000F0EE9",
"email-open-multiple-outline": "\U000F0EEA",
"email-open-outline": "\U000F05EF",
@@ -2936,8 +2915,6 @@ def on_start(self):
"file-document-outline": "\U000F09EE",
"file-document-plus": "\U000F1A9D",
"file-document-plus-outline": "\U000F1A9E",
- "file-document-refresh": "\U000F1C7A",
- "file-document-refresh-outline": "\U000F1C7B",
"file-document-remove": "\U000F1A9F",
"file-document-remove-outline": "\U000F1AA0",
"file-download": "\U000F0965",
@@ -3023,8 +3000,6 @@ def on_start(self):
"file-settings-outline": "\U000F107A",
"file-sign": "\U000F19C3",
"file-star": "\U000F103A",
- "file-star-four-points": "\U000F1C2D",
- "file-star-four-points-outline": "\U000F1C2E",
"file-star-outline": "\U000F103B",
"file-swap": "\U000F0FB4",
"file-swap-outline": "\U000F0FB5",
@@ -3324,7 +3299,6 @@ def on_start(self):
"football-australian": "\U000F025E",
"football-helmet": "\U000F025F",
"forest": "\U000F1897",
- "forest-outline": "\U000F1C63",
"forklift": "\U000F07C9",
"form-dropdown": "\U000F1400",
"form-select": "\U000F1401",
@@ -3880,8 +3854,6 @@ def on_start(self):
"home-off": "\U000F1A46",
"home-off-outline": "\U000F1A47",
"home-outline": "\U000F06A1",
- "home-percent": "\U000F1C7C",
- "home-percent-outline": "\U000F1C7D",
"home-plus": "\U000F0975",
"home-plus-outline": "\U000F13D6",
"home-remove": "\U000F1247",
@@ -3891,10 +3863,6 @@ def on_start(self):
"home-search-outline": "\U000F13B1",
"home-silo": "\U000F1BA0",
"home-silo-outline": "\U000F1BA1",
- "home-sound-in": "\U000F1C2F",
- "home-sound-in-outline": "\U000F1C30",
- "home-sound-out": "\U000F1C31",
- "home-sound-out-outline": "\U000F1C32",
"home-switch": "\U000F1794",
"home-switch-outline": "\U000F1795",
"home-thermometer": "\U000F0F54",
@@ -3991,7 +3959,6 @@ def on_start(self):
"image-filter-drama-outline": "\U000F1BFF",
"image-filter-frames": "\U000F02F4",
"image-filter-hdr": "\U000F02F5",
- "image-filter-hdr-outline": "\U000F1C64",
"image-filter-none": "\U000F02F6",
"image-filter-tilt-shift": "\U000F02F7",
"image-filter-vintage": "\U000F02F8",
@@ -4042,22 +4009,10 @@ def on_start(self):
"induction": "\U000F184C",
"infinity": "\U000F06E4",
"information": "\U000F02FC",
- "information-box": "\U000F1C65",
- "information-box-outline": "\U000F1C66",
"information-off": "\U000F178C",
"information-off-outline": "\U000F178D",
"information-outline": "\U000F02FD",
- "information-slab-box": "\U000F1C67",
- "information-slab-box-outline": "\U000F1C68",
- "information-slab-circle": "\U000F1C69",
- "information-slab-circle-outline": "\U000F1C6A",
- "information-slab-symbol": "\U000F1C6B",
- "information-symbol": "\U000F1C6C",
"information-variant": "\U000F064E",
- "information-variant-box": "\U000F1C6D",
- "information-variant-box-outline": "\U000F1C6E",
- "information-variant-circle": "\U000F1C6F",
- "information-variant-circle-outline": "\U000F1C70",
"instagram": "\U000F02FE",
"instrument-triangle": "\U000F104E",
"integrated-circuit-chip": "\U000F1913",
@@ -4178,7 +4133,6 @@ def on_start(self):
"land-plots": "\U000F1AB3",
"land-plots-circle": "\U000F1AB4",
"land-plots-circle-variant": "\U000F1AB5",
- "land-plots-marker": "\U000F1C5D",
"land-rows-horizontal": "\U000F1AB6",
"land-rows-vertical": "\U000F1AB7",
"landslide": "\U000F1A48",
@@ -4661,7 +4615,6 @@ def on_start(self):
"monitor-speaker": "\U000F0F5F",
"monitor-speaker-off": "\U000F0F60",
"monitor-star": "\U000F0DDC",
- "monitor-vertical": "\U000F1C33",
"moon-first-quarter": "\U000F0F61",
"moon-full": "\U000F0F62",
"moon-last-quarter": "\U000F0F63",
@@ -4808,9 +4761,7 @@ def on_start(self):
"nas": "\U000F08F3",
"nativescript": "\U000F0880",
"nature": "\U000F038E",
- "nature-outline": "\U000F1C71",
"nature-people": "\U000F038F",
- "nature-people-outline": "\U000F1C72",
"navigation": "\U000F0390",
"navigation-outline": "\U000F1607",
"navigation-variant": "\U000F18F0",
@@ -5001,13 +4952,7 @@ def on_start(self):
"octagon": "\U000F03C3",
"octagon-outline": "\U000F03C4",
"octagram": "\U000F06F9",
- "octagram-edit": "\U000F1C34",
- "octagram-edit-outline": "\U000F1C35",
- "octagram-minus": "\U000F1C36",
- "octagram-minus-outline": "\U000F1C37",
"octagram-outline": "\U000F0775",
- "octagram-plus": "\U000F1C38",
- "octagram-plus-outline": "\U000F1C39",
"octahedron": "\U000F1950",
"octahedron-off": "\U000F1951",
"odnoklassniki": "\U000F03C5",
@@ -5285,8 +5230,6 @@ def on_start(self):
"pine-tree": "\U000F0405",
"pine-tree-box": "\U000F0406",
"pine-tree-fire": "\U000F141A",
- "pine-tree-variant": "\U000F1C73",
- "pine-tree-variant-outline": "\U000F1C74",
"pinterest": "\U000F0407",
"pinwheel": "\U000F0AD5",
"pinwheel-outline": "\U000F0AD6",
@@ -5304,7 +5247,6 @@ def on_start(self):
"plane-train": "\U000F1B00",
"play": "\U000F040A",
"play-box": "\U000F127A",
- "play-box-edit-outline": "\U000F1C3A",
"play-box-lock": "\U000F1A16",
"play-box-lock-open": "\U000F1A17",
"play-box-lock-open-outline": "\U000F1A18",
@@ -5386,8 +5328,6 @@ def on_start(self):
"power-off": "\U000F0902",
"power-on": "\U000F0903",
"power-plug": "\U000F06A5",
- "power-plug-battery": "\U000F1C3B",
- "power-plug-battery-outline": "\U000F1C3C",
"power-plug-off": "\U000F06A6",
"power-plug-off-outline": "\U000F1424",
"power-plug-outline": "\U000F1425",
@@ -5477,7 +5417,6 @@ def on_start(self):
"progress-pencil": "\U000F1787",
"progress-question": "\U000F1522",
"progress-star": "\U000F1788",
- "progress-star-four-points": "\U000F1C3D",
"progress-upload": "\U000F0998",
"progress-wrench": "\U000F0CBD",
"projector": "\U000F042E",
@@ -5552,7 +5491,6 @@ def on_start(self):
"radioactive-circle-outline": "\U000F185E",
"radioactive-off": "\U000F0EC1",
"radiobox-blank": "\U000F043D",
- "radiobox-indeterminate-variant": "\U000F1C5E",
"radiobox-marked": "\U000F043E",
"radiology-box": "\U000F14C5",
"radiology-box-outline": "\U000F14C6",
@@ -5575,22 +5513,10 @@ def on_start(self):
"react": "\U000F0708",
"read": "\U000F0447",
"receipt": "\U000F0824",
- "receipt-clock": "\U000F1C3E",
- "receipt-clock-outline": "\U000F1C3F",
"receipt-outline": "\U000F04F7",
- "receipt-send": "\U000F1C40",
- "receipt-send-outline": "\U000F1C41",
"receipt-text": "\U000F0449",
- "receipt-text-arrow-left": "\U000F1C42",
- "receipt-text-arrow-left-outline": "\U000F1C43",
- "receipt-text-arrow-right": "\U000F1C44",
- "receipt-text-arrow-right-outline": "\U000F1C45",
"receipt-text-check": "\U000F1A63",
"receipt-text-check-outline": "\U000F1A64",
- "receipt-text-clock": "\U000F1C46",
- "receipt-text-clock-outline": "\U000F1C47",
- "receipt-text-edit": "\U000F1C48",
- "receipt-text-edit-outline": "\U000F1C49",
"receipt-text-minus": "\U000F1A65",
"receipt-text-minus-outline": "\U000F1A66",
"receipt-text-outline": "\U000F19DC",
@@ -5598,8 +5524,6 @@ def on_start(self):
"receipt-text-plus-outline": "\U000F1A68",
"receipt-text-remove": "\U000F1A69",
"receipt-text-remove-outline": "\U000F1A6A",
- "receipt-text-send": "\U000F1C4A",
- "receipt-text-send-outline": "\U000F1C4B",
"record": "\U000F044A",
"record-circle": "\U000F0EC2",
"record-circle-outline": "\U000F0EC3",
@@ -5797,7 +5721,6 @@ def on_start(self):
"run-fast": "\U000F046E",
"rv-truck": "\U000F11D4",
"sack": "\U000F0D2E",
- "sack-outline": "\U000F1C4C",
"sack-percent": "\U000F0D2F",
"safe": "\U000F0A6A",
"safe-square": "\U000F127C",
@@ -5916,10 +5839,6 @@ def on_start(self):
"send-lock": "\U000F07ED",
"send-lock-outline": "\U000F1166",
"send-outline": "\U000F1165",
- "send-variant": "\U000F1C4D",
- "send-variant-clock": "\U000F1C7E",
- "send-variant-clock-outline": "\U000F1C7F",
- "send-variant-outline": "\U000F1C4E",
"serial-port": "\U000F065C",
"server": "\U000F048B",
"server-minus": "\U000F048C",
@@ -5949,7 +5868,6 @@ def on_start(self):
"shape-outline": "\U000F0832",
"shape-oval-plus": "\U000F11FA",
"shape-plus": "\U000F0495",
- "shape-plus-outline": "\U000F1C4F",
"shape-polygon-plus": "\U000F065E",
"shape-rectangle-plus": "\U000F065F",
"shape-square-plus": "\U000F0660",
@@ -6268,7 +6186,6 @@ def on_start(self):
"sphere": "\U000F1954",
"sphere-off": "\U000F1955",
"spider": "\U000F11EA",
- "spider-outline": "\U000F1C75",
"spider-thread": "\U000F11EB",
"spider-web": "\U000F0BCA",
"spirit-level": "\U000F14F1",
@@ -6285,7 +6202,6 @@ def on_start(self):
"sprout-outline": "\U000F0E67",
"square": "\U000F0764",
"square-circle": "\U000F1500",
- "square-circle-outline": "\U000F1C50",
"square-edit-outline": "\U000F090C",
"square-medium": "\U000F0A13",
"square-medium-outline": "\U000F0A14",
@@ -6330,12 +6246,7 @@ def on_start(self):
"star-david": "\U000F097A",
"star-face": "\U000F09A5",
"star-four-points": "\U000F0AE2",
- "star-four-points-box": "\U000F1C51",
- "star-four-points-box-outline": "\U000F1C52",
- "star-four-points-circle": "\U000F1C53",
- "star-four-points-circle-outline": "\U000F1C54",
"star-four-points-outline": "\U000F0AE3",
- "star-four-points-small": "\U000F1C55",
"star-half": "\U000F0246",
"star-half-full": "\U000F04D0",
"star-minus": "\U000F1564",
@@ -6566,7 +6477,6 @@ def on_start(self):
"tag-faces": "\U000F04FA",
"tag-heart": "\U000F068B",
"tag-heart-outline": "\U000F0BCF",
- "tag-hidden": "\U000F1C76",
"tag-minus": "\U000F0910",
"tag-minus-outline": "\U000F121F",
"tag-multiple": "\U000F04FB",
@@ -6624,7 +6534,6 @@ def on_start(self):
"temple-hindu-outline": "\U000F1B09",
"tennis": "\U000F0DA0",
"tennis-ball": "\U000F0507",
- "tennis-ball-outline": "\U000F1C5F",
"tent": "\U000F0508",
"terraform": "\U000F1062",
"terrain": "\U000F0509",
@@ -6679,7 +6588,6 @@ def on_start(self):
"thermostat-auto": "\U000F1B17",
"thermostat-box": "\U000F0891",
"thermostat-box-auto": "\U000F1B18",
- "thermostat-cog": "\U000F1C80",
"thought-bubble": "\U000F07F6",
"thought-bubble-outline": "\U000F07F7",
"thumb-down": "\U000F0511",
@@ -6809,7 +6717,6 @@ def on_start(self):
"torch": "\U000F1606",
"tortoise": "\U000F0D3B",
"toslink": "\U000F12B8",
- "touch-text-outline": "\U000F1C60",
"tournament": "\U000F09AE",
"tow-truck": "\U000F083C",
"tower-beach": "\U000F0681",
@@ -6897,7 +6804,6 @@ def on_start(self):
"tray-plus": "\U000F1298",
"tray-remove": "\U000F1299",
"treasure-chest": "\U000F0726",
- "treasure-chest-outline": "\U000F1C77",
"tree": "\U000F0531",
"tree-outline": "\U000F0E69",
"trello": "\U000F0532",
@@ -6905,8 +6811,6 @@ def on_start(self):
"trending-neutral": "\U000F0534",
"trending-up": "\U000F0535",
"triangle": "\U000F0536",
- "triangle-down": "\U000F1C56",
- "triangle-down-outline": "\U000F1C57",
"triangle-outline": "\U000F0537",
"triangle-small-down": "\U000F1A09",
"triangle-small-up": "\U000F1A0A",
@@ -7111,7 +7015,6 @@ def on_start(self):
"view-gallery": "\U000F1888",
"view-gallery-outline": "\U000F1889",
"view-grid": "\U000F0570",
- "view-grid-compact": "\U000F1C61",
"view-grid-outline": "\U000F11D9",
"view-grid-plus": "\U000F0F8D",
"view-grid-plus-outline": "\U000F11DA",
@@ -7174,8 +7077,6 @@ def on_start(self):
"wall-sconce-round-variant": "\U000F091E",
"wall-sconce-round-variant-outline": "\U000F17CD",
"wallet": "\U000F0584",
- "wallet-bifold": "\U000F1C58",
- "wallet-bifold-outline": "\U000F1C59",
"wallet-giftcard": "\U000F0585",
"wallet-membership": "\U000F0586",
"wallet-outline": "\U000F0BDD",
@@ -7248,7 +7149,6 @@ def on_start(self):
"weather-hail": "\U000F0592",
"weather-hazy": "\U000F0F30",
"weather-hurricane": "\U000F0898",
- "weather-hurricane-outline": "\U000F1C78",
"weather-lightning": "\U000F0593",
"weather-lightning-rainy": "\U000F067E",
"weather-night": "\U000F0594",
@@ -7424,23 +7324,27 @@ def on_start(self):
if __name__ == "__main__":
from kivy.lang import Builder
from kivy.properties import StringProperty
- from kivy.uix.screenmanager import Screen
+ from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
- from kivymd.uix.list import OneLineIconListItem
+ from kivymd.uix.list import MDListItem
Builder.load_string(
"""
#:import images_path kivymd.images_path
-
+
- IconLeftWidget:
+ MDListItemLeadingIcon:
icon: root.icon
+ MDListItemSupportingText:
+ text: root.text
+
+ md_bg_color: self.theme_cls.backgroundColor
MDBoxLayout:
orientation: 'vertical'
@@ -7452,6 +7356,7 @@ def on_start(self):
MDIconButton:
icon: 'magnify'
+ pos_hint: {'center_y': .5}
MDTextField:
id: search_field
@@ -7464,7 +7369,7 @@ def on_start(self):
key_size: 'height'
RecycleBoxLayout:
- padding: dp(10)
+ padding: dp(10), dp(10), 0, dp(10)
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
@@ -7473,17 +7378,18 @@ def on_start(self):
"""
)
- class CustomOneLineIconListItem(OneLineIconListItem):
+ class IconItem(MDListItem):
icon = StringProperty()
+ text = StringProperty()
- class PreviousMDIcons(Screen):
+ class PreviousMDIcons(MDScreen):
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",
+ "viewclass": "IconItem",
"icon": name_icon,
"text": name_icon,
"callback": lambda x: x,
diff --git a/kivymd/material_resources.py b/kivymd/material_resources.py
index 2eb66266b..f2aeb3160 100755
--- a/kivymd/material_resources.py
+++ b/kivymd/material_resources.py
@@ -21,44 +21,3 @@
DEVICE_TYPE = "tablet"
else:
DEVICE_TYPE = "mobile"
-
-if DEVICE_TYPE == "mobile":
- MAX_NAV_DRAWER_WIDTH = dp(300)
- HORIZ_MARGINS = dp(16)
- STANDARD_INCREMENT = dp(56)
- PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT
- LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8)
-else:
- MAX_NAV_DRAWER_WIDTH = dp(400)
- HORIZ_MARGINS = dp(24)
- STANDARD_INCREMENT = dp(64)
- 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)
diff --git a/kivymd/tests/memory/test_textfield.py b/kivymd/tests/memory/test_textfield.py
index 2f09677b4..ae5370fea 100644
--- a/kivymd/tests/memory/test_textfield.py
+++ b/kivymd/tests/memory/test_textfield.py
@@ -2,12 +2,23 @@
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
-from kivymd.uix.textfield import MDTextField
+from kivymd.uix.textfield import (
+ MDTextField,
+ MDTextFieldLeadingIcon,
+ MDTextFieldHintText,
+ MDTextFieldHelperText,
+ MDTextFieldTrailingIcon,
+ MDTextFieldMaxLengthText,
+)
len_callbacks = 0
class MyScreen(MDScreen):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.md_bg_color = self.theme_cls.backgroundColor
+
def remove_widget(self, *args, **kwargs) -> None:
global len_callbacks
@@ -31,7 +42,26 @@ def add_items(self, *args):
self.counter += 1
self.root.clear_widgets()
- self.root.add_widget(MDTextField(text=f"Count {self.counter}"))
+ self.root.add_widget(
+ MDTextField(
+ MDTextFieldLeadingIcon(
+ icon="account",
+ ),
+ MDTextFieldHintText(
+ text="Hint text",
+ ),
+ MDTextFieldHelperText(
+ text="Helper text",
+ mode="persistent",
+ ),
+ MDTextFieldTrailingIcon(
+ icon="information",
+ ),
+ MDTextFieldMaxLengthText(
+ max_text_length=10,
+ ),
+ )
+ )
if self.counter > 10:
Clock.unschedule(self.add_items)
diff --git a/kivymd/theming.py b/kivymd/theming.py
index 0c89bcbc9..f68864482 100755
--- a/kivymd/theming.py
+++ b/kivymd/theming.py
@@ -4,7 +4,7 @@
.. seealso::
- `Material Design spec, Material theming `_
+ `Material Design spec, Dynamic color `_
Material App
------------
@@ -21,231 +21,48 @@
The main application class inherited from the :class:`~kivymd.app.MDApp` class
has the :attr:`~kivymd.app.MDApp.theme_cls` attribute, with which you control
the material properties of your application.
-
-Changing the theme colors
--------------------------
-
-The standard theme_cls is designed to provide the standard themes and colors as
-defined by Material Design.
-
-We do not recommend that you change this.
-
-However, if you do need to change the standard colors, for instance to meet branding
-guidelines, you can do this by overloading the `color_definitions.py` object.
-
-Create a custom color defintion object. This should have the same format as
-the `colors `_
-object in `color_definitions.py` and contain definitions for at least the
-primary color, the accent color and the Light and Dark backgrounds.
-
-.. note:: Your custom colors *must* use the names of the
- `existing colors as defined in the palette `_
- e.g. You can have `Blue` but you cannot have `NavyBlue`.
-
-Add the custom theme to the :class:`~kivymd.app.MDApp` as shown in the
-following snippet.
-
-.. tabs::
-
- .. tab:: Imperative python style with KV
-
- .. code-block:: python
-
- from kivy.lang import Builder
- from kivy.properties import ObjectProperty
-
- from kivymd.app import MDApp
- from kivymd.uix.floatlayout import MDFloatLayout
- from kivymd.uix.tab import MDTabsBase
- from kivymd.icon_definitions import md_icons
-
- colors = {
- "Teal": {
- "200": "#212121",
- "500": "#212121",
- "700": "#212121",
- },
- "Red": {
- "200": "#C25554",
- "500": "#C25554",
- "700": "#C25554",
- },
- "Light": {
- "StatusBar": "E0E0E0",
- "AppBar": "#202020",
- "Background": "#2E3032",
- "CardsDialogs": "#FFFFFF",
- "FlatButtonDown": "#CCCCCC",
- },
- }
-
-
- KV = '''
- MDBoxLayout:
- orientation: "vertical"
-
- MDTopAppBar:
- title: "Custom theme"
-
- MDTabs:
- id: tabs
-
-
-
-
- MDIconButton:
- id: icon
- icon: root.icon
- icon_size: "48sp"
- theme_icon_color: "Custom"
- icon_color: "white"
- pos_hint: {"center_x": .5, "center_y": .5}
- '''
-
-
- class Tab(MDFloatLayout, MDTabsBase):
- '''Class implementing content for a tab.'''
-
- icon = ObjectProperty()
-
-
- class Example(MDApp):
- icons = list(md_icons.keys())[15:30]
-
- def build(self):
- self.theme_cls.colors = colors
- self.theme_cls.primary_palette = "Teal"
- self.theme_cls.accent_palette = "Red"
- return Builder.load_string(KV)
-
- def on_start(self):
- for name_tab in self.icons:
- tab = Tab(title="This is " + name_tab, icon=name_tab)
- self.root.ids.tabs.add_widget(tab)
-
-
- Example().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivy.properties import ObjectProperty
-
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.button import MDIconButton
- from kivymd.uix.floatlayout import MDFloatLayout
- from kivymd.uix.tab import MDTabsBase, MDTabs
- from kivymd.icon_definitions import md_icons
- from kivymd.uix.toolbar import MDTopAppBar
-
- colors = {
- "Teal": {
- "200": "#212121",
- "500": "#212121",
- "700": "#212121",
- },
- "Red": {
- "200": "#C25554",
- "500": "#C25554",
- "700": "#C25554",
- },
- "Light": {
- "StatusBar": "E0E0E0",
- "AppBar": "#202020",
- "Background": "#2E3032",
- "CardsDialogs": "#FFFFFF",
- "FlatButtonDown": "#CCCCCC",
- },
- }
-
-
- class Tab(MDFloatLayout, MDTabsBase):
- '''Class implementing content for a tab.'''
-
- icon = ObjectProperty()
-
-
- class Example(MDApp):
- icons = list(md_icons.keys())[15:30]
-
- def build(self):
- self.theme_cls.colors = colors
- self.theme_cls.primary_palette = "Teal"
- self.theme_cls.accent_palette = "Red"
-
- return (
- MDBoxLayout(
- MDTopAppBar(title="Custom theme"),
- MDTabs(id="tabs"),
- orientation="vertical",
- )
- )
-
- def on_start(self):
- for name_tab in self.icons:
- self.root.ids.tabs.add_widget(
- Tab(
- MDIconButton(
- icon=name_tab,
- icon_size="48sp",
- theme_icon_color="Custom",
- icon_color="white",
- pos_hint={"center_x": .5, "center_y": .5},
- ),
- title="This is " + name_tab,
- icon=name_tab,
- )
- )
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-color.png
- :align: center
-
-This will change the theme colors to your custom definition. In all other
-respects, the theming stays as documented.
-
-.. warning:: Please note that the key ``'Red'`` is a required key for the
- dictionary :attr:`kivymd.color_definition.colors`.
"""
-from kivy.animation import Animation
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.event import EventDispatcher
-from kivy.metrics import dp
from kivy.properties import (
AliasProperty,
BooleanProperty,
- ColorProperty,
DictProperty,
NumericProperty,
ObjectProperty,
OptionProperty,
StringProperty,
+ ListProperty,
)
-from kivy.utils import get_color_from_hex
+from kivy.utils import get_color_from_hex, rgba, hex_colormap
-from kivymd.color_definitions import colors, hue, palette
+from kivymd.dynamic_color import DynamicColor
from kivymd.font_definitions import theme_font_styles
-from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE
+from kivymd.material_resources import DEVICE_IOS
+from kivymd.utils.get_wallpaper import get_wallpaper
+
+from materialyoucolor.utils.string_utils import argbFromRgb
+from materialyoucolor.utils.theme_utils import (
+ getDefaultTheme,
+ getDominantColors,
+ themeFromSourceColor,
+)
-class ThemeManager(EventDispatcher):
- primary_palette = OptionProperty("Blue", options=palette)
+class ThemeManager(EventDispatcher, DynamicColor):
+ primary_palette = OptionProperty(
+ None,
+ options=[name_color.capitalize() for name_color in hex_colormap.keys()],
+ )
"""
The name of the color scheme that the application will use.
All major `material` components will have the color
of the specified color theme.
- Available options are: `'Red'`, `'Pink'`, `'Purple'`, `'DeepPurple'`,
- `'Indigo'`, `'Blue'`, `'LightBlue'`, `'Cyan'`, `'Teal'`, `'Green'`,
- `'LightGreen'`, `'Lime'`, `'Yellow'`, `'Amber'`, `'Orange'`, `'DeepOrange'`,
- `'Brown'`, `'Gray'`, `'BlueGray'`.
+ See :attr:`kivy.utils.hex_colormap` keys for available values.
To change the color scheme of an application:
@@ -308,325 +125,38 @@ def build(self):
:align: center
:attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'Blue'`.
- """
-
- primary_hue = OptionProperty("500", options=hue)
- """
- The color hue of the application.
-
- Available options are: `'50'`, `'100'`, `'200'`, `'300'`, `'400'`, `'500'`,
- `'600'`, `'700'`, `'800'`, `'900'`, `'A100'`, `'A200'`, `'A400'`, `'A700'`.
-
- To change the hue color scheme of an application:
-
- .. tabs::
-
- .. tab:: Imperative python style with KV
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.button import MDRectangleFlatButton
-
-
- class MainApp(MDApp):
- def build(self):
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.primary_hue = "200" # "500"
- screen = MDScreen()
- screen.add_widget(
- MDRectangleFlatButton(
- text="Hello, World",
- pos_hint={"center_x": 0.5, "center_y": 0.5},
- )
- )
- return screen
-
-
- MainApp().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.button import MDRectangleFlatButton
- from kivymd.uix.screen import MDScreen
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_hue = "200" # "500"
-
- return (
- MDScreen(
- MDRectangleFlatButton(
- text="Hello, World",
- pos_hint={"center_x": 0.5, "center_y": 0.5},
- )
- )
- )
-
-
- Example().run()
-
- With a value of ``self.theme_cls.primary_hue = "200"`` and ``self.theme_cls.primary_hue = "500"``:
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary_hue.png
- :align: center
-
- :attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'500'`.
- """
-
- primary_light_hue = OptionProperty("200", options=hue)
- """
- Hue value for :attr:`primary_light`.
-
- :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'200'`.
- """
-
- primary_dark_hue = OptionProperty("700", options=hue)
- """
- Hue value for :attr:`primary_dark`.
-
- :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'700'`.
- """
-
- def _get_primary_color(self) -> list:
- return get_color_from_hex(
- self.colors[self.primary_palette][self.primary_hue]
- )
-
- primary_color = AliasProperty(
- _get_primary_color, bind=("primary_palette", "primary_hue")
- )
- """
- The color of the current application theme.
-
- :attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value of the current application theme, property is readonly.
- """
-
- def _get_primary_light(self) -> list:
- return get_color_from_hex(
- self.colors[self.primary_palette][self.primary_light_hue]
- )
-
- primary_light = AliasProperty(
- _get_primary_light, bind=("primary_palette", "primary_light_hue")
- )
- """
- Colors of the current application color theme (in lighter color).
-
- .. tabs::
-
- .. tab:: Declarative style with KV
-
- .. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
-
-
- KV = '''
- MDScreen:
-
- MDRaisedButton:
- text: "primary_light"
- pos_hint: {"center_x": 0.5, "center_y": 0.7}
- md_bg_color: app.theme_cls.primary_light
-
- MDRaisedButton:
- text: "primary_color"
- pos_hint: {"center_x": 0.5, "center_y": 0.5}
-
- MDRaisedButton:
- text: "primary_dark"
- pos_hint: {"center_x": 0.5, "center_y": 0.3}
- md_bg_color: app.theme_cls.primary_dark
- '''
-
-
- class MainApp(MDApp):
- def build(self):
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
-
-
- MainApp().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.button import MDRaisedButton
- from kivymd.uix.screen import MDScreen
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.theme_style = "Dark"
-
- return (
- MDScreen(
- MDRaisedButton(
- text="Primary light",
- pos_hint={"center_x": 0.5, "center_y": 0.7},
- md_bg_color=self.theme_cls.primary_light,
- ),
- MDRaisedButton(
- text="Primary color",
- pos_hint={"center_x": 0.5, "center_y": 0.5},
- ),
- MDRaisedButton(
- text="Primary dark",
- pos_hint={"center_x": 0.5, "center_y": 0.3},
- md_bg_color=self.theme_cls.primary_dark,
- ),
- )
- )
-
-
- Example().run()
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png
- :align: center
-
- :attr:`primary_light` is an :class:`~kivy.properties.AliasProperty` that
- returns the value of the current application theme (in lighter color),
- property is readonly.
- """
-
- def _get_primary_dark(self) -> list:
- return get_color_from_hex(
- self.colors[self.primary_palette][self.primary_dark_hue]
- )
-
- primary_dark = AliasProperty(
- _get_primary_dark, bind=("primary_palette", "primary_dark_hue")
- )
- """
- Colors of the current application color theme (in darker color).
-
- :attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that
- returns the value of the current application theme (in darker color),
- property is readonly.
- """
-
- accent_palette = OptionProperty("Amber", options=palette)
- """
- The application color palette used for items such as the tab indicator
- in the :class:`~kivymd.uix.tab.MDTabsBar` class and so on.
- See :attr:`kivymd.uix.tab.MDTabsBar.indicator_color` attribute.
-
- :attr:`accent_palette` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'Amber'`.
- """
-
- accent_hue = OptionProperty("500", options=hue)
- """
- Similar to :attr:`primary_hue`, but returns a value for :attr:`accent_palette`.
-
- :attr:`accent_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'500'`.
- """
-
- accent_light_hue = OptionProperty("200", options=hue)
- """
- Hue value for :attr:`accent_light`.
-
- :attr:`accent_light_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'200'`.
- """
-
- accent_dark_hue = OptionProperty("700", options=hue)
- """
- Hue value for :attr:`accent_dark`.
-
- :attr:`accent_dark_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'700'`.
- """
-
- def _get_accent_color(self) -> list:
- return get_color_from_hex(
- self.colors[self.accent_palette][self.accent_hue]
- )
-
- accent_color = AliasProperty(
- _get_accent_color, bind=["accent_palette", "accent_hue"]
- )
+ and defaults to `None`.
"""
- Similar to :attr:`primary_color`, but returns a value for :attr:`accent_color`.
-
- :attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`accent_color`, property is
- readonly.
- """
-
- def _get_accent_light(self) -> list:
- return get_color_from_hex(
- self.colors[self.accent_palette][self.accent_light_hue]
- )
- accent_light = AliasProperty(
- _get_accent_light, bind=["accent_palette", "accent_light_hue"]
- )
- """
- Similar to :attr:`primary_light`, but returns a value for :attr:`accent_light`.
+ current_color_theme = DictProperty()
- :attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`accent_light`, property is
- readonly.
+ dynamic_color = BooleanProperty(False)
"""
+ Enables or disables dynamic color.
- def _get_accent_dark(self) -> list:
- return get_color_from_hex(
- self.colors[self.accent_palette][self.accent_dark_hue]
- )
+ .. versionchanged:: 2.0.0
- accent_dark = AliasProperty(
- _get_accent_dark, bind=["accent_palette", "accent_dark_hue"]
- )
- """
- Similar to :attr:`primary_dark`, but returns a value for :attr:`accent_dark`.
+ .. seealso::
+
+ `Material Design spec, Dynamic color `_
- :attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`accent_dark`, property is
- readonly.
+ :attr:`dynamic_color` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
"""
- material_style = OptionProperty("M3", options=["M2", "M3"])
+ path_to_wallpaper = StringProperty()
"""
- Material design style.
- Available options are: 'M2', 'M3'.
-
- .. versionadded:: 1.0.0
+ The path to the image to set the color scheme. You can use this option
+ if you want to use dynamic color on platforms other than the Android
+ platform.
- .. versionchanged:: 1.2.0
- By default now `'M3'`.
+ .. versionadded:: 2.0.0
- .. seealso::
-
- `Material Design 2 `_ and
- `Material Design 3 `_
-
-
- :attr:`material_style` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'M3'`.
+ :attr:`path_to_wallpaper` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `''`.
"""
- theme_style_switch_animation = BooleanProperty(False)
+ theme_style_switch_animation = BooleanProperty(True)
"""
Animate app colors when switching app color scheme ('Dark/light').
@@ -746,7 +276,7 @@ def switch_theme_style(self, *args):
:align: center
:attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
+ and defaults to `True`.
"""
theme_style_switch_animation_duration = NumericProperty(0.2)
@@ -840,816 +370,522 @@ def _get_theme_style(self, opposite: bool) -> str:
else:
return self.theme_style
- def _get_bg_darkest(self, opposite: bool = False) -> list:
+ def _get_disabled_hint_text_color(self, opposite: bool = False) -> list:
theme_style = self._get_theme_style(opposite)
if theme_style == "Light":
- return get_color_from_hex(self.colors["Light"]["StatusBar"])
+ color = get_color_from_hex("000000")
+ color[3] = 0.38
elif theme_style == "Dark":
- return get_color_from_hex(self.colors["Dark"]["StatusBar"])
+ color = get_color_from_hex("FFFFFF")
+ color[3] = 0.50
+ return color
- bg_darkest = AliasProperty(_get_bg_darkest, bind=["theme_style"])
+ disabled_hint_text_color = AliasProperty(
+ _get_disabled_hint_text_color, bind=["theme_style"]
+ )
"""
- Similar to :attr:`bg_dark`,
- but the color values are a tone lower (darker) than :attr:`bg_dark`.
-
- .. tabs::
+ Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`.
- .. tab:: Declarative style with KV
+ :attr:`disabled_hint_text_color`
+ is an :class:`~kivy.properties.AliasProperty` that returns the value
+ in ``rgba`` format for :attr:`disabled_hint_text_color`,
+ property is readonly.
+ """
- .. code-block:: python
+ def _determine_device_orientation(self, _, window_size) -> None:
+ if window_size[0] > window_size[1]:
+ self.device_orientation = "landscape"
+ elif window_size[1] >= window_size[0]:
+ self.device_orientation = "portrait"
- from kivy.lang import Builder
+ device_orientation = StringProperty()
+ """
+ Device orientation.
- from kivymd.app import MDApp
+ :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `''`.
+ """
- KV = '''
- MDBoxLayout:
+ # Font name, size (sp), always caps, letter spacing (sp).
+ font_styles = DictProperty(theme_font_styles)
+ """
+ Data of default font styles.
- MDWidget:
- md_bg_color: app.theme_cls.bg_light
+ Add custom font
+ ---------------
- MDBoxLayout:
- md_bg_color: app.theme_cls.bg_normal
+ .. tabs::
- MDBoxLayout:
- md_bg_color: app.theme_cls.bg_dark
+ .. tab:: Declarative style with KV
+
+ .. code-block:: python
+
+ from kivy.core.text import LabelBase
+ from kivy.lang import Builder
+ from kivy.metrics import sp
- MDBoxLayout:
- md_bg_color: app.theme_cls.bg_darkest
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDLabel:
+ text: "MDLabel"
+ halign: "center"
+ font_style: "nasalization"
'''
- class MainApp(MDApp):
+ class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark" # "Light"
+ self.theme_cls.theme_style = "Dark"
+
+ LabelBase.register(
+ name="nasalization",
+ fn_regular="nasalization.ttf",
+ )
+
+ self.theme_cls.font_styles["nasalization"] = {
+ "large": {
+ "line-height": 1.64,
+ "font-name": "nasalization",
+ "font-size": sp(57),
+ },
+ "medium": {
+ "line-height": 1.52,
+ "font-name": "nasalization",
+ "font-size": sp(45),
+ },
+ "small": {
+ "line-height": 1.44,
+ "font-name": "nasalization",
+ "font-size": sp(36),
+ },
+ }
+
return Builder.load_string(KV)
- MainApp().run()
+ Example().run()
.. tab:: Declarative python style
.. code-block:: python
+ from kivy.core.text import LabelBase
+ from kivy.metrics import sp
+
+ from kivymd.uix.label import MDLabel
+ from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.widget import MDWidget
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark" # "Light"
+ self.theme_cls.theme_style = "Dark"
+
+ LabelBase.register(
+ name="nasalization",
+ fn_regular="/Users/urijivanov/Projects/Dev/MyGithub/Articles/StarTest/data/font/nasalization-rg.ttf",
+ )
+
+ self.theme_cls.font_styles["nasalization"] = {
+ "large": {
+ "line-height": 1.64,
+ "font-name": "nasalization",
+ "font-size": sp(57),
+ },
+ "medium": {
+ "line-height": 1.52,
+ "font-name": "nasalization",
+ "font-size": sp(45),
+ },
+ "small": {
+ "line-height": 1.44,
+ "font-name": "nasalization",
+ "font-size": sp(36),
+ },
+ }
return (
- MDBoxLayout(
- MDWidget(
- md_bg_color=self.theme_cls.bg_light,
- ),
- MDWidget(
- md_bg_color=self.theme_cls.bg_normal,
- ),
- MDWidget(
- md_bg_color=self.theme_cls.bg_dark,
- ),
- MDWidget(
- md_bg_color=self.theme_cls.bg_darkest,
- ),
+ MDScreen(
+ MDLabel(
+ text="JetBrainsMono",
+ halign="center",
+ font_style="nasalization",
+ )
)
)
Example().run()
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-font-styles.png
:align: center
- :attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`bg_darkest`,
- property is readonly.
+ :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
"""
- def _get_op_bg_darkest(self) -> list:
- return self._get_bg_darkest(True)
+ schemes_name_colors = ListProperty()
- opposite_bg_darkest = AliasProperty(
- _get_op_bg_darkest, bind=["theme_style"]
- )
- """
- The opposite value of color in the :attr:`bg_darkest`.
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self._determine_device_orientation(None, Window.size)
+ Window.bind(size=self._determine_device_orientation)
+ self.bind(
+ theme_style=lambda *x: Clock.schedule_once(
+ self.update_theme_colors, 0.1
+ ),
+ primary_palette=lambda *x: Clock.schedule_once(
+ self.set_colors, 0.1
+ ),
+ )
+ # Clock.schedule_once(self.sync_theme_styles)
+ Clock.schedule_once(self.set_colors)
- :attr:`opposite_bg_darkest` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`opposite_bg_darkest`,
- property is readonly.
- """
+ def set_colors(self, *args) -> None:
+ """Calls methods for setting a new color scheme."""
- def _get_bg_dark(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- return get_color_from_hex(self.colors["Light"]["AppBar"])
- elif theme_style == "Dark":
- return get_color_from_hex(self.colors["Dark"]["AppBar"])
+ if not self.dynamic_color:
+ if not self.primary_palette:
+ self._set_default_color()
+ else:
+ self._set_palette_color()
+ else:
+ path_to_wallpaper = get_wallpaper(
+ App.get_running_app().user_data_dir, self.path_to_wallpaper
+ )
+ if path_to_wallpaper:
+ self._set_dynamic_color(path_to_wallpaper)
+ else:
+ self._set_palette_color()
- bg_dark = AliasProperty(_get_bg_dark, bind=["theme_style"])
- """
- Similar to :attr:`bg_normal`,
- but the color values are one tone lower (darker) than :attr:`bg_normal`.
+ def on_path_to_wallpaper(self, instance, value) -> None:
+ """Fired when the `path_to_wallpaper` value changes."""
- :attr:`bg_dark` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`bg_dark`,
- property is readonly.
- """
+ if self.dynamic_color:
+ self.set_colors()
- def _get_op_bg_dark(self) -> list:
- return self._get_bg_dark(True)
+ def update_theme_colors(self, *args) -> None:
+ """Fired when the `theme_style` value changes."""
- opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=["theme_style"])
- """
- The opposite value of color in the :attr:`bg_dark`.
+ color_theme = self.current_color_theme
+ style_theme = self.theme_style
- :attr:`opposite_bg_dark` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`opposite_bg_dark`,
- property is readonly.
- """
+ if "schemes" in color_theme:
+ self.schemes_name_colors = list(
+ color_theme["schemes"][style_theme.lower()].props.keys()
+ )
+ for color_key in self.schemes_name_colors:
+ color = color_theme["schemes"][style_theme.lower()].props[
+ color_key
+ ]
+ exec(f"self.{color_key}Color = {rgba(color)}")
+ else:
+ self.schemes_name_colors = list(
+ color_theme[style_theme.lower()].keys()
+ )
+ for color_key in self.schemes_name_colors:
+ color = color_theme[style_theme.lower()][color_key]
+ exec(f"self.{color_key}Color = {rgba(color)}")
- def _get_bg_normal(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- return get_color_from_hex(self.colors["Light"]["Background"])
- elif theme_style == "Dark":
- return get_color_from_hex(self.colors["Dark"]["Background"])
+ def switch_theme(self) -> None:
+ """Switches the theme from light to dark."""
- bg_normal = AliasProperty(_get_bg_normal, bind=["theme_style"])
- """
- Similar to :attr:`bg_light`,
- but the color values are one tone lower (darker) than :attr:`bg_light`.
+ self.theme_style = "Dark" if self.theme_style == "Light" else "Light"
- :attr:`bg_normal` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`bg_normal`,
- property is readonly.
- """
+ def sync_theme_styles(self, *args) -> None:
+ # Syncs the values from self.font_styles to theme_font_styles
+ # this will ensure continuity when someone registers a new font_style.
+ for num, style in enumerate(theme_font_styles):
+ if style not in self.font_styles:
+ theme_font_styles.pop(num)
+ for style in self.font_styles.keys():
+ theme_font_styles.append(style)
- def _get_op_bg_normal(self) -> list:
- return self._get_bg_normal(True)
+ def _set_dynamic_color(self, path_to_wallpaper: str) -> None:
+ print("_set_dynamic_color")
+ dynamic_theme = themeFromSourceColor(
+ getDominantColors(path_to_wallpaper, quality=10, default_chunk=128)[
+ 0
+ ]
+ )
+ self.current_color_theme = dynamic_theme
+ self.schemes_name_colors = list(
+ dynamic_theme["schemes"][self.theme_style.lower()].props.keys()
+ )
- opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=["theme_style"])
- """
- The opposite value of color in the :attr:`bg_normal`.
+ for color_key in self.schemes_name_colors:
+ color = dynamic_theme["schemes"][self.theme_style.lower()].props[
+ color_key
+ ]
+ exec(f"self.{color_key}Color = {rgba(color)}")
+ self.disabledTextColor = self._get_disabled_hint_text_color()
+
+ def _set_default_color(self) -> None:
+ default_theme = getDefaultTheme()
+ self.current_color_theme = default_theme
+ self.schemes_name_colors = list(
+ default_theme[self.theme_style.lower()].keys()
+ )
- :attr:`opposite_bg_normal` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`opposite_bg_normal`,
- property is readonly.
- """
+ for color_key in self.schemes_name_colors:
+ color = default_theme[self.theme_style.lower()][color_key]
+ exec(f"self.{color_key}Color = {rgba(color)}")
+ self.disabledTextColor = self._get_disabled_hint_text_color()
+
+ def _set_palette_color(self) -> None:
+ if not self.primary_palette:
+ self.primary_palette = "Blue"
+ color_theme = themeFromSourceColor(
+ [
+ argbFromRgb(
+ *[
+ int(c * 255)
+ for c in get_color_from_hex(
+ hex_colormap[self.primary_palette.lower()]
+ )
+ ][:3]
+ )
+ ][0]
+ )
+ self.current_color_theme = color_theme
+ self.schemes_name_colors = list(
+ color_theme["schemes"][self.theme_style.lower()].props.keys()
+ )
- def _get_bg_light(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- return get_color_from_hex(self.colors["Light"]["CardsDialogs"])
- elif theme_style == "Dark":
- return get_color_from_hex(self.colors["Dark"]["CardsDialogs"])
+ for color_key in self.schemes_name_colors:
+ color = color_theme["schemes"][self.theme_style.lower()].props[
+ color_key
+ ]
+ exec(f"self.{color_key}Color = {rgba(color)}")
+ self.disabledTextColor = self._get_disabled_hint_text_color()
- bg_light = AliasProperty(_get_bg_light, bind=["theme_style"])
- """"
- Depending on the style of the theme (`'Dark'` or `'Light`')
- that the application uses, :attr:`bg_light` contains the color value
- in ``rgba`` format for the widgets background.
- :attr:`bg_light` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`bg_light`,
- property is readonly.
+class ThemableBehavior(EventDispatcher):
+ theme_cls = ObjectProperty()
"""
+ Instance of :class:`~ThemeManager` class.
- def _get_op_bg_light(self) -> list:
- return self._get_bg_light(True)
-
- opposite_bg_light = AliasProperty(_get_op_bg_light, bind=["theme_style"])
+ :attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
"""
- The opposite value of color in the :attr:`bg_light`.
- :attr:`opposite_bg_light` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`opposite_bg_light`,
- property is readonly.
+ device_ios = BooleanProperty(DEVICE_IOS)
"""
+ ``True`` if device is ``iOS``.
- def _get_divider_color(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- color = get_color_from_hex("000000")
- elif theme_style == "Dark":
- color = get_color_from_hex("FFFFFF")
- color[3] = 0.12
- return color
-
- divider_color = AliasProperty(_get_divider_color, bind=["theme_style"])
+ :attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`.
"""
- Color for dividing lines such as :class:`~kivymd.uix.card.MDSeparator`.
- :attr:`divider_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`divider_color`,
- property is readonly.
+ theme_line_color = OptionProperty("Primary", options=["Primary", "Custom"])
"""
+ Line color scheme name.
- def _get_op_divider_color(self) -> list:
- return self._get_divider_color(True)
+ .. versionadded:: 2.0.0
- opposite_divider_color = AliasProperty(
- _get_op_divider_color, bind=["theme_style"]
- )
- """
- The opposite value of color in the :attr:`divider_color`.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`opposite_divider_color` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`opposite_divider_color`,
- property is readonly.
+ :attr:`theme_line_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def _get_disabled_primary_color(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- lum = sum(self.primary_color[0:3]) / 3.0
- if theme_style == "Light":
- a = 0.38
- elif theme_style == "Dark":
- a = 0.50
- return [lum, lum, lum, a]
-
- disabled_primary_color = AliasProperty(
- _get_disabled_primary_color, bind=["theme_style"]
- )
+ theme_bg_color = OptionProperty("Primary", options=["Primary", "Custom"])
"""
- The greyscale disabled version of the current application theme color
- in ``rgba`` format.
+ Background color scheme name.
- .. versionadded:: 1.0.0
+ .. versionadded:: 2.0.0
- :attr:`disabled_primary_color`
- is an :class:`~kivy.properties.AliasProperty` that returns the value
- in ``rgba`` format for :attr:`disabled_primary_color`,
- property is readonly.
- """
-
- def _get_op_disabled_primary_color(self) -> list:
- return self._get_disabled_primary_color(True)
+ Available options are: `'Primary'`, `'Custom'`.
- opposite_disabled_primary_color = AliasProperty(
- _get_op_disabled_primary_color, bind=["theme_style"]
- )
+ :attr:`theme_bg_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- The opposite value of color in the :attr:`disabled_primary_color`.
-
- .. versionadded:: 1.0.0
- :attr:`opposite_disabled_primary_color` is an
- :class:`~kivy.properties.AliasProperty` that returns the value
- in ``rgba`` format for :attr:`opposite_disabled_primary_color`,
- property is readonly.
+ theme_shadow_color = OptionProperty(
+ "Primary", options=["Primary", "Custom"]
+ )
"""
+ Elevation color scheme name.
- def _get_text_color(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- color = get_color_from_hex("000000")
- color[3] = 0.87
- elif theme_style == "Dark":
- color = get_color_from_hex("FFFFFF")
- return color
+ .. versionadded:: 2.0.0
- text_color = AliasProperty(_get_text_color, bind=["theme_style"])
- """
- Color of the text used in the :class:`~kivymd.uix.label.MDLabel`.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`text_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`text_color`,
- property is readonly.
+ :attr:`theme_shadow_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def _get_op_text_color(self) -> list:
- return self._get_text_color(True)
-
- opposite_text_color = AliasProperty(
- _get_op_text_color, bind=["theme_style"]
+ theme_shadow_offset = OptionProperty(
+ "Primary", options=["Primary", "Custom"]
)
"""
- The opposite value of color in the :attr:`text_color`.
+ Elevation offset scheme name.
- :attr:`opposite_text_color` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`opposite_text_color`,
- property is readonly.
- """
+ .. versionadded:: 2.0.0
- def _get_secondary_text_color(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- color = get_color_from_hex("000000")
- color[3] = 0.54
- elif theme_style == "Dark":
- color = get_color_from_hex("FFFFFF")
- color[3] = 0.70
- return color
-
- secondary_text_color = AliasProperty(
- _get_secondary_text_color, bind=["theme_style"]
- )
- """
- The color for the secondary text that is used in classes
- from the module :class:`~kivymd/uix/list.TwoLineListItem`.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`secondary_text_color` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`secondary_text_color`,
- property is readonly.
+ :attr:`theme_shadow_offset` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def _get_op_secondary_text_color(self) -> list:
- return self._get_secondary_text_color(True)
-
- opposite_secondary_text_color = AliasProperty(
- _get_op_secondary_text_color, bind=["theme_style"]
+ theme_elevation_level = OptionProperty(
+ "Primary", options=["Primary", "Custom"]
)
"""
- The opposite value of color in the :attr:`secondary_text_color`.
+ Elevation level scheme name.
- :attr:`opposite_secondary_text_color`
- is an :class:`~kivy.properties.AliasProperty` that returns the value
- in ``rgba`` format for :attr:`opposite_secondary_text_color`,
- property is readonly.
- """
+ .. versionadded:: 2.0.0
- def _get_icon_color(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- color = get_color_from_hex("000000")
- color[3] = 0.54
- elif theme_style == "Dark":
- color = get_color_from_hex("FFFFFF")
- return color
+ Available options are: `'Primary'`, `'Custom'`.
- icon_color = AliasProperty(_get_icon_color, bind=["theme_style"])
+ :attr:`theme_elevation_level` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- Color of the icon used in the :class:`~kivymd.uix.button.MDIconButton`.
- :attr:`icon_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`icon_color`,
- property is readonly.
+ theme_font_size = OptionProperty("Primary", options=["Primary", "Custom"])
"""
+ Font size scheme name.
- def _get_op_icon_color(self) -> list:
- return self._get_icon_color(True)
+ .. versionadded:: 2.0.0
- opposite_icon_color = AliasProperty(
- _get_op_icon_color, bind=["theme_style"]
- )
- """
- The opposite value of color in the :attr:`icon_color`.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`opposite_icon_color` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`opposite_icon_color`,
- property is readonly.
+ :attr:`theme_font_size` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def _get_disabled_hint_text_color(self, opposite: bool = False) -> list:
- theme_style = self._get_theme_style(opposite)
- if theme_style == "Light":
- color = get_color_from_hex("000000")
- color[3] = 0.38
- elif theme_style == "Dark":
- color = get_color_from_hex("FFFFFF")
- color[3] = 0.50
- return color
-
- disabled_hint_text_color = AliasProperty(
- _get_disabled_hint_text_color, bind=["theme_style"]
- )
+ theme_width = OptionProperty("Primary", options=["Primary", "Custom"])
"""
- Color of the disabled text used in the :class:`~kivymd.uix.textfield.MDTextField`.
+ Widget width scheme name.
- :attr:`disabled_hint_text_color`
- is an :class:`~kivy.properties.AliasProperty` that returns the value
- in ``rgba`` format for :attr:`disabled_hint_text_color`,
- property is readonly.
- """
+ .. versionadded:: 2.0.0
- def _get_op_disabled_hint_text_color(self) -> list:
- return self._get_disabled_hint_text_color(True)
+ Available options are: `'Primary'`, `'Custom'`.
- opposite_disabled_hint_text_color = AliasProperty(
- _get_op_disabled_hint_text_color, bind=["theme_style"]
- )
+ :attr:`theme_width` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- The opposite value of color in the :attr:`disabled_hint_text_color`.
- :attr:`opposite_disabled_hint_text_color`
- is an :class:`~kivy.properties.AliasProperty` that returns the value
- in ``rgba`` format for :attr:`opposite_disabled_hint_text_color`,
- property is readonly.
+ theme_height = OptionProperty("Primary", options=["Primary", "Custom"])
"""
+ Widget width scheme name.
- # Hardcoded because muh standard
- def _get_error_color(self) -> list:
- return get_color_from_hex(self.colors["Red"]["A700"])
+ .. versionadded:: 2.0.0
- error_color = AliasProperty(_get_error_color, bind=["theme_style"])
- """
- Color of the error text used
- in the :class:`~kivymd.uix.textfield.MDTextField`.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`error_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`error_color`,
- property is readonly.
+ :attr:`theme_height` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def _get_ripple_color(self) -> list:
- return self._ripple_color
+ theme_line_height = OptionProperty("Primary", options=["Primary", "Custom"])
+ """
+ Line height scheme name.
- def _set_ripple_color(self, value) -> None:
- self._ripple_color = value
+ .. versionadded:: 2.0.0
- _ripple_color = ColorProperty(colors["Gray"]["400"])
- """Private value."""
+ Available options are: `'Primary'`, `'Custom'`.
- ripple_color = AliasProperty(
- _get_ripple_color, _set_ripple_color, bind=["_ripple_color"]
- )
+ :attr:`theme_line_height` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- Color of ripple effects.
- :attr:`ripple_color` is an :class:`~kivy.properties.AliasProperty` that
- returns the value in ``rgba`` format for :attr:`ripple_color`,
- property is readonly.
+ theme_font_name = OptionProperty("Primary", options=["Primary", "Custom"])
"""
+ Font name scheme name.
- def _determine_device_orientation(self, _, window_size) -> None:
- if window_size[0] > window_size[1]:
- self.device_orientation = "landscape"
- elif window_size[1] >= window_size[0]:
- self.device_orientation = "portrait"
+ .. versionadded:: 2.0.0
- device_orientation = StringProperty("")
- """
- Device orientation.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`.
+ :attr:`theme_font_name` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def _get_standard_increment(self) -> float:
- if DEVICE_TYPE == "mobile":
- if self.device_orientation == "landscape":
- return dp(48)
- else:
- return dp(56)
- else:
- return dp(64)
-
- standard_increment = AliasProperty(
- _get_standard_increment, bind=["device_orientation"]
+ theme_shadow_softness = OptionProperty(
+ "Primary", options=["Primary", "Custom"]
)
"""
- Value of standard increment.
+ Elevation softness scheme name.
- :attr:`standard_increment` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`standard_increment`,
- property is readonly.
- """
+ .. versionadded:: 2.0.0
- def _get_horizontal_margins(self) -> float:
- if DEVICE_TYPE == "mobile":
- return dp(16)
- else:
- return dp(24)
+ Available options are: `'Primary'`, `'Custom'`.
- horizontal_margins = AliasProperty(_get_horizontal_margins)
+ :attr:`theme_shadow_softness` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- Value of horizontal margins.
- :attr:`horizontal_margins` is an :class:`~kivy.properties.AliasProperty`
- that returns the value in ``rgba`` format for :attr:`horizontal_margins`,
- property is readonly.
+ theme_focus_color = OptionProperty("Primary", options=["Primary", "Custom"])
"""
+ Focus color scheme name.
- def on_theme_style(self, interval: int, theme_style: str) -> None:
- if (
- hasattr(App.get_running_app(), "theme_cls")
- and App.get_running_app().theme_cls == self
- ):
- self.set_clearcolor_by_theme_style(theme_style)
-
- _set_clearcolor = False
-
- def set_clearcolor_by_theme_style(self, theme_style):
- if self.theme_style_switch_animation and self._set_clearcolor:
- Animation(
- clearcolor=get_color_from_hex(
- self.colors[theme_style]["Background"]
- ),
- d=self.theme_style_switch_animation_duration,
- t="linear",
- ).start(Window)
- else:
- Window.clearcolor = get_color_from_hex(
- self.colors[theme_style]["Background"]
- )
- self._set_clearcolor = True
-
- # Font name, size (sp), always caps, letter spacing (sp).
- font_styles = DictProperty(
- {
- "H1": ["RobotoLight", 96, False, -1.5],
- "H2": ["RobotoLight", 60, False, -0.5],
- "H3": ["Roboto", 48, False, 0],
- "H4": ["Roboto", 34, False, 0.25],
- "H5": ["Roboto", 24, False, 0],
- "H6": ["RobotoMedium", 20, False, 0.15],
- "Subtitle1": ["Roboto", 16, False, 0.15],
- "Subtitle2": ["RobotoMedium", 14, False, 0.1],
- "Body1": ["Roboto", 16, False, 0.5],
- "Body2": ["Roboto", 14, False, 0.25],
- "Button": ["RobotoMedium", 14, True, 1.25],
- "Caption": ["Roboto", 12, False, 0.4],
- "Overline": ["Roboto", 10, True, 1.5],
- "Icon": ["Icons", 24, False, 0],
- }
- )
- """
- Data of default font styles.
-
- Add custom font
- ---------------
-
- .. tabs::
-
- .. tab:: Declarative style with KV
-
- .. code-block:: python
-
- from kivy.core.text import LabelBase
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.font_definitions import theme_font_styles
-
- KV = '''
- MDScreen:
-
- MDLabel:
- text: "JetBrainsMono"
- halign: "center"
- font_style: "JetBrainsMono"
- '''
-
-
- class MainApp(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
-
- LabelBase.register(
- name="JetBrainsMono",
- fn_regular="JetBrainsMono-Regular.ttf")
-
- theme_font_styles.append('JetBrainsMono')
- self.theme_cls.font_styles["JetBrainsMono"] = [
- "JetBrainsMono",
- 16,
- False,
- 0.15,
- ]
- return Builder.load_string(KV)
-
+ .. versionadded:: 2.0.0
- MainApp().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
+ Available options are: `'Primary'`, `'Custom'`.
- from kivy.core.text import LabelBase
-
- from kivymd.app import MDApp
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.label import MDLabel
- from kivymd.font_definitions import theme_font_styles
-
-
- class MainApp(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
-
- LabelBase.register(
- name="JetBrainsMono",
- fn_regular="JetBrainsMono-Regular.ttf")
-
- theme_font_styles.append('JetBrainsMono')
- self.theme_cls.font_styles["JetBrainsMono"] = [
- "JetBrainsMono",
- 16,
- False,
- 0.15,
- ]
- return (
- MDScreen(
- MDLabel(
- text="JetBrainsMono",
- halign="center",
- font_style="JetBrainsMono",
- )
- )
- )
-
-
- MainApp().run()
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles.png
- :align: center
-
- :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`.
+ :attr:`theme_focus_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- def set_colors(
- self,
- primary_palette: str,
- primary_hue: str,
- primary_light_hue: str,
- primary_dark_hue: str,
- accent_palette: str,
- accent_hue: str,
- accent_light_hue: str,
- accent_dark_hue: str,
- ) -> None:
- """
- Courtesy method to allow all of the theme color attributes to be set in one call.
-
- :attr:`set_colors` allows all of the following to be set in one method call:
-
- * primary palette color,
- * primary hue,
- * primary light hue,
- * primary dark hue,
- * accent palette color,
- * accent hue,
- * accent ligth hue, and
- * accent dark hue.
-
- Note that all values *must* be provided. If you only want to set one or two values
- use the appropriate method call for that.
-
- .. tabs::
-
- .. tab:: Imperative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.button import MDRectangleFlatButton
-
- class MainApp(MDApp):
- def build(self):
- self.theme_cls.set_colors(
- "Blue", "600", "50", "800", "Teal", "600", "100", "800"
- )
- screen = MDScreen()
- screen.add_widget(
- MDRectangleFlatButton(
- text="Hello, World",
- pos_hint={"center_x": 0.5, "center_y": 0.5},
- )
- )
- return screen
-
-
- MainApp().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.button import MDRectangleFlatButton
-
- class MainApp(MDApp):
- def build(self):
- self.theme_cls.set_colors(
- "Blue", "600", "50", "800", "Teal", "600", "100", "800"
- )
- return (
- MDScreen(
- MDRectangleFlatButton(
- text="Hello, World",
- pos_hint={"center_x": 0.5, "center_y": 0.5},
- )
- )
- )
-
-
- MainApp().run()
- """
-
- self.primary_palette = primary_palette
- self.primary_hue = primary_hue
- self.primary_light_hue = primary_light_hue
- self.primary_dark_hue = primary_dark_hue
- self.accent_palette = accent_palette
- self.accent_hue = accent_hue
- self.accent_light_hue = accent_light_hue
- self.accent_dark_hue = accent_dark_hue
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style))
- self._determine_device_orientation(None, Window.size)
- Window.bind(size=self._determine_device_orientation)
- self.bind(font_styles=self.sync_theme_styles)
- self.colors = colors
- Clock.schedule_once(self.sync_theme_styles)
-
- def sync_theme_styles(self, *args) -> None:
- # Syncs the values from self.font_styles to theme_font_styles
- # this will ensure continuity when someone registers a new font_style.
- for num, style in enumerate(theme_font_styles):
- if style not in self.font_styles:
- theme_font_styles.pop(num)
- for style in self.font_styles.keys():
- theme_font_styles.append(style)
-
-
-class ThemableBehavior(EventDispatcher):
- theme_cls = ObjectProperty()
+ theme_divider_color = OptionProperty(
+ "Primary", options=["Primary", "Custom"]
+ )
"""
- Instance of :class:`~ThemeManager` class.
+ Divider color scheme name.
- :attr:`theme_cls` is an :class:`~kivy.properties.ObjectProperty`.
- """
+ .. versionadded:: 2.0.0
- device_ios = BooleanProperty(DEVICE_IOS)
- """
- ``True`` if device is ``iOS``.
+ Available options are: `'Primary'`, `'Custom'`.
- :attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`.
+ :attr:`theme_divider_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- widget_style = OptionProperty(
- "android", options=["android", "ios", "desktop"]
+ theme_text_color = OptionProperty(
+ "Primary",
+ options=[
+ "Primary",
+ "Secondary",
+ "Hint",
+ "Error",
+ "Custom",
+ ],
)
"""
- Allows to set one of the three style properties for the widget:
- `'android'`, `'ios'`, `'desktop'`.
-
- For example, for the class :class:`~kivymd.uix.selectioncontrol.MDSwitch`
- has two styles - `'android'` and `'ios'`:
-
- .. code-block:: kv
-
- MDSwitch:
- widget_style: "ios"
-
- .. code-block:: kv
-
- MDSwitch:
- widget_style: "android"
+ Label color scheme name.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-android-ios.png
- :align: center
+ Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
+ `'Custom'`.
- :attr:`widget_style` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'android'`.
+ :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
- opposite_colors = BooleanProperty(False)
+ theme_icon_color = OptionProperty(
+ "Primary",
+ options=[
+ "Primary",
+ "Secondary",
+ "Hint",
+ "Error",
+ "Custom",
+ ],
+ )
"""
- For some widgets, for example, for a widget
- :class:`~kivymd.uix.toolbar.MDTopAppBar` changes the color of the label to
- the color opposite to the main theme.
-
- .. code-block:: kv
-
- MDTopAppBar:
- title: "MDTopAppBar"
- opposite_colors: True
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-true.png
- :align: center
+ Label color scheme name.
- .. code-block:: kv
+ Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
+ `'Custom'`.
- MDTopAppBar:
- title: "MDTopAppBar"
- opposite_colors: True
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-false.png
- :align: center
+ :attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Primary'`.
"""
def __init__(self, **kwargs):
- self.unbind_properties = [
- "theme_style",
- "material_style",
- "device_orientation",
- "primary_color",
- "primary_palette",
- "accent_palette",
- "text_color",
- ]
-
- if self.theme_cls is not None:
- pass
- else:
+ if self.theme_cls is None:
try:
if not isinstance(
App.get_running_app().property("theme_cls", True),
@@ -1690,15 +926,7 @@ def remove_widget(self, widget) -> None:
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:
+ for property_name in ["theme_style", "primary_palette"]:
if widget == callback.proxy:
widget.theme_cls.unbind(
**{
@@ -1707,10 +935,6 @@ def remove_widget(self, widget) -> None:
)
}
)
- # KivyMD widgets may contain other MD widgets.
- for children in widget.children:
- if hasattr(children, "theme_cls"):
- self.remove_widget(children)
except ReferenceError:
pass
diff --git a/kivymd/theming_dynamic_text.py b/kivymd/theming_dynamic_text.py
deleted file mode 100755
index 64f992195..000000000
--- a/kivymd/theming_dynamic_text.py
+++ /dev/null
@@ -1,90 +0,0 @@
-"""
-Theming Dynamic Text
-====================
-
-Two implementations. The first is based on color brightness obtained from-
-https://www.w3.org/TR/AERT#color-contrast
-The second is based on relative luminance calculation for sRGB obtained from-
-https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
-and contrast ratio calculation obtained from-
-https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
-
-Preliminary testing suggests color brightness more closely matches the
-`Material Design spec` suggested text colors, but the alternative implementation
-is both newer and the current 'correct' recommendation, so is included here
-as an option.
-"""
-
-
-def _color_brightness(color):
- # Implementation of color brightness method
- brightness = color[0] * 299 + color[1] * 587 + color[2] * 114
- brightness = brightness
- return brightness
-
-
-def _black_or_white_by_color_brightness(color):
- if _color_brightness(color) >= 500:
- return "black"
- else:
- return "white"
-
-
-def _normalized_channel(color):
- # Implementation of contrast ratio and relative luminance method
- if color <= 0.03928:
- return color / 12.92
- else:
- return ((color + 0.055) / 1.055) ** 2.4
-
-
-def _luminance(color):
- rg = _normalized_channel(color[0])
- gg = _normalized_channel(color[1])
- bg = _normalized_channel(color[2])
- return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg
-
-
-def _black_or_white_by_contrast_ratio(color):
- l_color = _luminance(color)
- l_black = 0.0
- l_white = 1.0
- b_contrast = (l_color + 0.05) / (l_black + 0.05)
- w_contrast = (l_white + 0.05) / (l_color + 0.05)
- return "white" if w_contrast >= b_contrast else "black"
-
-
-def get_contrast_text_color(color, use_color_brightness=True):
- if use_color_brightness:
- contrast_color = _black_or_white_by_color_brightness(color)
- else:
- contrast_color = _black_or_white_by_contrast_ratio(color)
- if contrast_color == "white":
- return 1, 1, 1, 1
- else:
- return 0, 0, 0, 1
-
-
-if __name__ == "__main__":
- from kivy.utils import get_color_from_hex
-
- from kivymd.color_definitions import colors, text_colors
-
- for c in colors.items():
- if c[0] in ["Light", "Dark"]:
- continue
- color = c[0]
- print(f"For the {color} color palette:")
- for name, hex_color in c[1].items():
- if hex_color:
- col = get_color_from_hex(hex_color)
- col_bri = get_contrast_text_color(col)
- con_rat = get_contrast_text_color(
- col, use_color_brightness=False
- )
- text_color = text_colors[c[0]][name]
- print(
- f" The {name} hue gives {col_bri} using color "
- f"brightness, {con_rat} using contrast ratio, and "
- f"{text_color} from the MD spec"
- )
diff --git a/kivymd/toast/androidtoast/androidtoast.py b/kivymd/toast/androidtoast/androidtoast.py
index 18af63e18..c52b74084 100644
--- a/kivymd/toast/androidtoast/androidtoast.py
+++ b/kivymd/toast/androidtoast/androidtoast.py
@@ -63,7 +63,7 @@ def toast(text, length_long=False, gravity=0, y=0, x=0):
:param length_long: the amount of time (in seconds) that the toast is
visible on the screen;
:param text: text to be displayed in the toast;
- :param short_duration: duration of the toast, if `True` the toast
+ :param length_long: duration of the toast, if `True` the toast
will last 2.3s but if it is `False` the toast will last 3.9s;
:param gravity: refers to the toast position, if it is 80 the toast will
be shown below, if it is 40 the toast will be displayed above;
diff --git a/kivymd/uix/__init__.py b/kivymd/uix/__init__.py
index 9aec2dc15..bb7066318 100755
--- a/kivymd/uix/__init__.py
+++ b/kivymd/uix/__init__.py
@@ -5,10 +5,8 @@
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen
-from kivymd.uix.behaviors import SpecificBackgroundColorBehavior
-
-class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
+class MDAdaptiveWidget:
adaptive_height = BooleanProperty(False)
"""
If `True`, the following properties will be applied to the widget:
diff --git a/kivymd/uix/anchorlayout.py b/kivymd/uix/anchorlayout.py
index fe435423f..381dc3a32 100644
--- a/kivymd/uix/anchorlayout.py
+++ b/kivymd/uix/anchorlayout.py
@@ -15,7 +15,7 @@
AnchorLayout:
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -26,7 +26,7 @@
.. code-block:: kv
MDAnchorLayout:
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
"""
__all__ = ("MDAnchorLayout",)
@@ -35,13 +35,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDAnchorLayout(
- DeclarativeBehavior, ThemableBehavior, AnchorLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ AnchorLayout,
+ MDAdaptiveWidget,
):
"""
- Anchor layout class. For more information, see in the
- :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation.
+ Anchor layout class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.anchorlayout.AnchorLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/appbar/__init__.py b/kivymd/uix/appbar/__init__.py
new file mode 100644
index 000000000..1a5c0fa18
--- /dev/null
+++ b/kivymd/uix/appbar/__init__.py
@@ -0,0 +1,12 @@
+# NOQA F401
+from .appbar import (
+ MDActionTopAppBarButton,
+ MDActionBottomAppBarButton,
+ MDBottomAppBar,
+ MDFabBottomAppBarButton,
+ MDTopAppBar,
+ MDTopAppBarLeadingButtonContainer,
+ MDActionTopAppBarButton,
+ MDTopAppBarTitle,
+ MDTopAppBarTrailingButtonContainer,
+)
diff --git a/kivymd/uix/appbar/appbar.kv b/kivymd/uix/appbar/appbar.kv
new file mode 100644
index 000000000..3600c949c
--- /dev/null
+++ b/kivymd/uix/appbar/appbar.kv
@@ -0,0 +1,102 @@
+
+ size_hint_x: None
+ width: self.minimum_width
+ padding: "8dp", 0, "16dp", 0
+
+
+
+ size_hint_x: None
+ width: self.minimum_width
+ padding: "16dp", 0, "16dp", 0
+ spacing: "4dp"
+
+
+
+ pos_hint: {"center_y": .5}
+ color:
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_icon_color == "Primary" else \
+ self.icon_color
+
+
+
+ font_style:
+ { \
+ "small": "Title", \
+ "medium": "Headline", \
+ "large": "Headline", \
+ }[self._appbar.type if self._appbar else "small"]
+ role:
+ { \
+ "small": "large", \
+ "medium": "small", \
+ "large": "medium", \
+ }[self._appbar.type if self._appbar else "large"]
+ adaptive_width:
+ ( \
+ True \
+ if self._appbar.type == "small" else \
+ False \
+ ) \
+ if self._appbar else True
+ size_hint_x:
+ ( \
+ None \
+ if self._appbar.type == "small" else \
+ 1 \
+ ) \
+ if self._appbar else None
+
+
+
+ canvas:
+ Color:
+ rgba:
+ self.theme_cls.surfaceColor \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color
+ Rectangle:
+ pos: self.pos
+ size: self.size
+
+ orientation:
+ "vertical" \
+ if self.type in ("medium", "large") else \
+ "horizontal"
+ size_hint_y: None
+ height:
+ { \
+ "small": "64dp", \
+ "medium": "112dp", \
+ "large": "152dp", \
+ }[self.type]
+
+ BoxLayout:
+ id: root_box
+
+ BoxLayout:
+ id: title_box
+ padding: "16dp", 0, "16dp", 0
+
+
+
+ elevation_level: 0
+ theme_shadow_color: "Custom"
+ shadow_color: self.theme_cls.transparentColor
+
+
+
+ size_hint_y: None
+ height: "80dp"
+ elevation_level:
+ 2 \
+ if self.theme_elevation_level == "Primary" else \
+ self.elevation_level
+ shadow_softness:
+ 2 \
+ if self.theme_shadow_softness == "Primary" else \
+ self.shadow_softness
+ md_bg_color:
+ self.theme_cls.surfaceContainerColor \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color
diff --git a/kivymd/uix/appbar/appbar.py b/kivymd/uix/appbar/appbar.py
new file mode 100755
index 000000000..a4665e47e
--- /dev/null
+++ b/kivymd/uix/appbar/appbar.py
@@ -0,0 +1,1244 @@
+"""
+Components/Appbar
+=================
+
+.. seealso::
+
+ `Material Design spec, App bars: top `_
+
+ `Material Design spec, App bars: bottom `_
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-top.png
+ :align: center
+
+`KivyMD` provides the following bar positions for use:
+
+- TopAppBar_
+- BottomAppBar_
+
+.. TopAppBar_:
+TopAppBar
+---------
+
+- Contains a title and actions related to the current screen
+- Four types: center-aligned, small, medium, and large
+- On scroll, apply a container fill color to separate app bar from body content
+- Top app bars have the same width as the device window
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-available-types.png
+ :align: center
+
+1. Center-aligned
+2. Small
+3. Medium
+4. Large
+
+.. note:: KivyMD does not provide a `Center-aligned` type panel. But you can
+ easily create this pit panel yourself (read the documentation below).
+
+Usage
+-----
+
+.. code-block:: kv
+
+ MDTopAppBar:
+ type: "small"
+
+ MDTopAppBarLeadingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "menu"
+
+ MDTopAppBarTitle:
+ text: "AppBar Center-aligned"
+ pos_hint: {"center_x": .5}
+
+ MDTopAppBarTrailingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "account-circle-outline"
+
+Anatomy
+-------
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-anatomy.png
+ :align: center
+
+Configurations
+==============
+
+1. Center-aligned
+-----------------
+
+.. code-block:: kv
+
+ MDScreen:
+ md_bg_color: self.theme_cls.secondaryContainerColor
+
+ MDTopAppBar:
+ type: "small"
+ size_hint_x: .8
+ pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDTopAppBarLeadingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "menu"
+
+ MDTopAppBarTitle:
+ text: "AppBar small"
+ pos_hint: {"center_x": .5}
+
+ MDTopAppBarTrailingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "account-circle-outline"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-center-aligned.png
+ :align: center
+
+2. Small
+--------
+
+.. code-block:: kv
+
+ MDScreen:
+ md_bg_color: self.theme_cls.secondaryContainerColor
+
+ MDTopAppBar:
+ type: "small"
+ size_hint_x: .8
+ pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDTopAppBarLeadingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "arrow-left"
+
+ MDTopAppBarTitle:
+ text: "AppBar small"
+
+ MDTopAppBarTrailingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "attachment"
+
+ MDActionTopAppBarButton:
+ icon: "calendar"
+
+ MDActionTopAppBarButton:
+ icon: "dots-vertical"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-small.png
+ :align: center
+
+3. Medium
+---------
+
+.. code-block:: kv
+
+ MDTopAppBar:
+ type: "medium"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-medium.png
+ :align: center
+
+4. Large
+--------
+
+.. code-block:: kv
+
+ MDTopAppBar:
+ type: "large"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-large.png
+ :align: center
+
+.. BottomAppBar:
+BottomAppBar
+------------
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-bottom-m3.png
+ :align: center
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDBottomAppBar:
+
+ MDFabBottomAppBarButton:
+ icon: "plus"
+ '''
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-1.png
+ :align: center
+
+Add action items
+----------------
+
+.. code-block:: kv
+
+ #:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton
+
+
+ MDScreen:
+
+ MDBottomAppBar:
+ action_items:
+ [
+ MDActionBottomAppBarButton(icon="gmail"),
+ MDActionBottomAppBarButton(icon="label-outline"),
+ MDActionBottomAppBarButton(icon="bookmark"),
+ ]
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-2.png
+ :align: center
+
+Change action items
+-------------------
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+ from kivymd.uix.appbar import MDActionBottomAppBarButton
+
+ KV = '''
+ #:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton
+
+
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDBottomAppBar:
+ id: bottom_appbar
+ action_items:
+ [
+ MDActionBottomAppBarButton(icon="gmail"),
+ MDActionBottomAppBarButton(icon="bookmark"),
+ ]
+
+ MDFabBottomAppBarButton:
+ icon: "plus"
+ on_release: app.change_actions_items()
+ '''
+
+
+ class Example(MDApp):
+ def change_actions_items(self):
+ self.root.ids.bottom_appbar.action_items = [
+ MDActionBottomAppBarButton(icon="magnify"),
+ MDActionBottomAppBarButton(icon="trash-can-outline"),
+ MDActionBottomAppBarButton(icon="download-box-outline"),
+ ]
+
+ def build(self):
+ self.theme_cls.theme_style = "Dark"
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-3.gif
+ :align: center
+
+A practical example
+-------------------
+
+.. code-block:: python
+
+ from kivy.clock import Clock
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty, BooleanProperty, ObjectProperty
+ from kivy.uix.behaviors import FocusBehavior
+ from kivy.uix.recycleboxlayout import RecycleBoxLayout
+ from kivy.uix.recycleview.layout import LayoutSelectionBehavior
+ from kivy.uix.recycleview.views import RecycleDataViewBehavior
+
+ from kivymd.uix.appbar import MDActionBottomAppBarButton
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.app import MDApp
+ from kivymd.utils import asynckivy
+
+ from faker import Faker # pip install Faker
+
+ KV = '''
+ #:import MDFabBottomAppBarButton kivymd.uix.appbar.MDFabBottomAppBarButton
+
+
+
+ orientation: "vertical"
+ adaptive_height: True
+ md_bg_color: "#373A22" if self.selected else "#1F1E15"
+ radius: 16
+ padding: 0, 0, 0, "16dp"
+
+ MDListItem:
+ theme_bg_color: "Custom"
+ md_bg_color: root.md_bg_color
+ radius: root.radius
+ ripple_effect: False
+
+ MDListItemLeadingAvatar:
+ source: root.avatar
+ # radius: self.height / 2
+
+ MDListItemHeadlineText:
+ text: root.name
+ theme_text_color: "Custom"
+ text_color: "#8A8D79"
+
+ MDListItemSupportingText:
+ text: root.time
+ theme_text_color: "Custom"
+ text_color: "#8A8D79"
+
+ MDLabel:
+ text: root.text
+ adaptive_height: True
+ theme_text_color: "Custom"
+ text_color: "#8A8D79"
+ padding_x: "16dp"
+ shorten: True
+ shorten_from: "right"
+
+ Widget:
+
+
+ MDFloatLayout:
+ md_bg_color: "#151511"
+
+ RecycleView:
+ id: card_list
+ viewclass: "UserCard"
+
+ SelectableRecycleGridLayout:
+ orientation: 'vertical'
+ spacing: "16dp"
+ padding: "16dp"
+ default_size: None, dp(120)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+ multiselect: True
+ touch_multiselect: True
+
+ MDBottomAppBar:
+ id: bottom_appbar
+ scroll_cls: card_list
+ allow_hidden: True
+ theme_bg_color: "Custom"
+ md_bg_color: "#232217"
+
+ MDFabBottomAppBarButton:
+ id: fab_button
+ icon: "plus"
+ theme_bg_color: "Custom"
+ md_bg_color: "#373A22"
+ theme_icon_color: "Custom"
+ icon_color: "#ffffff"
+ '''
+
+
+ class UserCard(RecycleDataViewBehavior, MDBoxLayout):
+ name = StringProperty()
+ time = StringProperty()
+ text = StringProperty()
+ avatar = StringProperty()
+ callback = ObjectProperty(lambda x: x)
+
+ index = None
+ selected = BooleanProperty(False)
+ selectable = BooleanProperty(True)
+
+ def refresh_view_attrs(self, rv, index, data):
+ self.index = index
+ return super().refresh_view_attrs(rv, index, data)
+
+ def on_touch_down(self, touch):
+ if super().on_touch_down(touch):
+ return True
+ if self.collide_point(*touch.pos) and self.selectable:
+ Clock.schedule_once(self.callback)
+ return self.parent.select_with_touch(self.index, touch)
+
+ def apply_selection(self, rv, index, is_selected):
+ self.selected = is_selected
+ rv.data[index]["selected"] = is_selected
+
+
+ class SelectableRecycleGridLayout(
+ FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout
+ ):
+ pass
+
+
+ class BottomAppBarButton(MDActionBottomAppBarButton):
+ theme_icon_color = "Custom"
+ icon_color = "#8A8D79"
+
+
+ class Test(MDApp):
+ selected_cards = False
+
+ def build(self):
+ return Builder.load_string(KV)
+
+ def on_tap_card(self, *args):
+ datas = [data["selected"] for data in self.root.ids.card_list.data]
+ if True in datas and not self.selected_cards:
+ self.root.ids.bottom_appbar.action_items = [
+ BottomAppBarButton(icon="gmail"),
+ BottomAppBarButton(icon="label-outline"),
+ BottomAppBarButton(icon="bookmark"),
+ ]
+ self.root.ids.fab_button.icon = "pencil"
+ self.selected_cards = True
+ else:
+ if len(list(set(datas))) == 1 and not list(set(datas))[0]:
+ self.selected_cards = False
+ if not self.selected_cards:
+ self.root.ids.bottom_appbar.action_items = [
+ BottomAppBarButton(icon="magnify"),
+ BottomAppBarButton(icon="trash-can-outline"),
+ BottomAppBarButton(icon="download-box-outline"),
+ ]
+ self.root.ids.fab_button.icon = "plus"
+
+ def on_start(self):
+ async def generate_card():
+ for i in range(10):
+ await asynckivy.sleep(0)
+ self.root.ids.card_list.data.append(
+ {
+ "name": fake.name(),
+ "time": fake.date(),
+ "avatar": fake.image_url(),
+ "text": fake.text(),
+ "selected": False,
+ "callback": self.on_tap_card,
+ }
+ )
+
+ self.on_tap_card()
+ fake = Faker()
+ Clock.schedule_once(lambda x: asynckivy.start(generate_card()))
+
+
+ Test().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-4.gif
+ :align: center
+
+API break
+=========
+
+1.2.0 version
+-------------
+
+.. code-block:: kv
+
+ MDTopAppBar:
+ type_height: "large"
+ headline_text: "Headline"
+ left_action_items: [["arrow-left", lambda x: x]]
+ right_action_items:
+ [ \
+ ["attachment", lambda x: x], \
+ ["calendar", lambda x: x], \
+ ["dots-vertical", lambda x: x], \
+ ]
+ anchor_title: "left"
+
+2.0.0 version
+-------------
+
+.. code-block:: kv
+
+ MDTopAppBar:
+ type: "large"
+
+ MDTopAppBarLeadingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "arrow-left"
+
+ MDTopAppBarTitle:
+ text: "AppBar small"
+
+ MDTopAppBarTrailingButtonContainer:
+
+ MDActionTopAppBarButton:
+ icon: "attachment"
+
+ MDActionTopAppBarButton:
+ icon: "calendar"
+
+ MDActionTopAppBarButton:
+ icon: "dots-vertical"
+"""
+
+from __future__ import annotations
+
+__all__ = (
+ "MDTopAppBar",
+ "MDTopAppBarTitle",
+ "MDBottomAppBar",
+ "MDActionTopAppBarButton",
+ "MDActionBottomAppBarButton",
+ "MDFabBottomAppBarButton",
+ "MDTopAppBarLeadingButtonContainer",
+ "MDTopAppBarTrailingButtonContainer",
+)
+
+import os
+
+from kivy import Logger
+from kivy.animation import Animation
+from kivy.clock import Clock
+from kivy.core.window import Window
+from kivy.lang import Builder
+from kivy.metrics import dp
+from kivy.properties import (
+ BooleanProperty,
+ ListProperty,
+ NumericProperty,
+ ObjectProperty,
+ OptionProperty,
+ StringProperty,
+ ColorProperty,
+)
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.floatlayout import FloatLayout
+from kivy.uix.scrollview import ScrollView
+from kivy.uix.widget import Widget
+
+from kivymd import uix_path
+from kivymd.theming import ThemableBehavior
+from kivymd.uix.behaviors import (
+ CommonElevationBehavior,
+ DeclarativeBehavior,
+ RotateBehavior,
+ ScaleBehavior,
+ BackgroundColorBehavior,
+)
+from kivymd.uix.button import MDFabButton, MDIconButton
+from kivymd.uix.controllers import WindowController
+from kivymd.uix.label import MDLabel
+
+from kivymd.utils import asynckivy
+from kivymd.utils.set_bars_colors import set_bars_colors
+
+with open(
+ os.path.join(uix_path, "appbar", "appbar.kv"), encoding="utf-8"
+) as kv_file:
+ Builder.load_string(kv_file.read())
+
+
+class BaseTopAppBarButtonContainer(DeclarativeBehavior, BoxLayout):
+ # kivymd.uix.appbar.appbar.MDTopAppBar object.
+ _appbar = ObjectProperty()
+
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDActionTopAppBarButton):
+ Clock.schedule_once(lambda x: self._check_icon_color(widget))
+
+ return super().add_widget(widget)
+
+ def _check_icon_color(self, widget):
+ if widget.theme_icon_color == "Primary" and widget.icon_color == None:
+ widget.theme_icon_color = "Custom"
+ widget.icon_color = widget.theme_cls.onSurfaceColor
+
+
+class MDFabBottomAppBarButton(MDFabButton, RotateBehavior, ScaleBehavior):
+ """
+ Implements a floating action button (FAB) for a bar with type 'bottom'.
+
+ For more information, see in the
+ :class:`~kivymd.uix.button.button.MDFabButton` and
+ :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior` and
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
+ classes documentation.
+ """
+
+
+class MDActionTopAppBarButton(MDIconButton):
+ """
+ Implements action buttons on the bar.
+
+ For more information, see in the
+ :class:`~kivymd.uix.button.button.MDIconButton` class documentation.
+ """
+
+ md_bg_color_disabled = ColorProperty(None)
+ """
+ The background color in (r, g, b, a) or string format of the button when
+ the button is disabled.
+
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+
+class MDActionBottomAppBarButton(MDActionTopAppBarButton):
+ """
+ Implements action buttons for a
+ :class:'~kivymd.uix.appbar.appbar.MDBottomAppBar' class.
+
+ .. versionadded:: 1.2.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.appbar.appbar.MDActionTopAppBarButton`
+ class documentation.
+ """
+
+
+class MDTopAppBarTitle(MDLabel):
+ """
+ Implements the panel title.
+
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
+
+ _appbar = ObjectProperty()
+ _title_width = NumericProperty(0)
+
+ def on_text(self, instance, value) -> None:
+ """Fired when the :attr:`text` value changes."""
+
+ def set_title_width(*args) -> None:
+ self._title_width = self.texture_size[0]
+
+ Clock.schedule_once(set_title_width)
+
+ def on_pos_hint(self, instance, value) -> None:
+ """Fired when the :attr:`pos_hint` value changes."""
+
+ if self._appbar:
+ Clock.schedule_once(
+ lambda x: self._appbar._set_padding_title(value)
+ )
+
+
+class MDTopAppBarLeadingButtonContainer(BaseTopAppBarButtonContainer):
+ """
+ Implements a container for the leading action buttons.
+
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout`
+ classes documentation.
+ """
+
+
+class MDTopAppBarTrailingButtonContainer(BaseTopAppBarButtonContainer):
+ """
+ Implements a container for the trailing action buttons.
+
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout`
+ classes documentation.
+ """
+
+
+class MDTopAppBar(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ CommonElevationBehavior,
+ BackgroundColorBehavior,
+ BoxLayout,
+ WindowController,
+):
+ """
+ Top app bar class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout` and
+ :class:`~kivymd.uix.controllers.windowcontroller.WindowController`
+ classes documentation.
+
+ :Events:
+ `on_action_button`
+ Method for the button used for the :class:`~MDBottomAppBar` class.
+ """
+
+ set_bars_color = BooleanProperty(False)
+ """
+ If `True` the background color of the bar status will be set automatically
+ according to the current color of the bar.
+
+ .. versionadded:: 1.0.0
+
+ See `set_bars_colors `_
+ for more information.
+
+ :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ type = OptionProperty("small", options=["medium", "large", "small"])
+ """
+ Bar height type.
+
+ .. versionadded:: 1.0.0
+
+ Available options are: 'medium', 'large', 'small'.
+
+ :attr:`type_height` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'small'`.
+ """
+
+ _trailing_button_container = ObjectProperty()
+ _leading_button_container = ObjectProperty()
+ _appbar_title = ObjectProperty()
+
+ def on_type(self, instance, value) -> None:
+ def on_type(*args):
+ if value in ("medium", "large"):
+ self.ids.root_box.add_widget(Widget(), index=1)
+
+ Clock.schedule_once(on_type, 0.5)
+
+ def on_size(self, instance, size) -> None:
+ """Fired when the application screen size changes."""
+
+ if self._appbar_title:
+ if not self._appbar_title._title_width:
+ self._appbar_title._title_width = (
+ self._appbar_title.texture_size[0]
+ )
+ Clock.schedule_once(
+ lambda x: self._appbar_title.on_pos_hint(
+ self._appbar_title, self._appbar_title.pos_hint
+ )
+ )
+
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDTopAppBarTitle):
+ widget._appbar = self
+ self._appbar_title = widget
+ Clock.schedule_once(lambda x: self._add_title(widget))
+ elif isinstance(widget, MDTopAppBarTrailingButtonContainer):
+ self._trailing_button_container = widget
+ widget._appbar = self
+ Clock.schedule_once(lambda x: self.ids.root_box.add_widget(widget))
+ elif isinstance(widget, MDTopAppBarLeadingButtonContainer):
+ widget._appbar = self
+ self._leading_button_container = widget
+ Clock.schedule_once(lambda x: self.ids.root_box.add_widget(widget))
+ else:
+ return super().add_widget(widget)
+
+ def _add_title(self, widget):
+ if self.type == "small":
+ self.ids.root_box.add_widget(widget)
+ else:
+ self.ids.title_box.add_widget(widget)
+
+ def _set_padding_title(self, value):
+ if value.get("center_x", 0) == 0.5 and self.type == "small":
+ if (
+ not self._trailing_button_container
+ and self._leading_button_container
+ ):
+ left_padding = (self.width // 2) - (
+ self._leading_button_container.width
+ + (self._appbar_title._title_width // 2)
+ )
+ self._appbar_title.padding = [left_padding, 0, 0, 0]
+ elif (
+ self._trailing_button_container
+ and not self._leading_button_container
+ ):
+ left_padding = (self.width // 2) - (
+ self._appbar_title._title_width // 2
+ )
+ right_padding = (self.width // 2) - (
+ self._trailing_button_container.width
+ + (self._appbar_title._title_width // 2)
+ )
+ self._appbar_title.padding = [left_padding, 0, right_padding, 0]
+ elif (
+ not self._trailing_button_container
+ and not self._leading_button_container
+ ):
+ left_padding = (self.width // 2) - (
+ self._appbar_title._title_width // 2
+ )
+ right_padding = (self.width // 2) - (
+ self._appbar_title._title_width // 2
+ )
+ self._appbar_title.padding = [left_padding, 0, right_padding, 0]
+ elif (
+ self._trailing_button_container
+ and self._leading_button_container
+ ):
+ left_padding = (self.width // 2) - (
+ self._leading_button_container.width
+ + (self._appbar_title._title_width // 2)
+ )
+ right_padding = (self.width // 2) - (
+ self._trailing_button_container.width
+ + (self._appbar_title._title_width // 2)
+ )
+ self._appbar_title.padding = [left_padding, 0, right_padding, 0]
+ elif (
+ not value
+ and self._trailing_button_container
+ and self._leading_button_container
+ ):
+ if self.type == "small":
+ right_padding = self.width - (
+ self._trailing_button_container.width
+ + self._leading_button_container.width
+ + self._appbar_title._title_width
+ )
+ self._appbar_title.padding = [0, 0, right_padding, 0]
+ elif (
+ not value
+ and self._trailing_button_container
+ and not self._leading_button_container
+ ):
+ if self.type == "small":
+ right_padding = self.width - (
+ self._trailing_button_container.width
+ + self._appbar_title._title_width
+ )
+ self._appbar_title.padding = [
+ dp(16),
+ 0,
+ right_padding - dp(16),
+ 0,
+ ]
+ elif (
+ not value
+ and not self._trailing_button_container
+ and not self._leading_button_container
+ ):
+ self._appbar_title.padding_x = dp(16)
+
+
+class MDBottomAppBar(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ CommonElevationBehavior,
+ FloatLayout,
+):
+ """
+ Bottom app bar class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivy.uix.floatlayout.FloatLayout`
+ classes documentation.
+
+ :Events:
+ `on_show_bar`
+ The method is fired when the :class:`~MDBottomAppBar` panel
+ is shown.
+ `on_hide_bar`
+ The method is fired when the :class:`~MDBottomAppBar` panel
+ is hidden.
+ """
+
+ action_items = ListProperty()
+ """
+ The icons on the left bar.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`action_items` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[]`.
+ """
+
+ animation = BooleanProperty(True)
+ """
+ # TODO: add description.
+ # FIXME: changing the value does not affect anything.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`animation` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `True`.
+ """
+
+ show_transition = StringProperty("linear")
+ """
+ Type of button display transition.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'linear'`.
+ """
+
+ hide_transition = StringProperty("in_back")
+ """
+ Type of button hidden transition.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'in_back'`.
+ """
+
+ hide_duration = NumericProperty(0.4)
+ """
+ Duration of button hidden transition.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
+ """
+
+ show_duration = NumericProperty(0.2)
+ """
+ Duration of button display transition.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
+ """
+
+ scroll_cls = ObjectProperty()
+ """
+ Widget inherited from the :class:`~kivy.uix.scrollview.ScrollView` class.
+ The value must be set if the :attr:`allow_hidden` parameter is `True`.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`scroll_cls` is a :class:`~kivy.properties.ObjectProperty`
+ and defaults to `None`.
+ """
+
+ allow_hidden = BooleanProperty(False)
+ """
+ Allows or disables hiding the panel when scrolling content.
+ If the value is `True`, the :attr:`scroll_cls` parameter must be specified.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`allow_hidden` is a :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ bar_is_hidden = BooleanProperty(False)
+ """
+ Is the panel currently hidden.
+
+ .. versionadded:: 1.2.0
+
+ :attr:`bar_is_hidden` is a :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ _padding = dp(16)
+ _x = -dp(48)
+ _scroll_cls_y = 0
+ _cache = []
+ _current_data = []
+ _wait_removed = False
+ _animated_hidden = True
+ _animated_show = True
+ _fab_bottom_app_bar_button = None
+ _action_overflow_button = None
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_event_type("on_show_bar")
+ self.register_event_type("on_hide_bar")
+
+ def button_centering_animation(
+ self,
+ button: MDActionBottomAppBarButton | MDFabBottomAppBarButton,
+ ) -> None:
+ """
+ Animation of centering buttons for
+ :class:`~MDActionOverFlowButton`,
+ :class:`~MDActionBottomAppBarButton` and
+ :class:`~MDFabBottomAppBarButton` classes.
+ """
+
+ if self.animation:
+ Animation(
+ y=self.height / 2 - dp(48) / 2,
+ opacity=1,
+ d=self.show_duration,
+ t=self.show_transition,
+ ).start(button)
+
+ def check_scroll_direction(self, scroll_cls, y: float) -> None:
+ """
+ Checks the scrolling direction.
+ Depending on the scrolling direction, hides or shows the
+ :class:`~MDBottomAppBar` panel.
+ """
+
+ if round(y, 1) < self._scroll_cls_y and not self.bar_is_hidden:
+ self.hide_bar()
+ if round(y, 1) > self._scroll_cls_y and self.bar_is_hidden:
+ self.show_bar()
+
+ self._scroll_cls_y = round(y, 1)
+
+ def show_bar(self) -> None:
+ """Show :class:`~MDBottomAppBar` panel."""
+
+ def on_complete(*args):
+ self.dispatch("on_show_bar")
+
+ def on_progress(animation, instance, progress):
+ if progress > 0.5 and self._animated_show:
+ self._animated_show = False
+ for i, widget in enumerate(self.children):
+ if isinstance(widget, MDActionBottomAppBarButton):
+ anim_icon = Animation(
+ y=self.height / 2 - dp(48) / 2,
+ d=self.show_duration,
+ t=self.show_transition,
+ )
+ Clock.schedule_once(
+ lambda x, y=widget: anim_icon.start(y),
+ i / 10,
+ )
+ if self._fab_bottom_app_bar_button:
+ Animation(
+ y=self._fab_bottom_app_bar_button.y + dp(4),
+ d=self.show_duration,
+ t=self.show_transition,
+ ).start(self._fab_bottom_app_bar_button)
+
+ self.bar_is_hidden = False
+ self._animated_show = True
+ anim = Animation(
+ y=0,
+ d=self.show_duration,
+ t=self.show_transition,
+ )
+ anim.bind(on_progress=on_progress, on_complete=on_complete)
+ anim.start(self)
+
+ def hide_bar(self) -> None:
+ """Hide :class:`~MDBottomAppBar` panel."""
+
+ def on_complete(*args):
+ self.dispatch("on_hide_bar")
+
+ def on_progress(animation, instance, progress):
+ if (
+ progress > 0.5
+ and self._animated_hidden
+ and widget_icon == instance.icon
+ ):
+ self._animated_hidden = False
+ anim_bar = Animation(
+ y=-self.height,
+ d=self.hide_duration,
+ # t=self.hide_transition,
+ )
+ anim_bar.bind(on_complete=on_complete)
+ anim_bar.start(self)
+
+ if self._fab_bottom_app_bar_button:
+ Animation(
+ y=self._fab_bottom_app_bar_button.y - dp(4),
+ d=self.hide_duration,
+ t=self.hide_transition,
+ ).start(self._fab_bottom_app_bar_button)
+
+ self.bar_is_hidden = True
+ self._animated_hidden = True
+ len_children = len(self.children)
+ widget_icon = ""
+
+ for i, widget in enumerate(self.children):
+ if isinstance(widget, MDActionBottomAppBarButton):
+ anim = Animation(
+ y=-widget.height,
+ d=self.hide_duration,
+ t=self.hide_transition,
+ )
+ if i + 2 == len_children:
+ widget_icon = widget.icon
+ anim.bind(on_progress=on_progress)
+ Clock.schedule_once(
+ lambda x, y=widget: anim.start(y),
+ i / 10,
+ )
+
+ def on_show_bar(self, *args) -> None:
+ """
+ The method is fired when the :class:`~MDBottomAppBar` panel
+ is shown.
+ """
+
+ def on_hide_bar(self, *args) -> None:
+ """
+ The method is fired when the :class:`~MDBottomAppBar` panel
+ is hidden.
+ """
+
+ def on_scroll_cls(self, instance, scroll_cls) -> None:
+ """
+ Fired when the value of the :attr:`scroll_cls` attribute changes.
+ """
+
+ def on_scroll_cls(*args):
+ if not self.allow_hidden:
+ Logger.warning(
+ "KivyMD: "
+ "In order for the bottom bar to be automatically hidden "
+ "in addition to the `scroll_cls` parameter, set the value "
+ "of the `allow_hidden` parameter to `True`"
+ )
+
+ if issubclass(scroll_cls.__class__, ScrollView):
+ if self.allow_hidden:
+ scroll_cls.bind(scroll_y=self.check_scroll_direction)
+ else:
+ raise TypeError(
+ f"The `scroll_cls` parameter must be an object inherited "
+ f"from the {ScrollView} class"
+ )
+
+ Clock.schedule_once(on_scroll_cls)
+
+ def on_size(self, *args) -> None:
+ """Fired when the root screen is resized."""
+
+ if self._fab_bottom_app_bar_button:
+ self._fab_bottom_app_bar_button.x = Window.width - (dp(56) + dp(16))
+
+ def on_action_items(self, instance, value: list) -> None:
+ """
+ Fired when the value of the :attr:`action_items` attribute changes.
+ """
+
+ def wait_removed(*args):
+ if len(self.children) == 1 or not self.children:
+ Clock.unschedule(wait_removed)
+ self._wait_removed = False
+ self._x = -dp(48)
+ asynckivy.start(add_widget())
+
+ async def add_widget():
+ for button in value:
+ await asynckivy.sleep(0)
+ self.add_widget(button)
+
+ if self._cache:
+ self._cache.append(value)
+
+ for data in self._cache:
+ if value[0] in data:
+ for i, widget in enumerate(self.children):
+ if not self._wait_removed:
+ Clock.schedule_interval(wait_removed, 0)
+ self._wait_removed = True
+ if isinstance(widget, MDActionBottomAppBarButton):
+ anim = Animation(
+ y=-widget.height,
+ d=self.hide_duration,
+ t=self.hide_transition,
+ )
+ anim.bind(
+ on_complete=lambda x, y=widget: self.remove_widget(
+ y
+ )
+ )
+ Clock.schedule_once(
+ lambda x, y=widget: anim.start(y),
+ i / 10,
+ )
+ else:
+ self._cache.append(value)
+ self._current_data = value
+ asynckivy.start(add_widget())
+
+ def set_fab_opacity(self, *ars) -> None:
+ """
+ Sets the transparency value of the:class:`~MDFabBottomAppBarButton`
+ button.
+ """
+
+ # self._fab_bottom_app_bar_button.opacity = 1
+
+ def set_fab_icon(self, instance, value) -> None:
+ """
+ Animates the size of the :class:`~MDFabBottomAppBarButton` button.
+ """
+
+ # self._fab_bottom_app_bar_button.opacity = 0
+ anim = Animation(
+ scale_value_x=0,
+ scale_value_y=0,
+ opacity=0,
+ d=self.hide_duration,
+ t=self.hide_transition,
+ ) + Animation(
+ scale_value_x=1,
+ scale_value_y=1,
+ opacity=1,
+ d=self.show_duration,
+ t=self.show_transition,
+ )
+ anim.bind(on_complete=self.set_fab_opacity)
+ anim.start(instance)
+
+ def add_widget(self, widget, index=0, canvas=None):
+ if isinstance(widget, MDActionBottomAppBarButton):
+ self._x += widget.width
+ widget.pos = (
+ self._x + self._padding,
+ -dp(48) if self.animation else self.height / 2 - dp(48) / 2,
+ )
+ widget.opacity = int(not self.animation)
+ super().add_widget(widget)
+ self.button_centering_animation(widget)
+ elif isinstance(widget, MDFabBottomAppBarButton):
+ widget.bind(icon=self.set_fab_icon)
+ self._fab_bottom_app_bar_button = widget
+ Clock.schedule_once(self.set_fab_opacity)
+ widget.scale_value_x = int(not self.animation)
+ widget.scale_value_y = int(not self.animation)
+ widget.pos = (
+ Window.width - (dp(56) + self._padding),
+ self.height / 2 - dp(56) / 2,
+ )
+ super().add_widget(widget)
diff --git a/kivymd/uix/backdrop/__init__.py b/kivymd/uix/backdrop/__init__.py
deleted file mode 100644
index 7a5e6cf27..000000000
--- a/kivymd/uix/backdrop/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .backdrop import MDBackdrop # NOQA F401
diff --git a/kivymd/uix/backdrop/backdrop.kv b/kivymd/uix/backdrop/backdrop.kv
deleted file mode 100644
index 2f56d5be4..000000000
--- a/kivymd/uix/backdrop/backdrop.kv
+++ /dev/null
@@ -1,50 +0,0 @@
-
- md_bg_color:
- root.theme_cls.primary_color \
- if not root.back_layer_color \
- else root.back_layer_color
-
- MDBackdropToolbar:
- id: toolbar
- type_height: "small"
- anchor_title: root.anchor_title
- title: root.title
- elevation: 0
- left_action_items: root.left_action_items
- right_action_items: root.right_action_items
- pos_hint: {"top": 1}
- md_bg_color:
- root.theme_cls.primary_color \
- if not root.back_layer_color \
- else root.back_layer_color
-
- _BackLayer:
- id: back_layer
- y: -toolbar.height
- padding: 0, 0, 0, toolbar.height + dp(10)
-
- _FrontLayer:
- id: _front_layer
- md_bg_color: 0, 0, 0, 0
- orientation: "vertical"
- size_hint_y: None
- height: root.height - toolbar.height
- padding: root.padding
- md_bg_color:
- root.theme_cls.bg_normal \
- if not root.front_layer_color \
- else root.front_layer_color
- radius:
- [root.radius_left, root.radius_right,
- 0, 0]
-
- OneLineListItem:
- id: header_button
- text: root.header_text
- divider: None
- _no_ripple_effect: True
- on_press: root.open()
-
- MDBoxLayout:
- id: front_layer
- padding: 0, 0, 0, "10dp"
diff --git a/kivymd/uix/backdrop/backdrop.py b/kivymd/uix/backdrop/backdrop.py
deleted file mode 100644
index 276eb1c78..000000000
--- a/kivymd/uix/backdrop/backdrop.py
+++ /dev/null
@@ -1,548 +0,0 @@
-"""
-Components/Backdrop
-===================
-
-.. seealso::
-
- `Material Design spec, Backdrop `_
-
-.. rubric:: Skeleton layout for using :class:`~MDBackdrop`:
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.png
- :align: center
-
-Usage
------
-
-.. code-block:: kv
-
-
-
- MDBackdrop:
-
- MDBackdropBackLayer:
-
- ContentForBackdropBackLayer:
-
- MDBackdropFrontLayer:
-
- ContentForBackdropFrontLayer:
-
-Example
--------
-
-.. tabs::
-
- .. tab:: Declarative KV styles
-
- .. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.uix.screen import MDScreen
- from kivymd.app import MDApp
-
- # Your layouts.
- Builder.load_string(
- '''
- #:import os os
- #:import Window kivy.core.window.Window
- #:import IconLeftWidget kivymd.uix.list.IconLeftWidget
- #:import images_path kivymd.images_path
-
-
-
- icon: "android"
-
- IconLeftWidget:
- icon: root.icon
-
-
-
- backdrop: None
- text: "Lower the front layer"
- secondary_text: " by 50 %"
- icon: "transfer-down"
- on_press: root.backdrop.open(-Window.height / 2)
- pos_hint: {"top": 1}
- _no_ripple_effect: True
-
-
-
- size_hint: .8, .8
- source: os.path.join(images_path, "logo", "kivymd-icon-512.png")
- pos_hint: {"center_x": .5, "center_y": .6}
- '''
- )
-
- # Usage example of MDBackdrop.
- Builder.load_string(
- '''
-
-
- MDBackdrop:
- id: backdrop
- left_action_items: [['menu', lambda x: self.open()]]
- title: "Example Backdrop"
- radius_left: "25dp"
- radius_right: "0dp"
- header_text: "Menu:"
-
- MDBackdropBackLayer:
- MyBackdropBackLayer:
- id: backlayer
-
- MDBackdropFrontLayer:
- MyBackdropFrontLayer:
- backdrop: backdrop
- '''
- )
-
-
- class ExampleBackdrop(MDScreen):
- pass
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return ExampleBackdrop()
-
-
- Example().run()
-
- .. tab:: Declarative python styles
-
- .. code-block:: python
-
- import os
-
- from kivy.core.window import Window
- from kivy.uix.image import Image
-
- from kivymd import images_path
- from kivymd.uix.backdrop import MDBackdrop
- from kivymd.uix.backdrop.backdrop import (
- MDBackdropBackLayer, MDBackdropFrontLayer
- )
- from kivymd.uix.list import TwoLineAvatarListItem, IconLeftWidget
- from kivymd.uix.screen import MDScreen
- from kivymd.app import MDApp
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
-
- return (
- MDScreen(
- MDBackdrop(
- MDBackdropBackLayer(
- Image(
- size_hint=(0.8, 0.8),
- source=os.path.join(images_path, "logo", "kivymd-icon-512.png"),
- pos_hint={"center_x": 0.5, "center_y": 0.6},
- )
- ),
- MDBackdropFrontLayer(
- TwoLineAvatarListItem(
- IconLeftWidget(icon="transfer-down"),
- text="Lower the front layer",
- secondary_text=" by 50 %",
- on_press=self.backdrop_open_by_50_percent,
- pos_hint={"top": 1},
- _no_ripple_effect=True,
- ),
- ),
- id="backdrop",
- title="Example Backdrop",
- radius_left="25dp",
- radius_right="0dp",
- header_text="Menu:",
- )
- )
- )
-
- def backdrop_open_by_50_percent(self, *args):
- self.root.ids.backdrop.open(-Window.height / 2)
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.gif
- :align: center
-
-.. Note:: `See full example `_
-"""
-
-__all__ = (
- "MDBackdropToolbar",
- "MDBackdropFrontLayer",
- "MDBackdropBackLayer",
- "MDBackdrop",
-)
-
-import os
-from typing import Union
-
-from kivy.animation import Animation
-from kivy.clock import Clock
-from kivy.lang import Builder
-from kivy.properties import (
- BooleanProperty,
- ColorProperty,
- ListProperty,
- NumericProperty,
- OptionProperty,
- StringProperty,
-)
-from kivy.uix.boxlayout import BoxLayout
-
-from kivymd import uix_path
-from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.card import MDCard
-from kivymd.uix.floatlayout import MDFloatLayout
-from kivymd.uix.toolbar.toolbar import ActionTopAppBarButton, MDTopAppBar
-
-with open(
- os.path.join(uix_path, "backdrop", "backdrop.kv"),
- encoding="utf-8",
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-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.
- :attr:`on_close`
- When the front layer rises.
- """
-
- anchor_title = OptionProperty("left", options=["left", "center", "right"])
- """
- Position toolbar title. Only used with `material_style = 'M3'`
- Available options are: `'left'`, `'center'`, `'right'`.
-
- .. versionadded:: 1.0.0
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-anchor-title.png
- :align: center
-
- :attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'left'`.
- """
-
- padding = ListProperty([0, 0, 0, 0])
- """
- Padding for contents of the front layer.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-padding.png
- :align: center
-
- :attr:`padding` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0, 0, 0]`.
- """
-
- left_action_items = ListProperty()
- """
- The icons and methods left of the :class:`kivymd.uix.toolbar.MDTopAppBar`
- in back layer. For more information, see the
- :class:`kivymd.uix.toolbar.MDTopAppBar` module
- and :attr:`left_action_items` parameter.
-
- :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
-
- right_action_items = ListProperty()
- """
- Works the same way as :attr:`left_action_items`.
-
- :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
-
- title = StringProperty()
- """
- See the :class:`kivymd.uix.toolbar.MDTopAppBar.title` parameter.
-
- :attr:`title` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- back_layer_color = ColorProperty(None)
- """
- 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
-
- :attr:`back_layer_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- front_layer_color = ColorProperty(None)
- """
- 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
-
- :attr:`front_layer_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- radius_left = NumericProperty("16dp")
- """
- The value of the rounding radius of the upper left corner
- of the front layer.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-radius-left.png
- :align: center
-
- :attr:`radius_left` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `16dp`.
- """
-
- radius_right = NumericProperty("16dp")
- """
- The value of the rounding radius of the upper right corner
- of the front layer.
-
- :attr:`radius_right` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `16dp`.
- """
-
- header = BooleanProperty(True)
- """
- Whether to use a header above the contents of the front layer.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header.png
- :align: center
-
- :attr:`header` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- header_text = StringProperty("Header")
- """
- Text of header.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header-text.png
- :align: center
-
- :attr:`header_text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'Header'`.
- """
-
- close_icon = StringProperty("close")
- """
- The name of the icon that will be installed on the toolbar
- on the left when opening the front layer.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-close-icon.png
- :align: center
-
- :attr:`close_icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'close'`.
- """
-
- opening_time = NumericProperty(0.2)
- """
- The time taken for the panel to slide to the :attr:`state` `'open'`.
-
- .. versionadded:: 1.0.0
-
- :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- opening_transition = StringProperty("out_quad")
- """
- The name of the animation transition type to use when animating to
- the :attr:`state` `'open'`.
-
- .. versionadded:: 1.0.0
-
- :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'out_quad'`.
- """
-
- closing_time = NumericProperty(0.2)
- """
- The time taken for the panel to slide to the :attr:`state` `'close'`.
-
- .. versionadded:: 1.0.0
-
- :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- closing_transition = StringProperty("out_quad")
- """
- The name of the animation transition type to use when animating to
- the :attr:`state` 'close'.
-
- .. versionadded:: 1.0.0
-
- :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'out_quad'`.
- """
-
- _open_icon = ""
- _front_layer_open = False
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.register_event_type("on_open")
- self.register_event_type("on_close")
- Clock.schedule_once(
- lambda x: self.on_left_action_items(self, self.left_action_items)
- )
-
- def on_open(self) -> None:
- """When the front layer drops."""
-
- def on_close(self) -> None:
- """When the front layer rises."""
-
- def on_left_action_items(self, instance_backdrop, menu: list) -> None:
- if menu:
- self.left_action_items = [menu[0]]
- else:
- self.left_action_items = [["menu", lambda x: self.open()]]
- self._open_icon = self.left_action_items[0][0]
-
- def on_header(self, instance_backdrop, value: bool) -> None:
- def on_header(*args):
- if not value:
- self.ids._front_layer.remove_widget(self.ids.header_button)
-
- Clock.schedule_once(on_header)
-
- def open(self, open_up_to: int = 0) -> None:
- """
- Opens the front layer.
-
- :open_up_to:
- the height to which the front screen will be lowered;
- if equal to zero - falls to the bottom of the screen;
- """
-
- self.animate_opacity_icon()
- if self._front_layer_open:
- self.close()
- return
-
- if open_up_to:
- if open_up_to < (
- self.ids.header_button.height - self.ids._front_layer.height
- ):
- y = self.ids.header_button.height - self.ids._front_layer.height
- elif open_up_to > 0:
- y = 0
- else:
- y = open_up_to
- else:
- y = self.ids.header_button.height - self.ids._front_layer.height
-
- Animation(y=y, d=self.opening_time, t=self.opening_transition).start(
- self.ids._front_layer
- )
- self._front_layer_open = True
- self.dispatch("on_open")
-
- def close(self) -> None:
- """Opens the front layer."""
-
- Animation(y=0, d=self.closing_time, t=self.closing_transition).start(
- self.ids._front_layer
- )
- self._front_layer_open = False
- self.dispatch("on_close")
-
- def animate_opacity_icon(
- self,
- instance_icon_menu: Union[ActionTopAppBarButton, None] = None,
- opacity_value: int = 0,
- call_set_new_icon: bool = True,
- ) -> None:
- """Starts the opacity animation of the icon."""
-
- if not instance_icon_menu:
- instance_icon_menu = self.ids.toolbar.ids.left_actions.children[0]
- anim = Animation(
- opacity=opacity_value,
- d=self.opening_time,
- t=self.opening_transition,
- )
- if call_set_new_icon:
- anim.bind(on_complete=self.set_new_icon)
- anim.start(instance_icon_menu)
-
- def set_new_icon(
- self,
- instance_animation: Animation,
- instance_icon_menu: ActionTopAppBarButton,
- ) -> None:
- """
- Sets the icon of the button depending on the state of the backdrop.
- """
-
- instance_icon_menu.icon = (
- self.close_icon
- if instance_icon_menu.icon == self._open_icon
- else self._open_icon
- )
- self.animate_opacity_icon(instance_icon_menu, 1, False)
-
- def add_widget(self, widget, index=0, canvas=None):
- if widget.__class__ in (MDBackdropToolbar, _BackLayer, _FrontLayer):
- return super().add_widget(widget)
- else:
- if widget.__class__ is MDBackdropBackLayer:
- self.ids.back_layer.add_widget(widget)
- elif widget.__class__ is MDBackdropFrontLayer:
- self.ids.front_layer.add_widget(widget)
-
-
-class MDBackdropToolbar(MDTopAppBar):
- """
- 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.
-
- For more information, see in the
- :class:`~kivymd.uix.boxlayout.MDBoxLayout` classes documentation.
- """
-
-
-class MDBackdropBackLayer(MDBoxLayout):
- """
- Container for back content.
-
- For more information, see in the
- :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
- """
-
-
-class _BackLayer(BoxLayout):
- pass
-
-
-class _FrontLayer(MDCard):
- pass
diff --git a/kivymd/uix/badge/__init__.py b/kivymd/uix/badge/__init__.py
new file mode 100644
index 000000000..043d8e1a4
--- /dev/null
+++ b/kivymd/uix/badge/__init__.py
@@ -0,0 +1 @@
+from .badge import MDBadge # NOQA F401
diff --git a/kivymd/uix/badge/badge.kv b/kivymd/uix/badge/badge.kv
new file mode 100644
index 000000000..ac085574d
--- /dev/null
+++ b/kivymd/uix/badge/badge.kv
@@ -0,0 +1,17 @@
+
+ font_style: "Label"
+ role: "small"
+ radius: [self.texture_size[1] / 2, ]
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+ padding: "4dp", "2dp"
+ halign: "center"
+ valign: "center"
+ adaptive_size: True
+ md_bg_color: self.theme_cls.errorColor
+ text_color: self.theme_cls.onErrorColor
+ pos:
+ ( \
+ self.parent.x + (self.parent.width / 2), \
+ self.parent.y + (self.parent.height / 2) \
+ ) \
+ if self.parent else (0, 0)
diff --git a/kivymd/uix/badge/badge.py b/kivymd/uix/badge/badge.py
new file mode 100644
index 000000000..8134046c7
--- /dev/null
+++ b/kivymd/uix/badge/badge.py
@@ -0,0 +1,74 @@
+"""
+Components/Badge
+================
+
+.. versionadded:: 2.0.0
+
+
+.. seealso::
+
+ `Material Design 3 spec, Badge `_
+
+.. rubric:: Badges show notifications, counts, or status information on
+ navigation items and icons.
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/badges.png
+ :align: center
+
+Example
+-------
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDIcon:
+ icon: "gmail"
+ pos_hint: {'center_x': .5, 'center_y': .5}
+
+ MDBadge:
+ text: "12"
+ '''
+
+
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/badges-example.png
+ :align: center
+"""
+
+__all__ = ("MDBadge",)
+
+import os
+
+from kivy.lang import Builder
+
+from kivymd.uix.label import MDLabel
+from kivymd import uix_path
+
+with open(
+ os.path.join(uix_path, "badge", "badge.kv"), encoding="utf-8"
+) as kv_file:
+ Builder.load_string(kv_file.read())
+
+
+class MDBadge(MDLabel):
+ """
+ Badge class.
+
+ .. versionadded:: 2.0.0
+
+ For more information see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
diff --git a/kivymd/uix/banner/__init__.py b/kivymd/uix/banner/__init__.py
deleted file mode 100644
index 3ed33d09d..000000000
--- a/kivymd/uix/banner/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .banner import MDBanner # NOQA F401
diff --git a/kivymd/uix/banner/banner.kv b/kivymd/uix/banner/banner.kv
deleted file mode 100644
index 3d874ce6d..000000000
--- a/kivymd/uix/banner/banner.kv
+++ /dev/null
@@ -1,85 +0,0 @@
-#:import Window kivy.core.window.Window
-
-
-
- text: root.text_message[0]
- secondary_text: root.text_message[1]
- tertiary_text: root.text_message[2]
- divider: None
- _no_ripple_effect: True
-
- ImageLeftWidget:
- source: root.icon
-
-
-
- text: root.text_message[0]
- secondary_text: root.text_message[1]
- divider: None
- _no_ripple_effect: True
-
- ImageLeftWidget:
- source: root.icon
-
-
-
- text: root.text_message[0]
- divider: None
- _no_ripple_effect: True
-
- ImageLeftWidget:
- source: root.icon
-
-
-
- text: root.text_message[0]
- secondary_text: root.text_message[1]
- tertiary_text: root.text_message[2]
- divider: None
- _no_ripple_effect: True
-
-
-
- text: root.text_message[0]
- secondary_text: root.text_message[1]
- divider: None
- _no_ripple_effect: True
-
-
-
- text: root.text_message[0]
- divider: None
- _no_ripple_effect: True
-
-
-
- size_hint_y: None
- height: self.minimum_height
- banner_y: 0
- orientation: "vertical"
- y: Window.height - self.banner_y
-
- canvas:
- Color:
- rgba: 0, 0, 0, 0
- Rectangle:
- pos: self.pos
- size: self.size
-
- MDBoxLayout:
- id: container_message
- adaptive_height: True
-
- MDBoxLayout:
- adaptive_size: True
- pos_hint: {"right": 1}
- padding: 0, 0, "8dp", "8dp"
- spacing: "8dp"
-
- MDBoxLayout:
- id: left_action_box
- adaptive_size: True
-
- MDBoxLayout:
- id: right_action_box
- adaptive_size: True
diff --git a/kivymd/uix/banner/banner.py b/kivymd/uix/banner/banner.py
deleted file mode 100644
index 534783c90..000000000
--- a/kivymd/uix/banner/banner.py
+++ /dev/null
@@ -1,439 +0,0 @@
-"""
-Components/Banner
-=================
-
-.. seealso::
-
- `Material Design spec, Banner `_
-
-.. rubric:: A banner displays a prominent message and related optional actions.
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner.png
- :align: center
-
-Usage
-=====
-
-.. code-block:: python
-
- from kivy.lang import Builder
- from kivy.factory import Factory
-
- from kivymd.app import MDApp
-
- Builder.load_string('''
-
-
- MDBanner:
- id: banner
- text: ["One line string text example without actions."]
- # The widget that is under the banner.
- # It will be shifted down to the height of the banner.
- over_widget: screen
- vertical_pad: toolbar.height
-
- MDTopAppBar:
- id: toolbar
- title: "Example Banners"
- elevation: 4
- pos_hint: {'top': 1}
-
- MDBoxLayout:
- id: screen
- orientation: "vertical"
- size_hint_y: None
- height: Window.height - toolbar.height
-
- OneLineListItem:
- text: "Banner without actions"
- on_release: banner.show()
-
- Widget:
- ''')
-
-
- class Test(MDApp):
- def build(self):
- return Factory.ExampleBanner()
-
-
- Test().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-example-1.gif
- :align: center
-
-.. rubric:: Banner type.
-
-By default, the banner is of the type ``'one-line'``:
-
-.. code-block:: kv
-
- MDBanner:
- text: ["One line string text example without actions."]
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-one-line.png
- :align: center
-
-To use a two-line banner, specify the ``'two-line'`` :attr:`MDBanner.type` for the banner
-and pass the list of two lines to the :attr:`MDBanner.text` parameter:
-
-.. code-block:: kv
-
- MDBanner:
- type: "two-line"
- text:
- ["One line string text example without actions.", "This is the second line of the banner message."]
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-two-line.png
- :align: center
-
-Similarly, create a three-line banner:
-
-.. code-block:: kv
-
- MDBanner:
- type: "three-line"
- text:
- ["One line string text example without actions.", "This is the second line of the banner message.", "and this is the third line of the banner message."]
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-three-line.png
- :align: center
-
-To add buttons to any type of banner,
-use the :attr:`MDBanner.left_action` and :attr:`MDBanner.right_action` parameters,
-which should take a list ['Button name', function]:
-
-.. code-block:: kv
-
- MDBanner:
- text: ["One line string text example without actions."]
- left_action: ["CANCEL", lambda x: None]
-
-Or two buttons:
-
-.. code-block:: kv
-
- MDBanner:
- text: ["One line string text example without actions."]
- left_action: ["CANCEL", lambda x: None]
- right_action: ["CLOSE", lambda x: None]
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-actions.png
- :align: center
-
-If you want to use the icon on the left in the banner,
-add the prefix `'-icon'` to the banner type:
-
-.. code-block:: kv
-
- MDBanner:
- type: "one-line-icon"
- icon: f"{images_path}/kivymd.png"
- text: ["One line string text example without actions."]
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-icon.png
- :align: center
-
-.. Note:: `See full example `_
-"""
-
-__all__ = ("MDBanner",)
-
-import os
-from typing import Union
-
-from kivy.animation import Animation
-from kivy.clock import Clock
-from kivy.lang import Builder
-from kivy.metrics import dp
-from kivy.properties import (
- BoundedNumericProperty,
- ListProperty,
- NumericProperty,
- ObjectProperty,
- OptionProperty,
- StringProperty,
-)
-from kivy.uix.widget import Widget
-
-from kivymd import uix_path
-from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.button import MDFlatButton
-from kivymd.uix.card import MDCard
-from kivymd.uix.list import (
- OneLineAvatarListItem,
- OneLineListItem,
- ThreeLineAvatarListItem,
- ThreeLineListItem,
- TwoLineAvatarListItem,
- TwoLineListItem,
-)
-
-with open(
- os.path.join(uix_path, "banner", "banner.kv"),
- encoding="utf-8",
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-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.
-
- :attr:`vertical_pad` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `dp(68)`.
- """
-
- opening_transition = StringProperty("in_quad")
- """
- The name of the animation transition.
-
- :attr:`opening_transition` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'in_quad'`.
- """
-
- icon = StringProperty("data/logo/kivy-icon-128.png")
- """
- Icon banner.
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'data/logo/kivy-icon-128.png'`.
- """
-
- over_widget = ObjectProperty()
- """
- The widget that is under the banner.
- It will be shifted down to the height of the banner.
-
- :attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
- """
-
- text = ListProperty()
- """
- List of lines for banner text.
- Must contain no more than three lines for a
- `'one-line'`, `'two-line'` and `'three-line'` banner, respectively.
-
- :attr:`text` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
-
- left_action = ListProperty()
- """
- The action of banner.
-
- To add one action, make a list [`'name_action'`, callback]
- where `'name_action'` is a string that corresponds to an action name and
- ``callback`` is the function called on a touch release event.
-
- :attr:`left_action` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
-
- right_action = ListProperty()
- """
- Works the same way as :attr:`left_action`.
-
- :attr:`right_action` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
-
- type = OptionProperty(
- "one-line",
- options=[
- "one-line",
- "two-line",
- "three-line",
- "one-line-icon",
- "two-line-icon",
- "three-line-icon",
- ],
- allownone=True,
- )
- """
- Banner type. . Available options are: (`"one-line"`, `"two-line"`,
- `"three-line"`, `"one-line-icon"`, `"two-line-icon"`, `"three-line-icon"`).
-
- :attr:`type` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'one-line'`.
- """
-
- opening_timeout = BoundedNumericProperty(0.7, min=0.7)
- """
- Time interval after which the banner will be shown.
-
- .. versionadded:: 1.0.0
-
- :attr:`opening_timeout` is an :class:`~kivy.properties.BoundedNumericProperty`
- and defaults to `0.7`.
- """
-
- opening_time = NumericProperty(0.15)
- """
- The time taken for the banner to slide to the :attr:`state` `'open'`.
-
- .. versionadded:: 1.0.0
-
- :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.15`.
- """
-
- closing_time = NumericProperty(0.15)
- """
- The time taken for the banner to slide to the :attr:`state` `'close'`.
-
- .. versionadded:: 1.0.0
-
- :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.15`.
- """
-
- _type_message = None
- _progress = False
-
- def add_actions_buttons(
- self, instance_box: MDBoxLayout, data: list
- ) -> None:
- """
- Adds buttons to the banner.
-
- :param data: ['NAME BUTTON', ];
- """
-
- if data:
- name_action_button, function_action_button = data
- action_button = MDFlatButton(
- text=f"[b]{name_action_button}[/b]",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- on_release=function_action_button,
- )
- action_button.markup = True
- instance_box.add_widget(action_button)
-
- def show(self) -> None:
- """Displays a banner on the screen."""
-
- def show(interval: Union[int, float]):
- self.set_type_banner()
- self.add_actions_buttons(self.ids.left_action_box, self.left_action)
- self.add_actions_buttons(
- self.ids.right_action_box, self.right_action
- )
- self._add_banner_to_container()
- Clock.schedule_once(self.animation_display_banner, 0.1)
-
- if not self._progress:
- self._progress = True
- if self.ids.container_message.children:
- self.hide()
- Clock.schedule_once(show, self.opening_timeout)
-
- def hide(self) -> None:
- """Hides the banner from the screen."""
-
- def hide(interval: Union[int, float]):
- anim = Animation(banner_y=0, d=self.closing_time)
- anim.bind(on_complete=self._remove_banner)
- anim.start(self)
- Animation(
- y=self.over_widget.y + self.height, d=self.closing_time
- ).start(self.over_widget)
-
- if not self._progress:
- self._progress = True
- Clock.schedule_once(hide, 0.5)
-
- def set_type_banner(self) -> None:
- self._type_message = {
- "three-line-icon": ThreeLineIconBanner,
- "two-line-icon": TwoLineIconBanner,
- "one-line-icon": OneLineIconBanner,
- "three-line": ThreeLineBanner,
- "two-line": TwoLineBanner,
- "one-line": OneLineBanner,
- }[self.type]
-
- def animation_display_banner(self, interval: Union[int, float]) -> None:
- Animation(
- banner_y=self.height + self.vertical_pad,
- d=self.opening_time,
- t=self.opening_transition,
- ).start(self)
- anim = Animation(
- y=self.over_widget.y - self.height,
- d=self.opening_time,
- t=self.opening_transition,
- )
- anim.bind(on_complete=self._reset_progress)
- anim.start(self.over_widget)
-
- def _remove_banner(self, *args):
- self.ids.container_message.clear_widgets()
- self.ids.left_action_box.clear_widgets()
- self.ids.right_action_box.clear_widgets()
- self._reset_progress()
-
- def _reset_progress(self, *args):
- self._progress = False
-
- def _add_banner_to_container(self) -> None:
- self.ids.container_message.add_widget(
- self._type_message(text_message=self.text, icon=self.icon)
- )
-
-
-class BaseBanner(Widget):
- """Implements the base banner class."""
-
- text_message = ListProperty(["", "", ""])
- """
- List of banner strings. First, second and, respectively, third lines.
-
- :attr:`text_message` is an :class:`~kivy.properties.ListProperty`
- and defaults to `['', '', '']`.
- """
-
- icon = StringProperty()
- """
- Icon banner.
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- def on_touch_down(self, touch):
- self.parent.parent.hide()
-
-
-class ThreeLineIconBanner(ThreeLineAvatarListItem, BaseBanner):
- pass
-
-
-class TwoLineIconBanner(TwoLineAvatarListItem, BaseBanner):
- pass
-
-
-class OneLineIconBanner(OneLineAvatarListItem, BaseBanner):
- pass
-
-
-class ThreeLineBanner(ThreeLineListItem, BaseBanner):
- pass
-
-
-class TwoLineBanner(TwoLineListItem, BaseBanner):
- pass
-
-
-class OneLineBanner(OneLineListItem, BaseBanner):
- pass
diff --git a/kivymd/uix/behaviors/__init__.py b/kivymd/uix/behaviors/__init__.py
index d80d8c180..5d26fefc7 100755
--- a/kivymd/uix/behaviors/__init__.py
+++ b/kivymd/uix/behaviors/__init__.py
@@ -5,21 +5,11 @@
Modules and classes implementing various behaviors for buttons etc.
"""
-from .backgroundcolor_behavior import (
- BackgroundColorBehavior,
- SpecificBackgroundColorBehavior,
-)
+from .backgroundcolor_behavior import BackgroundColorBehavior
# flake8: NOQA
from .declarative_behavior import DeclarativeBehavior
-from .elevation import (
- CircularElevationBehavior,
- CommonElevationBehavior,
- FakeCircularElevationBehavior,
- FakeRectangularElevationBehavior,
- RectangularElevationBehavior,
- RoundedRectangularElevationBehavior,
-)
+from .elevation import CommonElevationBehavior
from .motion_behavior import (
MotionDialogBehavior,
MotionShackBehavior,
diff --git a/kivymd/uix/behaviors/backgroundcolor_behavior.py b/kivymd/uix/behaviors/backgroundcolor_behavior.py
index a24166d68..9e4167e83 100755
--- a/kivymd/uix/behaviors/backgroundcolor_behavior.py
+++ b/kivymd/uix/behaviors/backgroundcolor_behavior.py
@@ -7,7 +7,7 @@
from __future__ import annotations
-__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior")
+__all__ = ("BackgroundColorBehavior",)
from kivy.animation import Animation
from kivy.lang import Builder
@@ -15,15 +15,10 @@
ColorProperty,
ListProperty,
NumericProperty,
- OptionProperty,
ReferenceListProperty,
StringProperty,
VariableListProperty,
)
-from kivy.utils import get_color_from_hex
-
-from kivymd.color_definitions import hue, palette, text_colors
-from kivymd.theming import ThemeManager
Builder.load_string(
"""
@@ -49,11 +44,17 @@
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:
+ SmoothLine:
width: root.line_width
rounded_rectangle:
+ [ \
+ 0,
+ 0, \
+ self.width, \
+ self.height, \
+ *self.radius, \
+ ] \
+ if isinstance(self, RelativeLayout) else \
[ \
self.x,
self.y, \
@@ -73,7 +74,7 @@ class BackgroundColorBehavior:
Background image path.
:attr:`background` is a :class:`~kivy.properties.StringProperty`
- and defaults to `None`.
+ and defaults to `''`.
"""
radius = VariableListProperty([0], length=4)
@@ -93,7 +94,7 @@ class BackgroundColorBehavior:
# 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])
+ md_bg_color = ColorProperty([0, 0, 0, 0])
"""
The background color of the widget (:class:`~kivy.uix.widget.Widget`)
that will be inherited from the :attr:`BackgroundColorBehavior` class.
@@ -118,12 +119,12 @@ class BackgroundColorBehavior:
md_bg_color: 0, 1, 1, 1
:attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[1, 1, 1, 0]`.
+ and defaults to `[0, 0, 0, 0]`.
"""
line_color = ColorProperty([0, 0, 0, 0])
"""
- If a custom value is specified for the `line_color parameter`, the border
+ If a custom value is specified for the `line_color` parameter, the border
of the specified color will be used to border the widget:
.. code-block:: kv
@@ -157,37 +158,18 @@ 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,
- 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
+ self.bind(pos=self.update_background_origin)
- 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, color: list | str):
+ """Fired when the values of :attr:`md_bg_color` change."""
if (
hasattr(self, "theme_cls")
and self.theme_cls.theme_style_switch_animation
+ and self.__class__.__name__ != "MDDropdownMenu"
):
Animation(
_md_bg_color=color,
@@ -197,94 +179,10 @@ def on_md_bg_color(self, instance_md_widget, color: list | str):
else:
self._md_bg_color = color
- 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."""
+ def update_background_origin(self, instance, pos: list) -> None:
+ """Fired when the values of :attr:`pos` change."""
if self.background_origin:
self._background_origin = self.background_origin
else:
self._background_origin = self.center
-
-
-class SpecificBackgroundColorBehavior(BackgroundColorBehavior):
- background_palette = OptionProperty(
- "Primary", options=["Primary", "Accent", *palette]
- )
- """
- See :attr:`kivymd.color_definitions.palette`.
-
- :attr:`background_palette` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'Primary'`.
- """
-
- background_hue = OptionProperty("500", options=hue)
- """
- See :attr:`kivymd.color_definitions.hue`.
-
- :attr:`background_hue` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'500'`.
- """
-
- specific_text_color = ColorProperty([0, 0, 0, 0.87])
- """
- :attr:`specific_text_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0.87]`.
- """
-
- specific_secondary_text_color = ColorProperty([0, 0, 0, 0.87])
- """
- :attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0.87]`.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- if hasattr(self, "theme_cls"):
- self.theme_cls.bind(
- primary_palette=self._update_specific_text_color,
- accent_palette=self._update_specific_text_color,
- theme_style=self._update_specific_text_color,
- )
- self.bind(
- background_hue=self._update_specific_text_color,
- background_palette=self._update_specific_text_color,
- )
- self._update_specific_text_color(None, None)
-
- def _update_specific_text_color(
- self, instance_theme_manager: ThemeManager, theme_style: str
- ) -> None:
- if hasattr(self, "theme_cls"):
- palette = {
- "Primary": self.theme_cls.primary_palette,
- "Accent": self.theme_cls.accent_palette,
- }.get(self.background_palette, self.background_palette)
- else:
- palette = {"Primary": "Blue", "Accent": "Amber"}.get(
- self.background_palette, self.background_palette
- )
- color = get_color_from_hex(text_colors[palette][self.background_hue])
- secondary_color = color[:]
- # Check for black text (need to adjust opacity).
- if (color[0] + color[1] + color[2]) == 0:
- color[3] = 0.87
- secondary_color[3] = 0.54
- else:
- secondary_color[3] = 0.7
-
- if (
- hasattr(self, "theme_cls")
- and self.theme_cls.theme_style_switch_animation
- ):
- Animation(
- specific_text_color=color,
- specific_secondary_text_color=secondary_color,
- d=self.theme_cls.theme_style_switch_animation_duration,
- t="linear",
- ).start(self)
- else:
- self.specific_text_color = color
- self.specific_secondary_text_color = secondary_color
diff --git a/kivymd/uix/behaviors/declarative_behavior.py b/kivymd/uix/behaviors/declarative_behavior.py
index 0fb5eb782..087d95ac5 100644
--- a/kivymd/uix/behaviors/declarative_behavior.py
+++ b/kivymd/uix/behaviors/declarative_behavior.py
@@ -293,6 +293,15 @@ def on_start(self):
from kivy.uix.widget import Widget
+class _Dict(dict):
+ """Implements access to dictionary values via a dot."""
+
+ def __getattr__(self, name):
+ return self[name]
+
+
+# TODO: Add cleaning of the `__ids` collection when removing child widgets
+# from the parent.
class DeclarativeBehavior:
"""
Implements the creation and addition of child widgets as declarative
@@ -307,6 +316,8 @@ class DeclarativeBehavior:
and defaults to `''`.
"""
+ __ids = _Dict()
+
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
@@ -314,4 +325,12 @@ def __init__(self, *args, **kwargs):
if issubclass(child.__class__, Widget):
self.add_widget(child)
if hasattr(child, "id") and child.id:
- self.ids[child.id] = child
+ self.__ids[child.id] = child
+
+ def get_ids(self) -> dict:
+ """
+ Returns a dictionary of widget IDs defined in Python
+ code that is written in a declarative style.
+ """
+
+ return self.__ids
diff --git a/kivymd/uix/behaviors/elevation.py b/kivymd/uix/behaviors/elevation.py
index f081d5c13..12dafc4e2 100755
--- a/kivymd/uix/behaviors/elevation.py
+++ b/kivymd/uix/behaviors/elevation.py
@@ -31,7 +31,7 @@
)
KV = '''
-
+
size_hint: None, None
size: "250dp", "50dp"
@@ -39,19 +39,19 @@
MDScreen:
# With elevation effect
- RectangularElevationButton:
+ ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 4
shadow_offset: 0, -6
shadow_softness: 4
# Without elevation effect
- RectangularElevationButton:
+ ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .4}
'''
- class RectangularElevationButton(
+ class ElevationWidget(
RectangularRippleBehavior,
CommonElevationBehavior,
ButtonBehavior,
@@ -84,7 +84,7 @@ def build(self):
from kivymd.uix.screen import MDScreen
- class RectangularElevationButton(
+ class ElevationWidget(
RectangularRippleBehavior,
CommonElevationBehavior,
ButtonBehavior,
@@ -101,13 +101,13 @@ class Example(MDApp):
def build(self):
return (
MDScreen(
- RectangularElevationButton(
+ ElevationWidget(
pos_hint={"center_x": .5, "center_y": .6},
elevation=4,
shadow_softness=4,
shadow_offset=(0, -6),
),
- RectangularElevationButton(
+ ElevationWidget(
pos_hint={"center_x": .5, "center_y": .4},
),
)
@@ -271,7 +271,7 @@ def build(self):
size: 100, 100
md_bg_color: 0, 0, 1, 1
elevation: 2
- radius: 18
+ radius: dp(18)
'''
@@ -306,6 +306,7 @@ def build(self):
from kivy.animation import Animation
from kivy.uix.behaviors import ButtonBehavior
+ from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior
@@ -341,7 +342,7 @@ def build(self):
size=(100, 100),
md_bg_color="blue",
elevation=2,
- radius=18,
+ radius=dp(18),
)
)
)
@@ -355,23 +356,17 @@ def build(self):
from __future__ import annotations
-__all__ = (
- "CommonElevationBehavior",
- "RectangularElevationBehavior",
- "CircularElevationBehavior",
- "RoundedRectangularElevationBehavior",
- "FakeRectangularElevationBehavior",
- "FakeCircularElevationBehavior",
-)
+__all__ = ("CommonElevationBehavior",)
-from kivy import Logger
from kivy.lang import Builder
+from kivy.metrics import dp
from kivy.properties import (
BoundedNumericProperty,
ColorProperty,
ListProperty,
NumericProperty,
VariableListProperty,
+ DictProperty,
)
from kivy.uix.widget import Widget
@@ -393,18 +388,16 @@ def build(self):
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
+ rgba: root.shadow_color
BoxShadow:
- pos: self.pos
+ pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0)
size: self.size
offset: root.shadow_offset
spread_radius: -(root.shadow_softness), -(root.shadow_softness)
- blur_radius: root.elevation * 10
+ blur_radius: root.elevation_levels[root.elevation_level] * 2.5
+ # blur_radius: root.elevation * 10
border_radius:
- (root.radius if hasattr(self, "radius") else [0, 0, 0, 0]) \
+ (root.radius if hasattr(self, "radius") and root.radius else [0, 0, 0, 0]) \
if root.shadow_radius == [0.0, 0.0, 0.0, 0.0] else \
root.shadow_radius
canvas.after:
@@ -421,6 +414,29 @@ class CommonElevationBehavior(Widget):
class documentation.
"""
+ elevation_level = BoundedNumericProperty(0, min=0, max=5)
+ """
+ Elevation level (values from 0 to 5)
+
+ .. versionadded:: 1.2.0
+
+ :attr:`elevation_level` is an :class:`~kivy.properties.BoundedNumericProperty`
+ and defaults to `0`.
+ """
+
+ elevation_levels = DictProperty(
+ {0: 0, 1: dp(1.5), 2: dp(3), 3: dp(6), 4: dp(8), 5: dp(12)}
+ )
+ """
+ Elevation is measured as the distance between components along the z-axis
+ in density-independent pixels (dps).
+
+ .. versionadded:: 1.2.0
+
+ :attr:`elevation_levels` is an :class:`~kivy.properties.DictProperty`
+ and defaults to `{0: dp(0), 1: dp(1), 2: dp(3), 3: dp(6), 4: dp(8), 5: dp(12)}`.
+ """
+
elevation = BoundedNumericProperty(0, min=0, errorvalue=0)
"""
Elevation of the widget.
@@ -449,7 +465,7 @@ class documentation.
MDScreen:
MDCard:
- radius: 12, 46, 12, 46
+ radius: dp(12), dp(46), dp(12), dp(46)
size_hint: .5, .3
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 2
@@ -486,26 +502,26 @@ def build(self):
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
KV = '''
-
+
size_hint: None, None
size: "250dp", "50dp"
MDScreen:
- RectangularElevationButton:
+ ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .6}
elevation: 6
shadow_softness: 6
- RectangularElevationButton:
+ ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .4}
elevation: 6
shadow_softness: 12
'''
- class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
+ class ElevationWidget(CommonElevationBehavior, BackgroundColorBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = "blue"
@@ -522,19 +538,7 @@ def build(self):
:align: center
:attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `12`.
- """
-
- 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`.
+ and defaults to `0.0`.
"""
shadow_offset = ListProperty((0, 0))
@@ -551,23 +555,23 @@ def build(self):
from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior
KV = '''
-
+
size_hint: None, None
size: "100dp", "100dp"
MDScreen:
- RectangularElevationButton:
+ ElevationWidget:
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 6
- shadow_radius: 6
+ shadow_radius: dp(6)
shadow_softness: 12
shadow_offset: -12, -12
'''
- class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior):
+ class ElevationWidget(CommonElevationBehavior, BackgroundColorBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = "blue"
@@ -585,7 +589,7 @@ def build(self):
.. code-block:: kv
- RectangularElevationButton:
+ ElevationWidget:
shadow_offset: 12, -12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png
@@ -593,7 +597,7 @@ def build(self):
.. code-block:: kv
- RectangularElevationButton:
+ ElevationWidget:
shadow_offset: 12, 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png
@@ -601,7 +605,7 @@ def build(self):
.. code-block:: kv
- RectangularElevationButton:
+ ElevationWidget:
shadow_offset: -12, 12
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png
@@ -619,7 +623,7 @@ def build(self):
.. code-block:: python
- RectangularElevationButton:
+ ElevationWidget:
shadow_color: 0, 0, 1, .8
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png
@@ -691,82 +695,10 @@ def build(self):
and defaults to `(0, 0, 1)`.
"""
- _elevation = 0
-
- def on_elevation(self, instance, value) -> None:
- self._elevation = value
-
-
-class RectangularElevationBehavior(CommonElevationBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~CommonElevationBehavior` class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `RectangularElevationBehavior` class has been deprecated. "
- "Use the `CommonElevationBehavior` class instead.`"
- )
-
-
-class CircularElevationBehavior(CommonElevationBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~CommonElevationBehavior` class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `CircularElevationBehavior` class has been deprecated. "
- "Use the `CommonElevationBehavior` class instead.`"
- )
-
-
-class RoundedRectangularElevationBehavior(CommonElevationBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~CommonElevationBehavior` class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `RoundedRectangularElevationBehavior` class has been "
- "deprecated. Use the `CommonElevationBehavior` class instead.`"
- )
-
-
-class FakeRectangularElevationBehavior(CommonElevationBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~CommonElevationBehavior` class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `FakeRectangularElevationBehavior` class has been "
- "deprecated. Use the `CommonElevationBehavior` class instead."
- )
-
-
-class FakeCircularElevationBehavior(CommonElevationBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~CommonElevationBehavior` class instead.
- """
+ # _elevation = 0
+ _elevation_level = 0
+ _shadow_softness = 0
+ _shadow_color = (0, 0, 0, 0)
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `FakeCircularElevationBehavior` class has been deprecated. "
- "Use the `CommonElevationBehavior` class instead."
- )
+ # def on_elevation(self, instance, value) -> None:
+ # self._elevation = value
diff --git a/kivymd/uix/behaviors/focus_behavior.py b/kivymd/uix/behaviors/focus_behavior.py
index 33e00bdd4..d9ad37ed2 100644
--- a/kivymd/uix/behaviors/focus_behavior.py
+++ b/kivymd/uix/behaviors/focus_behavior.py
@@ -15,7 +15,7 @@
from kivy.lang import Builder
from kivymd.app import MDApp
- from kivymd.uix.behaviors import RectangularElevationBehavior
+ from kivymd.uix.behaviors import CommonElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
@@ -36,7 +36,7 @@
'''
- class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior):
+ class FocusWidget(MDBoxLayout, CommonElevationBehavior, FocusBehavior):
pass
@@ -65,25 +65,26 @@ def build(self):
__all__ = ("FocusBehavior",)
-from kivy.app import App
from kivy.properties import BooleanProperty, ColorProperty
-from kivy.uix.behaviors import ButtonBehavior
from kivymd.uix.behaviors import HoverBehavior
-class FocusBehavior(HoverBehavior, ButtonBehavior):
+class FocusBehavior(HoverBehavior):
"""
Focus behavior class.
- For more information, see in the :class:`~kivymd.uix.behavior.HoverBehavior`
- and :class:`~kivy.uix.button.ButtonBehavior` classes documentation.
+ 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
+ Fired 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
+ Fired when the mouse exits the widget AND the widget is visible.
"""
focus_behavior = BooleanProperty(True)
@@ -109,39 +110,3 @@ class FocusBehavior(HoverBehavior, ButtonBehavior):
:attr:`unfocus_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
-
- def on_enter(self):
- """Called when mouse enter the bbox of the widget."""
-
- if (
- hasattr(self, "md_bg_color") or hasattr(self, "bg_color")
- ) and self.focus_behavior:
- if hasattr(self, "theme_cls") and not self.focus_color:
- color = self.theme_cls.bg_normal
- else:
- if not self.focus_color:
- color = App.get_running_app().theme_cls.bg_normal
- else:
- color = self.focus_color
- self._set_bg_color(color)
-
- def on_leave(self):
- """Called when the mouse exit the widget."""
-
- if (
- hasattr(self, "md_bg_color") or hasattr(self, "bg_color")
- ) and self.focus_behavior:
- if hasattr(self, "theme_cls") and not self.unfocus_color:
- color = self.theme_cls.bg_light
- else:
- if not self.unfocus_color:
- color = App.get_running_app().theme_cls.bg_light
- else:
- color = self.unfocus_color
- self._set_bg_color(color)
-
- def _set_bg_color(self, color):
- if hasattr(self, "md_bg_color"):
- self.md_bg_color = color
- elif hasattr(self, "bg_color"):
- self.bg_color = color
diff --git a/kivymd/uix/behaviors/hover_behavior.py b/kivymd/uix/behaviors/hover_behavior.py
index dbe1ce2b1..113662924 100644
--- a/kivymd/uix/behaviors/hover_behavior.py
+++ b/kivymd/uix/behaviors/hover_behavior.py
@@ -85,23 +85,25 @@ def build(self):
from kivy.core.window import Window
from kivy.properties import BooleanProperty, ObjectProperty
+from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.widget import Widget
-class HoverBehavior(object):
+class HoverBehavior:
"""
:Events:
:attr:`on_enter`
- Called when mouse enters the bbox of the widget AND the widget is visible
+ Fired 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
+ Fired when the mouse exits the widget AND the widget is visible.
"""
hovering = BooleanProperty(False)
"""
`True`, if the mouse cursor is within the borders of the widget.
- Note that this is set and cleared even if the widget is not visible
+ Note that this is set and cleared even if the widget is not visible.
:attr:`hover` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
@@ -109,7 +111,7 @@ class HoverBehavior(object):
hover_visible = BooleanProperty(False)
"""
- `True` if hovering is True AND is the current widget is visible
+ `True` if hovering is True AND is the current widget is visible.
:attr:`hover_visible` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
@@ -118,7 +120,7 @@ class HoverBehavior(object):
enter_point = ObjectProperty(allownone=True)
"""
Holds the last position where the mouse pointer crossed into the Widget
- if the Widget is visible and is currently in a hovering state
+ if the Widget is visible and is currently in a hovering state.
:attr:`enter_point` is a :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
@@ -132,22 +134,24 @@ class HoverBehavior(object):
and defaults to `True`.
"""
- def __init__(self, **kwargs):
+ def __init__(self, *args, **kwargs):
self.register_event_type("on_enter")
self.register_event_type("on_leave")
Window.bind(mouse_pos=self.on_mouse_update)
- super(HoverBehavior, self).__init__(**kwargs)
+ super().__init__(*args, **kwargs)
def on_mouse_update(self, *args):
- # If the Widget currently has no parent, do nothing
+ # If the Widget currently has no parent, do nothing.
if not self.get_root_window():
return
pos = args[1]
- #
- # is the pointer in the same position as the widget?
- # If not - then issue an on_exit event if needed
- #
- if not self.collide_point(*self.to_widget(*pos)):
+ # Is the pointer in the same position as the widget?
+ # If not - then issue an on_exit event if needed.
+ if not self.collide_point(
+ *self.to_widget(*pos)
+ if not isinstance(self, RelativeLayout)
+ else (pos[0], pos[1])
+ ):
self.hovering = False
self.enter_point = None
if self.hover_visible:
@@ -155,33 +159,21 @@ def on_mouse_update(self, *args):
self.dispatch("on_leave")
return
- #
# The pointer is in the same position as the widget
- #
-
if self.hovering:
- #
- # nothing to do here. Not - this does not handle the case where
- # a popup comes over an existing hover event.
- # This seems reasonable
- #
+ # Nothing to do here. Not - this does not handle the case where
+ # a popup comes over an existing hover event.
+ # This seems reasonable
return
- #
# Otherwise - set the hovering attribute
- #
self.hovering = True
- #
- # We need to traverse the tree to see if the Widget is visible
- #
- # This is a two stage process:
- # - first go up the tree to the root Window.
- # At each stage - check that the Widget is actually visible
- # - Second - At the root Window check that there is not another branch
- # covering the Widget
- #
-
+ # We need to traverse the tree to see if the Widget is visible.
+ # This is a two stage process - first go up the tree to the root
+ # Window. At each stage - check that the Widget is actually visible.
+ # Second - at the root Window check that there is not another branch
+ # covering the Widget.
self.hover_visible = True
if self.detect_visible:
widget: Widget = self
@@ -190,37 +182,35 @@ def on_mouse_update(self, *args):
parent = widget.parent
try:
# See if the mouse point collides with the parent
- # using both local and glabal coordinates to cover absoluet and relative layouts
+ # using both local and global coordinates to cover absolute
+ # and relative layouts.
pinside = parent.collide_point(
*parent.to_widget(*pos)
) or parent.collide_point(*pos)
except Exception:
- # The collide_point will error when you reach the root Window
+ # The collide_point will error when you reach the root
+ # Window.
break
if not pinside:
self.hover_visible = False
break
- # Iterate upwards
+ # Iterate upwards.
widget = parent
- #
# parent = root window
# widget = first Widget on the current branch
- #
-
children = parent.children
for child in children:
- # For each top level widget - check if is current branch
+ # For each top level widget - check if is current branch.
# If it is - then break.
- # If not then - since we start at 0 - this widget is visible
- #
- # Check to see if it should take the hover
- #
+ # If not then - since we start at 0 - this widget is visible.
+ # Check to see if it should take the hover.
if child == widget:
- # this means that the current widget is visible
+ # This means that the current widget is visible.
break
if child.collide_point(*pos):
- # this means that the current widget is covered by a modal or popup
+ # This means that the current widget is covered by a modal
+ # or popup.
self.hover_visible = False
break
if self.hover_visible:
@@ -228,7 +218,7 @@ def on_mouse_update(self, *args):
self.dispatch("on_enter")
def on_enter(self):
- """Called when mouse enters the bbox of the widget AND the widget is visible."""
+ """Fired when mouse enter the bbox of the widget."""
def on_leave(self):
- """Called when the mouse exits the widget AND the widget is visible."""
+ """Fired when the mouse goes outside the widget border."""
diff --git a/kivymd/uix/behaviors/motion_behavior.py b/kivymd/uix/behaviors/motion_behavior.py
index 044970f43..d510cb4b2 100644
--- a/kivymd/uix/behaviors/motion_behavior.py
+++ b/kivymd/uix/behaviors/motion_behavior.py
@@ -23,7 +23,9 @@
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
-from kivy.properties import StringProperty, NumericProperty
+from kivy.metrics import dp
+from kivy.properties import StringProperty, NumericProperty, OptionProperty
+from kivymd.uix.behaviors.scale_behavior import ScaleBehavior
from kivymd.uix.behaviors.stencil_behavior import StencilBehavior
@@ -166,80 +168,217 @@ def on__scale_y(self, instance, value):
self.scale_value_y = value
-class MotionDialogBehavior(MotionBase):
+class MotionExtendedFabButtonBehavior(MotionBase):
"""
- Base class for dialog movement behavior.
+ Base class for extended Fab button movement behavior.
For more information, see in the :class:`~MotionBase` class documentation.
"""
- show_duration = NumericProperty(0.1)
+ show_transition = StringProperty("out_circ")
+ """
+ The type of transition of the widget opening.
+
+ :attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_circ'`.
+ """
+
+ shift_transition = StringProperty("out_sine")
+ """
+ Text label transition.
+
+ :attr:`shift_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_sine'`.
+ """
+
+ show_duration = NumericProperty(0.3)
"""
Duration of widget display transition.
:attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.1`.
+ and defaults to `0.3`.
"""
- scale_x = NumericProperty(1.5)
+ hide_transition = StringProperty("out_sine")
"""
- Default X-axis scaling values.
+ The type of transition of the widget closing.
- :attr:`scale_x` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `1.5`.
+ :attr:`hide_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'linear'`.
"""
- scale_y = NumericProperty(1.5)
+ hide_duration = NumericProperty(0.2)
"""
- Default Y-axis scaling values.
+ Duration of widget closing transition.
- :attr:`scale_y` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `1.5`.
+ :attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
"""
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.set_default_values()
+ _x = NumericProperty(0)
+ _anim_opacity = None
+
+ def collapse(self, *args) -> None:
+ """Collapses the button."""
+
+ def collapse(*args):
+ if self._label and self._icon:
+ Animation(_x=0, d=self.hide_duration).start(self)
+ anim = Animation(
+ width=dp(56), d=self.hide_duration, t=self.hide_transition
+ )
+ anim.bind(on_progress=self._check_collapse_progress)
+ anim.start(self)
+
+ Clock.schedule_once(collapse)
+
+ def expand(self, *args) -> None:
+ """Expands the button."""
+
+ def expand(*args):
+ if self._label and self._icon:
+ anim = Animation(
+ width=self.width
+ + self._label.texture_size[0]
+ + (dp(18) if self._icon else 0),
+ d=self.show_duration,
+ t=self.show_transition,
+ )
+ anim.bind(on_progress=self._check_expand_progress)
+ anim.start(self)
+ Animation(
+ _x=dp(12), d=self.show_duration, t=self.shift_transition
+ ).start(self)
+
+ Clock.schedule_once(expand)
+
+ def set_opacity_text_button(self, value: int) -> None:
+ if self._label:
+ self._anim_opacity = Animation(
+ opacity=value,
+ d=self.show_duration * 16.666666666666668 / 100
+ if value
+ else self.show_duration * 1.6666666666666667 / 100,
+ )
+ self._anim_opacity.bind(
+ on_complete=lambda *x: setattr(self, "_anim_opacity", None)
+ )
+ self._anim_opacity.start(self._label)
+
+ def _check_collapse_progress(self, animation, instance, progress) -> None:
+ if progress > 0.1:
+ if not self._anim_opacity:
+ self.set_opacity_text_button(0)
+
+ def _check_expand_progress(self, animation, instance, progress) -> None:
+ if progress > 0.3:
+ if not self._anim_opacity:
+ self.set_opacity_text_button(1)
+
+
+class MotionDialogBehavior(ScaleBehavior, MotionBase):
+ """
+ Base class for dialog movement behavior.
+
+ For more information, see in the :class:`~MotionBase` class documentation.
+ """
+
+ show_transition = StringProperty("out_expo")
+ """
+ The type of transition of the widget opening.
+
+ :attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_expo'`.
+ """
+
+ show_button_container_transition = StringProperty("out_circ")
+ """
+ The type of transition of the widget opening.
- def set_default_values(self):
- """Sets default scaled and transparency values."""
+ :attr:`show_button_container_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_circ'`.
+ """
- self.scale_value_x = self.scale_x
- self.scale_value_y = self.scale_y
- self.opacity = 0
+ hide_transition = StringProperty("out_circ")
+ """
+ The type of transition of the widget opening.
+
+ :attr:`show_transition` is a :class:`~kivy.properties.StringProperty`
+ and defaults to `'hide_transition'`.
+ """
+
+ show_duration = NumericProperty(0.4)
+ """
+ Duration of widget display transition.
+
+ :attr:`show_duration` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
+ """
def on_dismiss(self, *args):
- """Called when a dialog closed."""
+ """Fired when a dialog closed."""
- self.set_default_values()
+ def remove_dialog(*args):
+ Window.remove_widget(self)
+ if self._scrim:
+ Window.remove_widget(self._scrim)
- def on_open(self, *args):
- """Called when a dialog opened."""
+ if self._scrim:
+ Animation(alpha=0, d=self.hide_duration).start(self._scrim)
Animation(
- opacity=1,
- scale_value_x=1,
- scale_value_y=1,
- t=self.show_transition,
- d=self.show_duration,
- ).start(self)
+ y=self.ids.content_container.y,
+ t=self.hide_transition,
+ d=self.hide_duration,
+ ).start(self.ids.button_container)
+
+ anim = Animation(
+ opacity=0,
+ scale_value_y=0,
+ t=self.hide_transition,
+ d=self.hide_duration,
+ )
+ anim.bind(on_complete=remove_dialog)
+ anim.start(self)
+
+ def on_open(self, *args):
+ """Fired when a dialog opened."""
+ def open(*args):
+ self.scale_value_y = 0
+ self.scale_value_center = (0, self.center[1] + self.height / 2)
+ Animation(
+ opacity=1,
+ scale_value_y=1,
+ t=self.show_transition,
+ d=self.show_duration,
+ ).start(self)
+
+ Animation(
+ y=dp(24),
+ t=self.show_button_container_transition,
+ d=self.show_duration + 0.15,
+ ).start(self.ids.button_container)
-class MotionShackBehavior(StencilBehavior, MotionBase):
+ if self._scrim:
+ Animation(alpha=0.4, d=self.show_duration).start(self._scrim)
+
+ Clock.schedule_once(open)
+
+
+class MotionShackBehavior(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.
+ :class:`~MotionBase` class documentation.
"""
_interval = 0
_height = 0
def on_dismiss(self, *args):
- """Called when a snackbar closed."""
+ """Fired when a snackbar closed."""
def remove_snackbar(*args):
Window.parent.remove_widget(self)
@@ -257,7 +396,7 @@ def remove_snackbar(*args):
anim.start(self)
def on_open(self, *args):
- """Called when a snackbar opened."""
+ """Fired when a snackbar opened."""
def open(*args):
self._height = self.height
@@ -274,9 +413,9 @@ def open(*args):
)
)
anim.start(self)
+ self.dispatch("on_open")
- Clock.schedule_once(open)
- self.dispatch("on_open")
+ Clock.schedule_once(open, 0.2)
def _wait_interval(self, interval):
self._interval += interval
diff --git a/kivymd/uix/behaviors/ripple_behavior.py b/kivymd/uix/behaviors/ripple_behavior.py
index fddcef709..319d8e142 100644
--- a/kivymd/uix/behaviors/ripple_behavior.py
+++ b/kivymd/uix/behaviors/ripple_behavior.py
@@ -285,11 +285,12 @@ class CommonRipple:
and defaults to `'ripple_func_out'`.
"""
+ ripple_effect = BooleanProperty(True)
+
_ripple_rad = NumericProperty()
_doing_ripple = BooleanProperty(False)
_finishing_ripple = BooleanProperty(False)
_fading_out = BooleanProperty(False)
- _no_ripple_effect = BooleanProperty(False)
_round_rad = ListProperty([0, 0, 0, 0])
def lay_canvas_instructions(self) -> NoReturn:
@@ -333,6 +334,8 @@ def fade_out(self, *args) -> None:
anim.start(self)
def anim_complete(self, *args) -> None:
+ """Fired when the "fade_out" animation complete."""
+
self._doing_ripple = False
self._finishing_ripple = False
self._fading_out = False
@@ -378,7 +381,7 @@ def call_ripple_animation_methods(self, touch) -> None:
if self.ripple_color:
pass
elif hasattr(self, "theme_cls"):
- self.ripple_color = self.theme_cls.ripple_color
+ self.ripple_color = self.theme_cls.rippleColor
else:
# If no theme, set Gray 300.
self.ripple_color = [
@@ -429,7 +432,11 @@ class documentation.
"""
def lay_canvas_instructions(self) -> None:
- if self._no_ripple_effect:
+ """
+ Adds graphic instructions to the canvas to implement ripple animation.
+ """
+
+ if not self.ripple_effect:
return
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
@@ -493,7 +500,7 @@ class documentation.
"""
def lay_canvas_instructions(self) -> None:
- if self._no_ripple_effect:
+ if not self.ripple_effect:
return
with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
diff --git a/kivymd/uix/behaviors/state_layer_behavior.py b/kivymd/uix/behaviors/state_layer_behavior.py
new file mode 100644
index 000000000..607b05104
--- /dev/null
+++ b/kivymd/uix/behaviors/state_layer_behavior.py
@@ -0,0 +1,526 @@
+# TODO: Add docs.
+
+"""
+Behaviors/State Layer
+=====================
+
+.. seealso::
+
+ `Material Design spec, State layers `_
+"""
+
+from kivy.lang import Builder
+from kivy.properties import ColorProperty, NumericProperty, ObjectProperty
+
+from kivymd.uix.behaviors.focus_behavior import FocusBehavior
+
+
+Builder.load_string(
+ """
+
+ canvas.after:
+ Color
+ rgba: self.state_layer_color
+ RoundedRectangle:
+ group: "State_layer_instruction"
+ size: self.size
+ pos: self.pos
+ radius: self.radius if hasattr(self, "radius") else [0, ]
+""",
+ filename="StateLayerBehavior.kv",
+)
+
+# TODO: Add methods `set_text_color` and `set_icon_color`
+# (methods that set the color of text and icons in the state
+# `on_enter` and `on_leave` and `pressed`).
+
+
+class StateLayerBehavior(FocusBehavior):
+ state_layer_color = ColorProperty([0, 0, 0, 0])
+ """
+ The color of the layer state.
+
+ :attr:`state_layer_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0, 0, 0, 0]`.
+ """
+
+ state_hover = NumericProperty(0.08)
+ """
+ The transparency level of the layer as a percentage when hovering.
+
+ :attr:`state_hover` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.08`.
+ """
+
+ state_press = NumericProperty(0.12)
+ """
+ The transparency level of the layer as a percentage when pressed.
+
+ :attr:`state_press` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.12`.
+ """
+
+ state_drag = NumericProperty(0.16)
+ """
+ The transparency level of the layer as a percentage when dragged.
+
+ :attr:`state_drag` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.16`.
+ """
+
+ # The transparency value of the disabled state.
+ # These values are specified in the M3 specification.
+
+ # -------------------------------------------------------------------------
+ # MDIconButton
+ # -------------------------------------------------------------------------
+
+ # Filled.
+ icon_button_filled_opacity_value_disabled_container = NumericProperty(0.12)
+ icon_button_filled_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # Tonal.
+ icon_button_tonal_opacity_value_disabled_container = NumericProperty(0.12)
+ icon_button_tonal_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # Outlined.
+ icon_button_outlined_opacity_value_disabled_container = NumericProperty(
+ 0.12
+ )
+ icon_button_outlined_opacity_value_disabled_line = NumericProperty(0.12)
+ icon_button_outlined_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # Standard.
+ icon_button_standard_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDFabButton
+ # -------------------------------------------------------------------------
+
+ fab_button_opacity_value_disabled_container = NumericProperty(0.12)
+ fab_button_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDButton
+ # -------------------------------------------------------------------------
+
+ # Filled.
+ button_filled_opacity_value_disabled_container = NumericProperty(0.12)
+ button_filled_opacity_value_disabled_icon = NumericProperty(0.38)
+ button_filled_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # Tonal.
+ button_tonal_opacity_value_disabled_container = NumericProperty(0.12)
+ button_tonal_opacity_value_disabled_icon = NumericProperty(0.38)
+ button_tonal_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # Outlined.
+ button_outlined_opacity_value_disabled_container = NumericProperty(0.12)
+ button_outlined_opacity_value_disabled_line = NumericProperty(0.12)
+ button_outlined_opacity_value_disabled_icon = NumericProperty(0.38)
+ button_outlined_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # Elevated.
+ button_elevated_opacity_value_disabled_container = NumericProperty(0.12)
+ button_elevated_opacity_value_disabled_icon = NumericProperty(0.38)
+ button_elevated_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # Text.
+ button_text_opacity_value_disabled_icon = NumericProperty(0.38)
+ button_text_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDLabel
+ # -------------------------------------------------------------------------
+
+ label_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDCard
+ # -------------------------------------------------------------------------
+
+ card_filled_opacity_value_disabled_state_container = NumericProperty(0.38)
+ card_outlined_opacity_value_disabled_state_container = NumericProperty(0.12)
+ card_opacity_value_disabled_state_elevated_container = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDSegmentedButton
+ # -------------------------------------------------------------------------
+
+ segmented_button_opacity_value_disabled_container = NumericProperty(0.12)
+ segmented_button_opacity_value_disabled_container_active = NumericProperty(
+ 0.38
+ )
+ segmented_button_opacity_value_disabled_line = NumericProperty(0.12)
+ segmented_button_opacity_value_disabled_icon = NumericProperty(0.38)
+ segmented_button_opacity_value_disabled_text = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDChip
+ # -------------------------------------------------------------------------
+
+ chip_opacity_value_disabled_container = NumericProperty(0.12)
+ chip_opacity_value_disabled_text = NumericProperty(0.38)
+ chip_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDSwitch
+ # -------------------------------------------------------------------------
+
+ switch_opacity_value_disabled_line = NumericProperty(0.12)
+ switch_opacity_value_disabled_container = NumericProperty(0.12)
+ switch_thumb_opacity_value_disabled_container = NumericProperty(0.38)
+ switch_opacity_value_disabled_icon = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDCheckbox
+ # -------------------------------------------------------------------------
+
+ checkbox_opacity_value_disabled_container = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # List
+ # -------------------------------------------------------------------------
+
+ list_opacity_value_disabled_container = NumericProperty(0.38)
+ list_opacity_value_disabled_leading_avatar = NumericProperty(0.38)
+
+ # -------------------------------------------------------------------------
+ # MDTextField
+ # -------------------------------------------------------------------------
+
+ text_field_filled_opacity_value_disabled_state_container = NumericProperty(
+ 0.18
+ )
+ text_field_outlined_opacity_value_disabled_state_container = (
+ NumericProperty(0)
+ )
+ text_field_opacity_value_disabled_max_length_label = NumericProperty(0.60)
+ text_field_opacity_value_disabled_line = NumericProperty(0.12)
+
+ _state = 0.0
+ _bg_color = (0, 0, 0, 0)
+ _is_already_disabled = False
+ _shadow_softness = [0, 0]
+ _elevation_level = 0
+
+ # def __init__(self, *args, **kwargs):
+ # super().__init__(*args, **kwargs)
+
+ def set_properties_widget(self) -> None:
+ """Fired `on_release/on_press/on_enter/on_leave` events."""
+
+ if not self.disabled:
+ self._restore_properties()
+ self._set_state_layer_color()
+
+ def on_disabled(self, instance, value) -> None:
+ """Fired when the `disabled` value changes."""
+
+ from kivymd.uix.card import MDCard
+ from kivymd.uix.button import (
+ MDIconButton,
+ MDButton,
+ MDFabButton,
+ MDExtendedFabButton,
+ )
+ from kivymd.uix.segmentedbutton import MDSegmentedButtonItem
+ from kivymd.uix.segmentedbutton.segmentedbutton import (
+ MDSegmentButtonSelectedIcon,
+ )
+ from kivymd.uix.selectioncontrol import MDSwitch
+ from kivymd.uix.list import BaseListItem
+ from kivymd.uix.textfield import MDTextField
+
+ if value and not self._is_already_disabled:
+ self._is_already_disabled = True
+ if isinstance(self, MDCard):
+ self.state_layer_color = (
+ {
+ "filled": self.theme_cls.surfaceColor[:-1]
+ + [
+ self.card_filled_opacity_value_disabled_state_container
+ ],
+ "outlined": self.theme_cls.outlineColor[:-1]
+ + [
+ self.card_outlined_opacity_value_disabled_state_container
+ ],
+ "elevated": self.theme_cls.surfaceVariantColor[:-1]
+ + [
+ self.card_opacity_value_disabled_state_elevated_container
+ ],
+ }[self.style]
+ if not self.md_bg_color_disabled
+ else self.md_bg_color_disabled
+ )
+ elif isinstance(self, MDIconButton):
+ self.state_layer_color = (
+ {
+ "tonal": self.theme_cls.onSurfaceColor[:-1]
+ + [
+ self.icon_button_tonal_opacity_value_disabled_container
+ ],
+ "filled": self.theme_cls.onSurfaceColor[:-1]
+ + [
+ self.icon_button_filled_opacity_value_disabled_container
+ ],
+ "outlined": self.theme_cls.onSurfaceColor[:-1]
+ + [
+ self.icon_button_outlined_opacity_value_disabled_container
+ ],
+ "standard": self.theme_cls.transparentColor,
+ }[self.style]
+ if not self.md_bg_color_disabled
+ else self.md_bg_color_disabled
+ )
+ elif isinstance(self, MDButton):
+ self.state_layer_color = (
+ {
+ "elevated": self.theme_cls.onSurfaceColor[:-1]
+ + [
+ self.button_elevated_opacity_value_disabled_container
+ ],
+ "tonal": self.theme_cls.onSurfaceColor[:-1]
+ + [self.button_tonal_opacity_value_disabled_container],
+ "filled": self.theme_cls.onSurfaceColor[:-1]
+ + [self.button_filled_opacity_value_disabled_container],
+ "outlined": self.theme_cls.onSurfaceColor[:-1]
+ + [
+ self.button_outlined_opacity_value_disabled_container
+ ],
+ "text": self.theme_cls.transparentColor,
+ }[self.style]
+ if not self.md_bg_color_disabled
+ else self.md_bg_color_disabled
+ )
+ elif isinstance(self, (MDFabButton, MDExtendedFabButton)):
+ self.state_layer_color = (
+ self.theme_cls.onSurfaceColor[:-1]
+ + [self.fab_button_opacity_value_disabled_container]
+ if not self.md_bg_color_disabled
+ else self.md_bg_color_disabled
+ )
+ elif isinstance(self, MDTextField):
+ if self.mode == "filled":
+ self.state_layer_color = self.theme_cls.onSurfaceColor[
+ :-1
+ ] + [
+ self.text_field_filled_opacity_value_disabled_state_container
+ ]
+ else:
+ self.state_layer_color = self.theme_cls.transparentColor
+ elif isinstance(self.parent, MDSegmentedButtonItem):
+ self.state_layer_color = (
+ self.theme_cls.onSurfaceColor[:-1]
+ + [self.segmented_button_opacity_value_disabled_container]
+ if not self.parent.md_bg_color_disabled
+ else self.parent.md_bg_color_disabled
+ )
+ elif isinstance(self, MDSwitch):
+ self.state_layer_color = (
+ (
+ self.theme_cls.surfaceContainerHighestColor
+ if not self.active
+ else self.theme_cls.onSurfaceColor
+ )[:-1]
+ + [self.switch_opacity_value_disabled_container]
+ if not self.md_bg_color_disabled
+ else self.md_bg_color_disabled
+ )
+ elif isinstance(self, BaseListItem):
+ self.state_layer_color = (
+ self.theme_cls.onSurfaceColor[:-1]
+ + [self.list_opacity_value_disabled_container]
+ if not self.md_bg_color_disabled
+ else self.md_bg_color_disabled
+ )
+ elif not value and self._is_already_disabled:
+ self.state_layer_color = self.theme_cls.transparentColor
+ self._is_already_disabled = False
+
+ def on_enter(self) -> None:
+ """Fired when mouse enter the bbox of the widget."""
+
+ self._state = self.state_hover
+ self.set_properties_widget()
+
+ def on_leave(self) -> None:
+ """Fired when the mouse goes outside the widget border."""
+
+ self._state = 0.0
+ self.set_properties_widget()
+
+ def _on_release(self, *args):
+ """
+ Fired when the button is released
+ (i.e. the touch/click that pressed the button goes away).
+ """
+
+ self.on_enter()
+
+ def _on_press(self, *args):
+ """Fired when the button is pressed."""
+
+ self._state = self.state_press
+ self.set_properties_widget()
+
+ def _restore_properties(self):
+ if self._state == self.state_hover and self.focus_behavior:
+ if hasattr(self, "elevation_level"):
+ self._elevation_level = self.elevation_level
+ if hasattr(self, "shadow_softness"):
+ self._shadow_softness = self.shadow_softness
+ if hasattr(self, "md_bg_color"):
+ self._bg_color = self.md_bg_color
+ elif not self._state:
+ if hasattr(self, "elevation_level"):
+ self.elevation_level = self._elevation_level
+ if hasattr(self, "shadow_softness"):
+ self.shadow_softness = self._shadow_softness
+ if hasattr(self, "bg_color"):
+ self.bg_color = self._md_bg_color
+
+ # FIXME: For some widgets, the color of the state of its elements is
+ # ignored. For example, for the `MDSwitch` widget, the color of the status
+ # of the `Thumb` element and the color of the icon are ignored.
+ def _get_target_color(self):
+ from kivymd.uix.card import MDCard
+ from kivymd.uix.button import (
+ MDIconButton,
+ MDButton,
+ MDFabButton,
+ MDExtendedFabButton,
+ )
+ from kivymd.uix.segmentedbutton.segmentedbutton import (
+ MDSegmentedButtonContainer,
+ )
+ from kivymd.uix.chip import MDChip
+ from kivymd.uix.selectioncontrol import MDSwitch, MDCheckbox
+ from kivymd.uix.list import BaseListItem
+ from kivymd.uix.textfield import MDTextField
+ from kivymd.uix.navigationdrawer import MDNavigationDrawerItem
+
+ target_color = None
+
+ if not self.disabled:
+ self._restore_properties()
+
+ if isinstance(self, MDTextField):
+ if self.mode == "filled":
+ target_color = self.theme_cls.onSurfaceColor
+ else:
+ target_color = self.theme_cls.transparentColor
+ elif isinstance(self, (MDCard, BaseListItem)) and not isinstance(
+ self, MDNavigationDrawerItem
+ ):
+ target_color = self.theme_cls.onSurfaceColor
+ elif isinstance(self, MDNavigationDrawerItem):
+ target_color = self.theme_cls.onSecondaryContainerColor
+ elif isinstance(self.parent, MDSegmentedButtonContainer):
+ target_color = (
+ self.theme_cls.onSurfaceColor
+ if not self.active
+ else self.theme_cls.onSecondaryContainerColor
+ )
+ elif isinstance(self, MDChip):
+ # Here, depending on the widget state (focus/pressed...)
+ # we set the target color of the widget's layer.
+ # For example:
+ #
+ # if self._state == self.state_press:
+ # target_color = [., ., ., .]
+ # else:
+ # ...
+ if self.type == "assist":
+ target_color = self.theme_cls.onSurfaceColor
+ elif self.type in ["filter", "input", "suggestion"]:
+ target_color = self.theme_cls.onSurfaceVariantColor
+ elif isinstance(self, MDIconButton):
+ if self.style == "filled":
+ target_color = self.theme_cls.onPrimaryColor
+ elif self.style == "tonal":
+ target_color = self.theme_cls.onSecondaryContainerColor
+ elif self.style in ["outlined", "standard"]:
+ target_color = self.theme_cls.onSurfaceVariantColor
+ elif isinstance(self, MDButton):
+ target_color = (
+ self.theme_cls.onPrimaryColor
+ if self.style == "filled"
+ else self.theme_cls.primaryColor
+ )
+ elif isinstance(self, MDCheckbox):
+ target_color = (
+ self.theme_cls.primaryColor
+ if self.active
+ else self.theme_cls.onSurfaceColor
+ )
+ elif isinstance(self, (MDFabButton, MDExtendedFabButton)):
+ target_color = self.theme_cls.onPrimaryContainerColor
+ elif isinstance(self, MDSwitch):
+ target_color = (
+ self.theme_cls.primaryColor
+ if self.active
+ else self.theme_cls.onSurfaceVariantColor
+ )
+ else:
+ target_color = self.theme_cls.onSurfaceColor
+
+ return target_color
+
+ def _set_state_layer_color(self):
+ from kivymd.uix.card import MDCard
+ from kivymd.uix.button import (
+ MDIconButton,
+ MDButton,
+ MDFabButton,
+ MDExtendedFabButton,
+ )
+ from kivymd.uix.segmentedbutton.segmentedbutton import (
+ MDSegmentedButtonContainer,
+ )
+ from kivymd.uix.chip import MDChip
+ from kivymd.uix.selectioncontrol import MDSwitch, MDCheckbox
+ from kivymd.uix.list import BaseListItem
+ from kivymd.uix.textfield import MDTextField
+
+ target_color = self._get_target_color()
+ if (
+ isinstance(
+ self,
+ (
+ MDCard,
+ MDTextField,
+ MDIconButton,
+ MDButton,
+ MDFabButton,
+ MDExtendedFabButton,
+ MDChip,
+ MDSwitch,
+ MDCheckbox,
+ BaseListItem,
+ ),
+ )
+ or isinstance(self.parent, MDSegmentedButtonContainer)
+ and target_color
+ ):
+ if self._state == self.state_hover and self.focus_behavior:
+ if (
+ not self.focus_color
+ or self.theme_cls.dynamic_color
+ and self.theme_focus_color == "Primary"
+ ):
+ if (
+ isinstance(self, MDTextField)
+ and self.mode == "outlined"
+ ):
+ self.state_layer_color = target_color
+ else:
+ self.state_layer_color = target_color[:-1] + [
+ self._state
+ ]
+ else:
+ self.state_layer_color = self.focus_color
+ elif self._state == self.state_press:
+ self.state_layer_color = target_color[:-1] + [self._state]
+ elif not self._state:
+ self.state_layer_color = target_color[:-1] + [self._state]
diff --git a/kivymd/uix/behaviors/toggle_behavior.py b/kivymd/uix/behaviors/toggle_behavior.py
index c396a29c7..df77fed06 100644
--- a/kivymd/uix/behaviors/toggle_behavior.py
+++ b/kivymd/uix/behaviors/toggle_behavior.py
@@ -125,25 +125,16 @@ def build(self):
- :class:`~kivymd.uix.button.MDFillRoundFlatIconButton`
"""
-__all__ = ("MDToggleButton",)
+__all__ = ("MDToggleButtonBehavior",)
+from kivy import Logger
from kivy.properties import BooleanProperty, ColorProperty
from kivy.uix.behaviors import ToggleButtonBehavior
-from kivymd.uix.button import (
- ButtonContentsIconText,
- MDFillRoundFlatButton,
- MDFillRoundFlatIconButton,
- MDFlatButton,
- MDRaisedButton,
- MDRectangleFlatButton,
- MDRectangleFlatIconButton,
- MDRoundFlatButton,
- MDRoundFlatIconButton,
-)
+from kivymd.uix.button import MDButton, MDIconButton, MDFabButton, BaseButton
-class MDToggleButton(ToggleButtonBehavior):
+class MDToggleButtonBehavior(ToggleButtonBehavior):
background_normal = ColorProperty(None)
"""
Color of the button in ``rgba`` format for the 'normal' state.
@@ -180,38 +171,29 @@ class MDToggleButton(ToggleButtonBehavior):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- classinfo = (
- MDRaisedButton,
- MDFlatButton,
- MDRectangleFlatButton,
- MDRectangleFlatIconButton,
- MDRoundFlatButton,
- MDRoundFlatIconButton,
- MDFillRoundFlatButton,
- MDFillRoundFlatIconButton,
- )
+ classinfo = (MDButton, MDIconButton, MDFabButton)
# Do the object inherited from the "supported" buttons?
if not issubclass(self.__class__, classinfo):
raise ValueError(
f"Class {self.__class__} must be inherited from one of the "
f"classes in the list {classinfo}"
)
+ else:
+ print(666, self.md_bg_color)
+ # self.theme_bg_color = "Custom"
if (
not self.background_normal
): # This means that if the value == [] or None will return True.
# If the object inherits from buttons with background:
- if isinstance(
- self,
- (
- MDRaisedButton,
- MDFillRoundFlatButton,
- MDFillRoundFlatIconButton,
- ),
- ):
+ if isinstance(self, BaseButton):
+ print(111)
self.__is_filled = True
- self.background_normal = self.theme_cls.primary_color
+ self.background_normal = (
+ "yellow" # self.theme_cls.primary_color
+ )
# If not background_normal must be the same as the inherited one.
else:
+ print(222)
self.background_normal = (
self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0)
)
@@ -228,25 +210,44 @@ def __init__(self, **kwargs):
# self.bind(state=self._update_bg)
self.fbind("state", self._update_bg)
- def _update_bg(self, ins, val):
+ def _update_bg(self, instance, value):
"""Updates the color of the background."""
- if val == "down":
- self.md_bg_color = self.background_down
- if (
- self.__is_filled is False
- ): # If the background is transparent, and the button it toggled,
- # the font color must be withe [1, 1, 1, 1].
- self.text_color = self.font_color_down
+ if self.theme_bg_color == "Primary":
+ self.theme_bg_color = "Custom"
+ if self.theme_icon_color == "Primary":
+ self.theme_icon_color = "Custom"
+
+ if value == "down":
+ if isinstance(self, MDIconButton):
+ self.md_bg_color = self.theme_cls.primaryColor
+ self.icon_color = self.theme_cls.onPrimaryColor
+
+ # if (
+ # self.__is_filled is False
+ # ):
+ # self.text_color = self.font_color_down
if self.group:
self._release_group(self)
else:
- self.md_bg_color = self.background_normal
- if (
- self.__is_filled is False
- ): # If the background is transparent, the font color must be the
- # primary color.
- self.text_color = self.font_color_normal
-
- if issubclass(self.__class__, ButtonContentsIconText):
- self.icon_color = self.text_color
+ if isinstance(self, MDIconButton):
+ self.md_bg_color = self.theme_cls.surfaceContainerHighestColor
+ self.icon_color = self.theme_cls.primaryColor
+
+ # if (
+ # self.__is_filled is False
+ # ):
+ # self.text_color = self.font_color_normal
+
+ # if issubclass(self.__class__, ButtonContentsIconText):
+ # self.icon_color = self.text_color
+
+
+class MDToggleButton(MDToggleButtonBehavior):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ Logger.warning(
+ f"KivyMD: "
+ f"The `{self.__class__.__name__}` class has been deprecated. "
+ f"Use the `MDToggleButtonBehavior` class instead."
+ )
diff --git a/kivymd/uix/behaviors/touch_behavior.py b/kivymd/uix/behaviors/touch_behavior.py
index 8aca7bf22..4b528aaa4 100644
--- a/kivymd/uix/behaviors/touch_behavior.py
+++ b/kivymd/uix/behaviors/touch_behavior.py
@@ -91,10 +91,10 @@ def delete_clock(self, widget, touch, *args):
del touch.ud["event"]
def on_long_touch(self, touch, *args):
- """Called when the widget is pressed for a long time."""
+ """Fired when the widget is pressed for a long time."""
def on_double_tap(self, touch, *args):
- """Called by double clicking on the widget."""
+ """Fired by double-clicking on the widget."""
def on_triple_tap(self, touch, *args):
- """Called by triple clicking on the widget."""
+ """Fired by triple clicking on the widget."""
diff --git a/kivymd/uix/bottomnavigation/__init__.py b/kivymd/uix/bottomnavigation/__init__.py
deleted file mode 100644
index d62fda660..000000000
--- a/kivymd/uix/bottomnavigation/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# NOQA F401
-from .bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
diff --git a/kivymd/uix/bottomnavigation/bottomnavigation.kv b/kivymd/uix/bottomnavigation/bottomnavigation.kv
deleted file mode 100644
index 79f726b28..000000000
--- a/kivymd/uix/bottomnavigation/bottomnavigation.kv
+++ /dev/null
@@ -1,118 +0,0 @@
-#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
-
-
-
- orientation: "vertical"
- height:
- STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp"
-
- ScreenManager:
- id: tab_manager
- transition: root.transition(duration=root.transition_duration)
- on_current:
- root.dispatch( \
- "on_switch_tabs", \
- root._get_switchig_tab(self.current), \
- self.current \
- )
-
- MDBottomNavigationBar:
- id: bottom_panel
- size_hint_y: None
- radius: root.radius
- height:
- STANDARD_INCREMENT \
- if app.theme_cls.material_style == "M2" else \
- "80dp"
- md_bg_color:
- root.theme_cls.bg_dark \
- if not root.panel_color \
- else root.panel_color
-
- MDBoxLayout:
- id: tab_bar
- pos_hint: {"center_x": .5, "center_y": .5}
- size_hint: None, None
- height:
- STANDARD_INCREMENT \
- if app.theme_cls.material_style == "M2" else \
- "80dp"
-
-
-
- md_bg_color: root.panel_color
- on_press: self.tab.dispatch("on_tab_press")
- on_release: self.tab.dispatch("on_tab_release")
- on_touch_down: self.tab.dispatch("on_tab_touch_down", *args)
- on_touch_move: self.tab.dispatch("on_tab_touch_move", *args)
- on_touch_up: self.tab.dispatch("on_tab_touch_up", *args)
- width:
- root.panel.width / len(root.panel.ids.tab_manager.screens) \
- if len(root.panel.ids.tab_manager.screens) != 0 \
- else root.panel.width
- padding:
- 0, "12dp", 0, "12dp" if app.theme_cls.material_style == "M2" else "16dp"
-
- RelativeLayout:
- id: item_container
-
- MDIcon:
- id: _label_icon
- icon: root.tab.icon
- height: self.height
- badge_icon: root.tab.badge_icon
- theme_text_color: "Custom"
- text_color: root._text_color_normal
- opposite_colors: root.opposite_colors
- pos: [self.pos[0], self.pos[1]]
- font_size: "24dp"
- y: item_container.height - self.height
- pos_hint:
- {"center_x": .5, "center_y": .5} \
- if not root.panel.use_text else \
- {"center_x": .5, "top": 1}
- on_icon:
- if self.icon not in md_icons.keys(): \
- self.size_hint = (None, None); \
- self.width = self.font_size; \
- self.height = self.font_size
-
- canvas.before:
- Color:
- rgba:
- ( \
- ( \
- app.theme_cls.disabled_hint_text_color \
- if not root.selected_color_background else \
- root.selected_color_background \
- ) \
- if root.active else \
- (0, 0, 0, 0) \
- ) \
- if app.theme_cls.material_style == "M3" else \
- (0, 0, 0, 0)
- RoundedRectangle:
- radius: [16,]
- size: root._selected_region_width, dp(32)
- pos:
- self.center_x - root._selected_region_width / 2, \
- self.center_y - (dp(16))
-
- MDLabel:
- id: _label
- text: root.tab.text
- size_hint_x: None
- text_size: None, root.height
- adaptive_height: True
- theme_text_color: "Custom"
- text_color: root._text_color_normal
- opposite_colors: root.opposite_colors
- font_size: root._label_font_size
- pos_hint: {"center_x": .5}
- y: -dp(4) if app.theme_cls.material_style == "M2" else 0
- font_style:
- "Button" if app.theme_cls.material_style == "M2" else "Body2"
-
-
-
- md_bg_color: root.theme_cls.bg_normal
diff --git a/kivymd/uix/bottomnavigation/bottomnavigation.py b/kivymd/uix/bottomnavigation/bottomnavigation.py
deleted file mode 100644
index 9b764d4ec..000000000
--- a/kivymd/uix/bottomnavigation/bottomnavigation.py
+++ /dev/null
@@ -1,880 +0,0 @@
-"""
-Components/BottomNavigation
-===========================
-
-.. seealso::
-
- `Material Design 2 spec, Bottom navigation `_ and
- `Material Design 3 spec, Bottom navigation `_
-
-.. rubric:: Bottom navigation bars allow movement between primary destinations in an app:
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png
- :align: center
-
-Usage
------
-
-.. code-block:: kv
-
-
-
- MDBottomNavigation:
-
- MDBottomNavigationItem:
- name: "screen 1"
-
- YourContent:
-
- MDBottomNavigationItem:
- name: "screen 2"
-
- YourContent:
-
- MDBottomNavigationItem:
- name: "screen 3"
-
- YourContent:
-
-For ease of understanding, this code works like this:
-
-.. code-block:: kv
-
-
-
- ScreenManager:
-
- Screen:
- name: "screen 1"
-
- YourContent:
-
- Screen:
- name: "screen 2"
-
- YourContent:
-
- Screen:
- name: "screen 3"
-
- YourContent:
-
-Example
--------
-
-.. tabs::
-
- .. tab:: Declarative KV style
-
- .. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
-
-
- class Test(MDApp):
-
- def build(self):
- self.theme_cls.material_style = "M3"
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(
- '''
- MDScreen:
-
- MDBottomNavigation:
- #panel_color: "#eeeaea"
- selected_color_background: "orange"
- text_color_active: "lightgrey"
-
- MDBottomNavigationItem:
- name: 'screen 1'
- text: 'Mail'
- icon: 'gmail'
- badge_icon: "numeric-10"
-
- MDLabel:
- text: 'Mail'
- halign: 'center'
-
- MDBottomNavigationItem:
- name: 'screen 2'
- text: 'Twitter'
- icon: 'twitter'
- badge_icon: "numeric-5"
-
- MDLabel:
- text: 'Twitter'
- halign: 'center'
-
- MDBottomNavigationItem:
- name: 'screen 3'
- text: 'LinkedIN'
- icon: 'linkedin'
-
- MDLabel:
- text: 'LinkedIN'
- halign: 'center'
- '''
- )
-
-
- Test().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem
- from kivymd.uix.label import MDLabel
- from kivymd.uix.screen import MDScreen
-
-
- class Test(MDApp):
- def build(self):
- self.theme_cls.material_style = "M3"
- self.theme_cls.theme_style = "Dark"
- return (
- MDScreen(
- MDBottomNavigation(
- MDBottomNavigationItem(
- MDLabel(
- text='Mail',
- halign='center',
- ),
- name='screen 1',
- text='Mail',
- icon='gmail',
- badge_icon="numeric-10",
- ),
- MDBottomNavigationItem(
- MDLabel(
- text='Twitter',
- halign='center',
- ),
- name='screen 1',
- text='Twitter',
- icon='twitter',
- badge_icon="numeric-10",
- ),
- MDBottomNavigationItem(
- MDLabel(
- text='LinkedIN',
- halign='center',
- ),
- name='screen 1',
- text='LinkedIN',
- icon='linkedin',
- badge_icon="numeric-10",
- ),
- selected_color_background="orange",
- text_color_active="lightgrey",
- )
- )
- )
-
-
- Test().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif
- :align: center
-
-.. rubric:: :class:`~MDBottomNavigationItem` provides the following events for use:
-
-.. code-block:: python
-
- __events__ = (
- "on_tab_touch_down",
- "on_tab_touch_move",
- "on_tab_touch_up",
- "on_tab_press",
- "on_tab_release",
- )
-
-.. code-block:: kv
-
- Root:
-
- MDBottomNavigation:
-
- MDBottomNavigationItem:
- on_tab_touch_down: print("on_tab_touch_down")
- on_tab_touch_move: print("on_tab_touch_move")
- on_tab_touch_up: print("on_tab_touch_up")
- on_tab_press: print("on_tab_press")
- on_tab_release: print("on_tab_release")
-
- YourContent:
-
-How to automatically switch a tab?
-----------------------------------
-
-Use method :attr:`~MDBottomNavigation.switch_tab` which takes as argument
-the name of the tab you want to switch to.
-
-Use custom icon
----------------
-
-.. code-block:: kv
-
- MDBottomNavigation:
-
- MDBottomNavigationItem:
- icon: "icon.png"
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-custom-icon.png
- :align: center
-"""
-
-__all__ = (
- "TabbedPanelBase",
- "MDBottomNavigationItem",
- "MDBottomNavigation",
- "MDTab",
-)
-
-import os
-from typing import Union
-
-from kivy.animation import Animation
-from kivy.clock import Clock
-from kivy.core.window import Window
-from kivy.core.window.window_sdl2 import WindowSDL
-from kivy.lang import Builder
-from kivy.metrics import dp, sp
-from kivy.properties import (
- BooleanProperty,
- ColorProperty,
- ListProperty,
- NumericProperty,
- ObjectProperty,
- StringProperty,
-)
-from kivy.uix.behaviors import ButtonBehavior
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.screenmanager import FadeTransition, ScreenManagerException
-
-from kivymd import uix_path
-from kivymd.material_resources import STANDARD_INCREMENT
-from kivymd.theming import ThemableBehavior, ThemeManager
-from kivymd.uix.anchorlayout import MDAnchorLayout
-from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior
-from kivymd.uix.behaviors.backgroundcolor_behavior import (
- SpecificBackgroundColorBehavior,
-)
-from kivymd.uix.floatlayout import MDFloatLayout
-from kivymd.uix.screen import MDScreen
-from kivymd.utils.set_bars_colors import set_bars_colors
-
-with open(
- os.path.join(uix_path, "bottomnavigation", "bottomnavigation.kv"),
- encoding="utf-8",
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-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 in (r, g, b, a) or string format.
-
- :attr:`panel_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[1, 1, 1, 0]`.
- """
-
- tab = ObjectProperty()
- """
- :attr:`tab` is an :class:`~MDBottomNavigationItem`
- and defaults to `None`.
- """
-
- panel = ObjectProperty()
- """
- :attr:`panel` is an :class:`~MDBottomNavigation`
- and defaults to `None`.
- """
-
- active = BooleanProperty(False)
-
- text = StringProperty()
- """
- :attr:`text` is an :class:`~MDTab.text`
- and defaults to `''`.
- """
-
- text_color_normal = ColorProperty([1, 1, 1, 1])
- """
- 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]`.
- """
-
- text_color_active = ColorProperty([1, 1, 1, 1])
- """
- 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]`.
- """
-
- selected_color_background = ColorProperty(None)
- """
- The background color in (r, g, b, a) or string format of the highlighted
- item when using Material Design v3.
-
- .. versionadded:: 1.0.0
-
- :attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- opposite_colors = BooleanProperty(True)
-
- _label = ObjectProperty()
- _label_font_size = NumericProperty("12sp")
- _text_color_normal = ColorProperty([1, 1, 1, 1])
- _text_color_active = ColorProperty([1, 1, 1, 1])
- _selected_region_width = NumericProperty(dp(64))
-
- def __init__(self, panel, tab):
- self.panel = panel
- self.tab = tab
- super().__init__()
- self._text_color_normal = (
- self.theme_cls.disabled_hint_text_color
- if self.text_color_normal == [1, 1, 1, 1]
- else self.text_color_normal
- )
- self._label = self.ids._label
- self._label_font_size = sp(12)
- self.theme_cls.bind(disabled_hint_text_color=self._update_theme_style)
- self.active = False
-
- def on_press(self) -> None:
- """Called when clicking on a panel item."""
-
- if self.theme_cls.material_style == "M2":
- Animation(_label_font_size=sp(14), d=0.1).start(self)
- elif self.theme_cls.material_style == "M3":
- Animation(
- _selected_region_width=dp(64),
- t="in_out_sine",
- d=0,
- ).start(self)
- Animation(
- _text_color_normal=self.theme_cls.primary_color
- if self.text_color_active == [1, 1, 1, 1]
- else self.text_color_active,
- d=0.1,
- ).start(self)
-
- def _update_theme_style(
- self, instance_theme_manager: ThemeManager, color: list
- ):
- """Called when the application theme style changes (White/Black)."""
-
- if not self.active:
- self._text_color_normal = (
- color
- if self.text_color_normal == [1, 1, 1, 1]
- else self.text_color_normal
- )
-
-
-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__ = (
- "on_tab_touch_down",
- "on_tab_touch_move",
- "on_tab_touch_up",
- "on_tab_press",
- "on_tab_release",
- )
- """Events provided."""
-
- text = StringProperty()
- """
- Tab header text.
-
- :attr:`text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- icon = StringProperty("checkbox-blank-circle")
- """
- Tab header icon.
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'checkbox-blank-circle'`.
- """
-
- badge_icon = StringProperty()
- """
- Tab header badge icon.
-
- .. versionadded:: 1.0.0
-
- :attr:`badge_icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.index = 0
- self.parent_widget = None
- self.register_event_type("on_tab_touch_down")
- self.register_event_type("on_tab_touch_move")
- self.register_event_type("on_tab_touch_up")
- self.register_event_type("on_tab_press")
- self.register_event_type("on_tab_release")
-
- def on_tab_touch_down(self, *args):
- pass
-
- def on_tab_touch_move(self, *args):
- pass
-
- def on_tab_touch_up(self, *args):
- pass
-
- def on_tab_press(self, *args):
- par = self.parent_widget
- if par.previous_tab is not self:
- if par.previous_tab.index > self.index:
- par.ids.tab_manager.transition.direction = "right"
- elif par.previous_tab.index < self.index:
- par.ids.tab_manager.transition.direction = "left"
- par.ids.tab_manager.current = self.name
- par.previous_tab = self
-
- def on_tab_release(self, *args):
- pass
-
- def __repr__(self):
- return f""
-
-
-class MDBottomNavigationItem(MDTab):
- header = ObjectProperty()
- """
- :attr:`header` is an :class:`~MDBottomNavigationHeader`
- and defaults to `None`.
- """
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- def animate_header(
- self, bottom_navigation_object, bottom_navigation_header_object
- ) -> None:
- if bottom_navigation_object.use_text:
- Animation(_label_font_size=sp(12), d=0.1).start(
- bottom_navigation_object.previous_tab.header
- )
- Animation(
- _selected_region_width=0,
- t="in_out_sine",
- d=0,
- ).start(bottom_navigation_header_object)
- Animation(
- _text_color_normal=bottom_navigation_header_object.text_color_normal
- if bottom_navigation_object.previous_tab.header.text_color_normal
- != [1, 1, 1, 1]
- else self.theme_cls.disabled_hint_text_color,
- d=0.1,
- ).start(bottom_navigation_object.previous_tab.header)
- bottom_navigation_object.previous_tab.header.active = False
- self.header.active = True
-
- def on_tab_press(self, *args) -> None:
- """Called when clicking on a panel item."""
-
- bottom_navigation_object = self.parent_widget
- bottom_navigation_header_object = (
- bottom_navigation_object.previous_tab.header
- )
-
- if bottom_navigation_object.previous_tab is not self:
- self.animate_header(
- bottom_navigation_object, bottom_navigation_header_object
- )
-
- super().on_tab_press(*args)
-
- def on_disabled(
- self, instance_bottom_navigation_item, disabled_value: bool
- ) -> None:
- self.header.disabled = disabled_value
-
- def on_leave(self, *args):
- pass
-
-
-class TabbedPanelBase(
- ThemableBehavior, SpecificBackgroundColorBehavior, BoxLayout
-):
- """
- 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)
- """
- Current tab name.
-
- :attr:`current` is an :class:`~kivy.properties.StringProperty`
- and defaults to `None`.
- """
-
- previous_tab = ObjectProperty(None, aloownone=True)
- """
- :attr:`previous_tab` is an :class:`~MDTab` and defaults to `None`.
- """
-
- panel_color = ColorProperty(None)
- """
- Panel color of bottom navigation.
-
- :attr:`panel_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- tabs = ListProperty()
-
-
-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
- opened.
-
- .. versionadded:: 1.0.0
- """
-
- transition = ObjectProperty(FadeTransition)
- """
- Transition animation of bottom navigation screen manager.
-
- .. versionadded:: 1.1.0
-
- :attr:`transition` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `FadeTransition`.
- """
-
- transition_duration = NumericProperty(0.2)
- """
- Duration animation of bottom navigation screen manager.
-
- .. versionadded:: 1.1.0
-
- :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- text_color_normal = ColorProperty([1, 1, 1, 1])
- """
- Text color of the label when it is not selected.
-
- .. code-block:: kv
-
- MDBottomNavigation:
- text_color_normal: 1, 0, 1, 1
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_normal.png
-
- :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[1, 1, 1, 1]`.
- """
-
- text_color_active = ColorProperty([1, 1, 1, 1])
- """
- Text color of the label when it is selected.
-
- .. code-block:: kv
-
- MDBottomNavigation:
- text_color_active: 0, 0, 0, 1
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_active.png
-
- :attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[1, 1, 1, 1]`.
- """
-
- use_text = BooleanProperty(True)
- """
- Use text for :class:`~MDBottomNavigationItem` or not.
- If ``True``, the :class:`~MDBottomNavigation` panel height will be reduced
- by the text height.
-
- .. versionadded:: 1.0.0
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-use-text.png
- :align: center
-
- :attr:`use_text` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- selected_color_background = ColorProperty(None)
- """
- The background color of the highlighted item when using Material Design v3.
-
- .. versionadded:: 1.0.0
-
- .. code-block:: kv
-
- MDBottomNavigation:
- selected_color_background: 0, 0, 1, .4
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation=selected-color-background.png
-
- :attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- font_name = StringProperty("Roboto")
- """
- Font name of the label.
-
- .. versionadded:: 1.0.0
-
- .. code-block:: kv
-
- MDBottomNavigation:
- font_name: "path/to/font.ttf"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-font-name.png
-
- :attr:`font_name` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'Roboto'`.
- """
-
- first_widget = ObjectProperty()
- """
- :attr:`first_widget` is an :class:`~MDBottomNavigationItem`
- and defaults to `None`.
- """
-
- tab_header = ObjectProperty()
- """
- :attr:`tab_header` is an :class:`~MDBottomNavigationHeader`
- and defaults to `None`.
- """
-
- set_bars_color = BooleanProperty(False)
- """
- If `True` the background color of the navigation bar will be set
- automatically according to the current color of the toolbar.
-
- .. versionadded:: 1.0.0
-
- :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- widget_index = NumericProperty(0)
-
- # Text active color if it is selected.
- _active_color = ColorProperty([1, 1, 1, 1])
-
- def __init__(self, *args, **kwargs):
- self.previous_tab = None
- self.register_event_type("on_switch_tabs")
- super().__init__(*args, **kwargs)
- self.theme_cls.bind(material_style=self.refresh_tabs)
- Window.bind(on_resize=self.on_resize)
- Clock.schedule_once(lambda x: self.on_resize())
- Clock.schedule_once(self.set_status_bar_color)
-
- def set_status_bar_color(self, interval: Union[int, float]) -> None:
- if self.set_bars_color:
- set_bars_colors(self.panel_color, None, self.theme_cls.theme_style)
-
- def switch_tab(self, name_tab) -> None:
- """Switching the tab by name."""
-
- if not self.ids.tab_manager.has_screen(name_tab):
- raise ScreenManagerException(f"No Screen with name '{name_tab}'.")
- self.ids.tab_manager.get_screen(name_tab).dispatch("on_tab_press")
- count_index_screen = [
- self.ids.tab_manager.screens.index(screen)
- for screen in self.ids.tab_manager.screens
- if screen.name == name_tab
- ][0]
- numbers_screens = list(range(len(self.ids.tab_manager.screens)))
- numbers_screens.reverse()
- self.ids.tab_bar.children[
- numbers_screens.index(count_index_screen)
- ].dispatch("on_press")
-
- def refresh_tabs(self, *args) -> None:
- """Refresh all tabs."""
-
- if self.ids:
- tab_bar = self.ids.tab_bar
- tab_bar.clear_widgets()
- tab_manager = self.ids.tab_manager
- self._active_color = self.theme_cls.primary_color
-
- if self.text_color_active != [1, 1, 1, 1]:
- self._active_color = self.text_color_active
-
- for tab in tab_manager.screens:
- self.tab_header = MDBottomNavigationHeader(tab=tab, panel=self)
- tab.header = self.tab_header
- tab_bar.add_widget(self.tab_header)
-
- if tab is self.first_widget:
- self.tab_header._text_color_normal = self._active_color
- self.tab_header._label_font_size = sp(14)
- self.tab_header.active = True
- else:
- self.tab_header.ids._label.font_size = sp(12)
- self.tab_header._label_font_size = sp(12)
-
- def on_font_name(self, instance_bottom_navigation, font_name: str) -> None:
- for tab in self.ids.tab_bar.children:
- tab.ids._label.font_name = font_name
-
- def on_selected_color_background(
- self, instance_bottom_navigation, color: list
- ) -> None:
- def on_selected_color_background(*args):
- for tab in self.ids.tab_bar.children:
- tab.selected_color_background = color
-
- Clock.schedule_once(on_selected_color_background)
-
- def on_use_text(
- self, instance_bottom_navigation, use_text_value: bool
- ) -> None:
- if not use_text_value:
- for instance_bottom_navigation_header in self.ids.tab_bar.children:
- instance_bottom_navigation_header.ids.item_container.remove_widget(
- instance_bottom_navigation_header.ids._label
- )
- if self.theme_cls.material_style == "M2":
- height = dp(42)
- else:
- height = dp(80)
- self.height = height
- self.ids.bottom_panel.height = height
- self.ids.tab_bar.height = height
- else:
- if self.theme_cls.material_style == "M2":
- height = STANDARD_INCREMENT
- else:
- height = dp(80)
- self.height = height
- self.ids.bottom_panel.height = height
- self.ids.tab_bar.height = height
-
- def on_text_color_normal(
- self, instance_bottom_navigation, color: list
- ) -> None:
- MDBottomNavigationHeader.text_color_normal = color
- for tab in self.ids.tab_bar.children:
- if not tab.active:
- tab._text_color_normal = color
-
- def on_text_color_active(
- self, instance_bottom_navigation, color: list
- ) -> None:
- def on_text_color_active(*args):
- MDBottomNavigationHeader.text_color_active = color
- self.text_color_active = color
- for tab in self.ids.tab_bar.children:
- tab.text_color_active = color
- if tab.active:
- tab._text_color_normal = color
-
- Clock.schedule_once(on_text_color_active)
-
- def on_switch_tabs(self, bottom_navigation_item, name_tab: str) -> None:
- """
- Called when switching tabs. Returns the object of the tab to be opened.
- """
-
- def on_size(self, *args) -> None:
- self.on_resize()
-
- def on_resize(
- self,
- instance: Union[WindowSDL, None] = None,
- width: Union[int, None] = None,
- do_again: bool = True,
- ) -> None:
- """Called when the application window is resized."""
-
- full_width = 0
- for tab in self.ids.tab_manager.screens:
- full_width += tab.header.width
- tab.header.text_color_normal = self.text_color_normal
- self.ids.tab_bar.width = full_width
- if do_again:
- Clock.schedule_once(lambda x: self.on_resize(do_again=False), 0.1)
-
- def add_widget(self, widget, **kwargs):
- if isinstance(widget, MDBottomNavigationItem):
- self.widget_index += 1
- widget.index = self.widget_index
- widget.parent_widget = self
- self.ids.tab_manager.add_widget(widget)
- if self.widget_index == 1:
- self.previous_tab = widget
- self.first_widget = widget
- self.refresh_tabs()
- else:
- super().add_widget(widget)
-
- def remove_widget(self, widget):
- if isinstance(widget, MDBottomNavigationItem):
- self.ids.tab_manager.remove_widget(widget)
- self.refresh_tabs()
- else:
- super().remove_widget(widget)
-
- def _get_switchig_tab(self, name_tab: str) -> MDBottomNavigationItem:
- bottom_navigation_item = None
- for bottom_navigation_header_instance in self.ids.tab_bar.children:
- if bottom_navigation_header_instance.tab.name == name_tab:
- bottom_navigation_item = bottom_navigation_header_instance.tab
- break
- return bottom_navigation_item
-
-
-class MDBottomNavigationBar(CommonElevationBehavior, MDFloatLayout):
- pass
diff --git a/kivymd/uix/bottomsheet/bottomsheet.kv b/kivymd/uix/bottomsheet/bottomsheet.kv
index 6d63e1c20..bf2e31e47 100644
--- a/kivymd/uix/bottomsheet/bottomsheet.kv
+++ b/kivymd/uix/bottomsheet/bottomsheet.kv
@@ -27,7 +27,7 @@
orientation: "vertical"
- md_bg_color: root.bg_color if root.bg_color else app.theme_cls.bg_darkest
+ md_bg_color: "red" # root.bg_color if root.bg_color else app.theme_cls.bg_darkest
radius: 16, 16, 0, 0
padding: 0, "8dp", 0, 0
diff --git a/kivymd/uix/bottomsheet/bottomsheet.py b/kivymd/uix/bottomsheet/bottomsheet.py
index 4961ff029..1406ebcf3 100644
--- a/kivymd/uix/bottomsheet/bottomsheet.py
+++ b/kivymd/uix/bottomsheet/bottomsheet.py
@@ -579,6 +579,7 @@ def build(self):
from kivymd import uix_path
from kivymd.uix.behaviors import CommonElevationBehavior, TouchBehavior
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.label import MDLabel
@@ -589,7 +590,7 @@ def build(self):
os.path.join(uix_path, "bottomsheet", "bottomsheet.kv"),
encoding="utf-8",
) as kv_file:
- Builder.load_string(kv_file.read())
+ Builder.load_string(kv_file.read(), filename="MDBottomSheet.kv")
class BottomSheetDragHandle(MDWidget):
@@ -685,13 +686,16 @@ def add_widget(self, widget, *args, **kwargs):
return super().add_widget(widget)
-class MDBottomSheet(MDBoxLayout, CommonElevationBehavior, TouchBehavior):
+class MDBottomSheet(
+ MDBoxLayout, CommonElevationBehavior, StateLayerBehavior, TouchBehavior
+):
"""
Bottom sheet class.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` and
:class:`~kivymd.uix.behaviors.touch_behavior.CommonElevationBehavior` and
+ :class:`~kivymd.uix.behaviors.StateLayerBehavior` and
:class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior`
classes documentation.
diff --git a/kivymd/uix/boxlayout.py b/kivymd/uix/boxlayout.py
index c5f3e75d7..bf465b64e 100644
--- a/kivymd/uix/boxlayout.py
+++ b/kivymd/uix/boxlayout.py
@@ -16,7 +16,7 @@
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -28,7 +28,7 @@
MDBoxLayout:
adaptive_height: True
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
Available options are:
----------------------
@@ -89,15 +89,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDBoxLayout(
- DeclarativeBehavior, ThemableBehavior, BoxLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ BoxLayout,
+ MDAdaptiveWidget,
):
"""
Box layout class.
- For more information, see in the
- :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
+ For more information see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/button/__init__.py b/kivymd/uix/button/__init__.py
index b833a4cd3..b908f1fe9 100644
--- a/kivymd/uix/button/__init__.py
+++ b/kivymd/uix/button/__init__.py
@@ -1,17 +1,13 @@
# NOQA F401
from .button import (
- BaseButton,
- ButtonContentsIconText,
- MDFillRoundFlatButton,
- MDFillRoundFlatIconButton,
- MDFlatButton,
- MDFloatingActionButton,
- MDFloatingActionButtonSpeedDial,
+ MDButton,
+ MDButtonIcon,
+ MDButtonText,
MDIconButton,
- MDRaisedButton,
- MDRectangleFlatButton,
- MDRectangleFlatIconButton,
- MDRoundFlatButton,
- MDRoundFlatIconButton,
- MDTextButton,
+ MDFabButton,
+ BaseButton,
+ BaseFabButton,
+ MDExtendedFabButton,
+ MDExtendedFabButtonIcon,
+ MDExtendedFabButtonText,
)
diff --git a/kivymd/uix/button/button.kv b/kivymd/uix/button/button.kv
index 77f7d09d3..92113cacd 100644
--- a/kivymd/uix/button/button.kv
+++ b/kivymd/uix/button/button.kv
@@ -1,225 +1,396 @@
-
- canvas:
- Clear
+
+ size_hint: None, None
+ text_size: self.size
+ halign: "center"
+ valign: "center"
+ size:
+ { \
+ "standard": ("56dp", "56dp"), \
+ "small": ("40dp", "40dp"), \
+ "large": ("96dp", "96dp"), \
+ }[self.style]
+ radius:
+ { \
+ "standard": [dp(16), ], \
+ "small": [dp(12), ], \
+ "large": [dp(28), ], \
+ }[self.style]
+ shadow_radius:
+ { \
+ "standard": [dp(12), ], \
+ "small": [dp(8), ], \
+ "large": [dp(24), ], \
+ }[self.style]
+ shadow_offset: 0, -1
+ shadow_softness:
+ { \
+ "standard": 2, \
+ "small": 2, \
+ "large": 2, \
+ }[self.style]
+ elevation_level:
+ { \
+ "standard": 2, \
+ "small": 2, \
+ "large": 2, \
+ }[self.style]
+ shadow_color:
+ ( \
+ self.theme_cls.shadowColor \
+ if self.theme_shadow_color == "Primary" else \
+ self.shadow_color \
+ ) \
+ if not self.disabled else self.theme_cls.transparentColor
+ icon_color:
+ { \
+ "surface": self.theme_cls.onPrimaryContainerColor, \
+ "secondary": self.theme_cls.onSecondaryContainerColor, \
+ "tertiary": self.theme_cls.onTertiaryContainerColor \
+ }[self.color_map] \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color \
+ if self.icon_color else \
+ self.theme_cls.transparentColor \
+ )
+ disabled_color:
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.fab_button_opacity_value_disabled_icon] \
+ if not self.icon_color_disabled else self.icon_color_disabled
+ theme_font_size: "Custom"
+ font_size:
+ { \
+ "standard": "24sp", \
+ "small": "24sp", \
+ "large": "36sp", \
+ }[root.style]
+
+ canvas.before:
Color:
- group: "bg-color"
rgba:
- self._md_bg_color \
- if not self.disabled else \
- self._md_bg_color_disabled
+ { \
+ "surface": self.theme_cls.surfaceColor, \
+ "secondary": self.theme_cls.secondaryColor, \
+ "tertiary": self.theme_cls.tertiaryColor \
+ }[self.color_map] \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color
RoundedRectangle:
size: self.size
pos: self.pos
- source: self.source if hasattr(self, "source") else ""
- radius: [root._radius, ]
- Color:
- group: "outline-color"
- rgba:
- root._line_color \
- if not root.disabled else \
- (root._line_color_disabled or self._disabled_color)
- Line:
- width: root.line_width
- rounded_rectangle:
- ( \
- self.x, self.y, self.width, self.height, \
- root._radius, root._radius, root._radius, root._radius, \
- self.height \
- )
-
- size_hint: None, None
- anchor_x: root.halign
- anchor_y: root.valign
- _round_rad: [self._radius] * 4
+ radius: self.radius
-
- lbl_txt: lbl_txt
- width:
- max( \
- root._min_width, \
- root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
- )
- size_hint_min_x:
- max( \
- root._min_width, \
- root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \
- )
- height:
- max( \
- root._min_height, \
- root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
- )
- size_hint_min_y:
- max( \
- root._min_height, \
- root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \
- )
+
+ x: "16dp"
+ icon_color:
+ ( \
+ { \
+ "surface": self.theme_cls.onPrimaryContainerColor, \
+ "secondary": self.theme_cls.onSecondaryContainerColor, \
+ "tertiary": self.theme_cls.onTertiaryContainerColor \
+ }[self.parent.color_map] \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color \
+ if self.icon_color else self.theme_cls.transparentColor \
+ ) \
+ ) \
+ if self.parent else self.theme_cls.transparentColor
+ disabled_color:
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.fab_button_opacity_value_disabled_icon] \
+ if not self.icon_color_disabled else self.icon_color_disabled
+ pos_hint: {"center_y": .5}
- MDLabel:
- id: lbl_txt
- text: root.text
- font_size: root.font_size
- font_style: root.font_style
- halign: 'center'
- valign: 'middle'
- adaptive_size: True
- -text_size: None, None
- theme_text_color: root._theme_text_color
- text_color: root._text_color
- markup: True
- disabled: root.disabled
- opposite_colors: root.opposite_colors
- font_name: root.font_name if root.font_name else self.font_name
-
-
-
- lbl_ic: lbl_ic
- size: "48dp", "48dp"
- padding: "12dp" if root.icon in md_icons else (0, 0, 0, 0)
- # Backwards compatibility.
- theme_icon_color: root.theme_icon_color or root.theme_text_color
-
- MDIcon:
- id: lbl_ic
- icon: root.icon
- font_size: root.icon_size if root.icon_size else self.font_size
- font_name: root.font_name if root.font_name else self.font_name
- opposite_colors: root.opposite_colors
- text_color:
- # FIXME: ValueError: None is not allowed for MDIcon.text_color.
- # This is only a temporary fix and does not fix the cause of the error.
- (root._icon_color if root._icon_color else root.theme_cls.text_color) \
- if not root.disabled else \
- root.theme_cls.disabled_hint_text_color \
- if not root.disabled_color else \
- root.disabled_color
- # 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
-
-
-
- lbl_txt: lbl_txt
- lbl_ic: lbl_ic
-
- width:
- max( \
- root._min_width, \
- root.padding[0] \
- + lbl_ic.texture_size[0] \
- + box.spacing \
- + lbl_txt.texture_size[0] \
- + root.padding[2] \
- )
- size_hint_min_x:
- max( \
- root._min_width, \
- root.padding[0] \
- + lbl_ic.texture_size[0] \
- + box.spacing \
- + lbl_txt.texture_size[0] \
- + root.padding[2] \
- )
- height:
- max( \
- root._min_height, \
- root.padding[1] \
- + max(lbl_ic.texture_size[1], lbl_txt.texture_size[1]) \
- + root.padding[3] \
- )
- size_hint_min_y:
- max( \
- root._min_height, \
- root.padding[1] \
- + max(lbl_ic.texture_size[1], lbl_txt.texture_size[1]) \
- + root.padding[3] \
- )
- MDBoxLayout:
- id: box
- adaptive_size: True
- padding: 0
- spacing: "8dp"
-
- MDIcon:
- id: lbl_ic
- size_hint_x: None
- pos_hint: {"center_y": .5}
- icon: root.icon
- opposite_colors: root.opposite_colors
- font_size:
- root.icon_size \
- if root.icon_size else \
- (18 / 14 * lbl_txt.font_size)
- text_color:
- root._icon_color \
- if not root.disabled else \
- root.theme_cls.disabled_hint_text_color
- theme_text_color: root._theme_icon_color
-
- MDLabel:
- id: lbl_txt
- adaptive_size: True
- -text_size: None, None
- pos_hint: {"center_y": .5}
- halign: 'center'
- valign: 'middle'
- text: root.text
- font_size: root.font_size
- font_style: root.font_style
- font_name: root.font_name if root.font_name else self.font_name
- theme_text_color: root._theme_text_color
- text_color: root._text_color
- markup: True
- disabled: root.disabled
- opposite_colors: root.opposite_colors
-
-
-
- adaptive_size: True
- color: root.theme_cls.primary_color if not root.color else root.color
- opacity: 1
+
+ adaptive_width: True
+ text_color:
+ ( \
+ { \
+ "surface": self.theme_cls.onPrimaryContainerColor, \
+ "secondary": self.theme_cls.onSecondaryContainerColor, \
+ "tertiary": self.theme_cls.onTertiaryContainerColor \
+ }[self.parent.color_map] \
+ if self.theme_text_color == "Primary" else self.text_color \
+ ) \
+ if self.parent else self.text_color
+ disabled_color:
+ ( \
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.fab_button_opacity_value_disabled_icon] \
+ if not self.parent.icon_color_disabled else \
+ self.parent.icon_color_disabled \
+ ) \
+ if self.parent else self.theme_cls.transparentColor
+ pos_hint: {"center_y": .5}
-
- theme_text_color: "Custom"
- md_bg_color: self.theme_cls.primary_color
+
+ size_hint: None, None
+ size: "56dp", "56dp"
+ radius: [dp(16), ]
+ shadow_radius: [dp(12), ]
+ shadow_offset: 0, -1
+ shadow_softness: 2
+ elevation_level: 2
+ shadow_color:
+ ( \
+ self.theme_cls.shadowColor \
+ if self.theme_shadow_color == "Primary" else \
+ self.shadow_color \
+ ) \
+ if not self.disabled else self.theme_cls.transparentColor
+ theme_font_size: "Custom"
+ font_size: "24sp"
canvas.before:
Color:
rgba:
- self.theme_cls.primary_color \
- if not self._bg_color else \
- self._bg_color
+ { \
+ "standard": self.theme_cls.surfaceContainerColor \
+ if self.color_map == "surface" else \
+ { \
+ "secondary": self.theme_cls.secondaryContainerColor, \
+ "tertiary": self.theme_cls.tertiaryContainerColor \
+ }[self.color_map], \
+ "small": self.theme_cls.surfaceContainerHighColor \
+ if self.color_map == "surface" else \
+ { \
+ "secondary": self.theme_cls.secondaryContainerColor, \
+ "tertiary": self.theme_cls.tertiaryColor \
+ }[self.color_map], \
+ "large": self.theme_cls.surfaceContainerColor \
+ if self.color_map == "surface" else \
+ { \
+ "secondary": self.theme_cls.secondaryContainerColor, \
+ "tertiary": self.theme_cls.tertiaryColor \
+ }[self.color_map], \
+ }[self.style] \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color
RoundedRectangle:
- pos:
- (self.x - self._canvas_width + dp(1.5)) + self._padding_right / 2, \
- self.y - self._padding_right / 2 + dp(1.5)
- size:
- self.width + self._canvas_width - dp(3), \
- self.height + self._padding_right - dp(3)
- radius: [self.height / 2]
-
-
-
- theme_text_color: "Custom"
- md_bg_color: self.theme_cls.primary_color
-
+ size: self.size
+ pos: 0, 0
+ radius: self.radius
-
- padding_x: "8dp"
- padding_y: "8dp"
- adaptive_size: True
- theme_text_color: "Custom"
+
canvas.before:
Color:
- rgba: self.bg_color
+ rgba:
+ ( \
+ { \
+ "standard": self.theme_cls.transparentColor, \
+ "outlined": self.theme_cls.transparentColor, \
+ "tonal": self.theme_cls.secondaryContainerColor, \
+ "filled": self.theme_cls.primaryColor, \
+ }[self.style] \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color \
+ ) \
+ if not self.disabled else \
+ ( \
+ ( \
+ { \
+ "standard": self.theme_cls.transparentColor, \
+ "outlined": self.theme_cls.transparentColor, \
+ "tonal": self.theme_cls.onSurfaceColor[:-1] \
+ + [self.icon_button_tonal_opacity_value_disabled_container], \
+ "filled": self.theme_cls.onSurfaceColor[:-1] \
+ + [self.icon_button_filled_opacity_value_disabled_container], \
+ }[self.style] \
+ ) \
+ if not self.md_bg_color_disabled else self.md_bg_color_disabled \
+ )
RoundedRectangle:
size: self.size
pos: self.pos
radius: self.radius
+
+ radius: [20,]
+ halign: "center"
+ valign: "center"
+ size_hint: None, None
+ size: dp(40), dp(40)
+ text_size: self.size
+ line_color:
+ ( \
+ ( \
+ self.theme_cls.outlineColor \
+ if self.theme_line_color == "Primary" else \
+ ( \
+ self._line_color \
+ if self._line_color else \
+ self.line_color \
+ ) \
+ ) \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.icon_button_outlined_opacity_value_disabled_line] \
+ ) \
+ if self.style == "outlined" else self.theme_cls.transparentColor
+ icon_color:
+ ( \
+ { \
+ "standard": self.theme_cls.primaryColor, \
+ "tonal": self.theme_cls.onSecondaryContainerColor, \
+ "filled": self.theme_cls.onPrimaryColor, \
+ "outlined": self.theme_cls.onSurfaceVariantColor, \
+ }[self.style] \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color \
+ if self.icon_color else self.theme_cls.transparentColor \
+ ) \
+ )
+ disabled_color:
+ { \
+ "standard": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.icon_button_standard_opacity_value_disabled_icon], \
+ "tonal": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.icon_button_tonal_opacity_value_disabled_icon], \
+ "filled": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.icon_button_filled_opacity_value_disabled_icon], \
+ "outlined": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.icon_button_outlined_opacity_value_disabled_icon], \
+ }[self.style] \
+ if not self.icon_color_disabled else self.icon_color_disabled
+
+
+
+ md_bg_color:
+ { \
+ "elevated": self.theme_cls.surfaceContainerLowColor, \
+ "filled": self.theme_cls.primaryColor, \
+ "tonal": self.theme_cls.secondaryContainerColor, \
+ "outlined": self.theme_cls.transparentColor, \
+ "text": self.theme_cls.transparentColor, \
+ }[self.style] \
+ if self.theme_bg_color == "Primary" else self.md_bg_color
+ line_color:
+ ( \
+ ( \
+ self.theme_cls.outlineColor \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_outlined_opacity_value_disabled_line] \
+ ) \
+ if self.style == "outlined" else \
+ self.theme_cls.transparentColor \
+ ) \
+ if self.theme_line_color == "Primary" else self.line_color
+ size_hint_x: None if self.theme_width == "Primary" else self.size_hint_x
+ size_hint_y: None if self.theme_height == "Primary" else self.size_hint_y
+ height: "40dp"
+ elevation: self.elevation_levels[self.elevation_level]
+ shadow_color:
+ ( \
+ ( \
+ self.theme_cls.shadowColor \
+ if self.theme_shadow_color == "Primary" else \
+ self.shadow_color \
+ ) \
+ if self.style not in ["outlined", "text"] else \
+ self.theme_cls.transparentColor \
+ ) \
+ if not self.disabled else self.theme_cls.transparentColor
+ shadow_radius: self.radius
+ elevation_level:
+ { \
+ "elevated": 1, \
+ "filled": 0, \
+ "tonal": 0, \
+ "outlined": 0, \
+ "text": 0, \
+ }[self.style]
+ shadow_offset: [0, -1] if self.style == "elevated" else [0, 0]
+
+
+
+ adaptive_size: True
+ pos_hint: {"center_y": .5}
+ font_style: "Label"
+ role: "large"
+ markup: True
+ disabled: self._button.disabled if self._button else False
+ text_color:
+ ( \
+ ( \
+ ( \
+ { \
+ "elevated": self.theme_cls.primaryColor, \
+ "filled": self.theme_cls.onPrimaryColor, \
+ "tonal": self.theme_cls.onSecondaryContainerColor, \
+ "outlined": self.theme_cls.primaryColor, \
+ "text": self.theme_cls.primaryColor, \
+ }[self._button.style] \
+ ) \
+ if self._button else self.theme_cls.transparentColor \
+ ) \
+ if self.theme_text_color == "Primary" else self.text_color \
+ )
+ disabled_color:
+ ( \
+ { \
+ "elevated": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_elevated_opacity_value_disabled_text], \
+ "filled": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_filled_opacity_value_disabled_text], \
+ "tonal": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_tonal_opacity_value_disabled_text], \
+ "outlined": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_outlined_opacity_value_disabled_text], \
+ "text": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_text_opacity_value_disabled_text], \
+ }[self._button.style] \
+ ) \
+ if self._button else self.theme_cls.transparentColor
+
+
+
+ size_hint: None, None
+ size: "18dp", "18dp"
+ theme_font_size: "Custom"
+ font_size: "20sp"
+ x: "16dp"
+ pos_hint: {"center_y": .5}
+ icon_color:
+ ( \
+ ( \
+ ( \
+ { \
+ "elevated": self.theme_cls.primaryColor, \
+ "filled": self.theme_cls.onPrimaryColor, \
+ "tonal": self.theme_cls.onSecondaryContainerColor, \
+ "outlined": self.theme_cls.primaryColor, \
+ "text": self.theme_cls.primaryColor, \
+ }[self._button.style] \
+ ) \
+ if self._button else self.theme_cls.transparentColor \
+ ) \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color \
+ if self.icon_color else \
+ self.theme_cls.transparentColor \
+ ) \
+ )
+ disabled_color:
+ ( \
+ { \
+ "elevated": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_elevated_opacity_value_disabled_icon], \
+ "filled": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_filled_opacity_value_disabled_icon], \
+ "tonal": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_tonal_opacity_value_disabled_icon], \
+ "outlined": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_outlined_opacity_value_disabled_icon], \
+ "text": self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_text_opacity_value_disabled_icon], \
+ }[self._button.style] \
+ if not self.icon_color_disabled else self.icon_color_disabled \
+ ) \
+ if self._button else self.theme_cls.transparentColor
diff --git a/kivymd/uix/button/button.py b/kivymd/uix/button/button.py
index 5750a334d..f8e915881 100755
--- a/kivymd/uix/button/button.py
+++ b/kivymd/uix/button/button.py
@@ -4,34 +4,55 @@
.. seealso::
- `Material Design spec, Buttons `_
-
- `Material Design spec, Buttons: floating action button `_
+ `Material Design spec, Buttons `_
.. rubric:: Buttons allow users to take actions, and make choices,
- with a single tap.
+ with a single tap. When choosing the right button for an action, consider
+ the level of emphasis each button type provides.
+
+KivyMD provides the following button classes for use:
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/buttons.png
:align: center
-`KivyMD` provides the following button classes for use:
-
-- MDIconButton_
-- MDFloatingActionButton_
-- MDFlatButton_
-- MDRaisedButton_
-- MDRectangleFlatButton_
-- MDRectangleFlatIconButton_
-- MDRoundFlatButton_
-- MDRoundFlatIconButton_
-- MDFillRoundFlatButton_
-- MDFillRoundFlatIconButton_
-- MDTextButton_
-- MDFloatingActionButtonSpeedDial_
-
-.. MDIconButton:
-MDIconButton
-------------
+1. Elevated button
+2. Filled button
+3. Filled tonal button
+4. Outlined button
+5. Text button
+6. Icon button
+7. Segmented button
+8. Floating action button (FAB)
+9. Extended FAB
+
+Common buttons
+==============
+
+.. rubric:: Buttons help people take action, such as sending an email, sharing
+ a document, or liking a comment.
+
+.. seealso::
+
+ `Material Design spec, Buttons `_
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/common-buttons.png
+ :align: center
+
+1. Elevated button
+2. Filled button
+3. Filled tonal button
+4. Outlined button
+5. Text button
+
+- Elevated_
+- Filled_
+- Tonal_
+- Outlined_
+- Text_
+
+.. Elevated:
+Elevated
+--------
.. tabs::
@@ -45,17 +66,23 @@
KV = '''
MDScreen:
+ md_bg_color: app.theme_cls.surfaceColor
- MDIconButton:
- icon: "language-python"
+ MDButton:
+ style: "elevated"
pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDButtonIcon:
+ icon: "plus"
+
+ MDButtonText:
+ text: "Elevated"
'''
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
+ self.theme_cls.primary_palette = "Green"
return Builder.load_string(KV)
@@ -66,637 +93,602 @@ def build(self):
.. code-block:: python
from kivymd.app import MDApp
- from kivymd.uix.button import MDIconButton
+ from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText
from kivymd.uix.screen import MDScreen
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
+ self.theme_cls.primary_palette = "Green"
return (
MDScreen(
- MDIconButton(
- icon="language-python",
+ MDButton(
+ MDButtonIcon(
+ icon="plus",
+ ),
+ MDButtonText(
+ text="Elevated",
+ ),
+ style="elevated",
pos_hint={"center_x": 0.5, "center_y": 0.5},
- )
+ ),
+ md_bg_color=self.theme_cls.surfaceColor,
)
)
Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevated-button.gif
:align: center
-The :class:`~MDIconButton.icon` parameter must have the name of the icon
-from ``kivymd/icon_definitions.py`` file.
-
-You can also use custom icons:
+Common buttons can contain an icon or be without an icon:
.. code-block:: kv
- MDIconButton:
- icon: "kivymd/images/logo/kivymd-icon-256.png"
+ MDButton:
+ style: "elevated"
+ text: "Elevated"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevated-without-icon-button.png
:align: center
-By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``.
-Use :class:`~BaseButton.icon_size` attribute to resize the button:
+.. Filled:
+Filled
+------
.. code-block:: kv
- MDIconButton:
- icon: "android"
- icon_size: "64sp"
+ MDButton:
+ style: "filled"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.png
+ MDButtonText:
+ text: "Filled"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/filled-button.gif
:align: center
-By default, the color of :class:`~MDIconButton`
-(depending on the style of the application) is black or white.
-You can change the color of :class:`~MDIconButton` as the text color
-of :class:`~kivymd.uix.label.MDLabel`, substituting ``theme_icon_color`` for
-``theme_text_color`` and ``icon_color`` for ``text_color``.
+.. Tonal:
+Tonal
+-----
.. code-block:: kv
- MDIconButton:
- icon: "android"
- theme_icon_color: "Custom"
- icon_color: app.theme_cls.primary_color
+ MDButton:
+ style: "tonal"
+
+ MDButtonText:
+ text: "Tonal"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-theme-text-color.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tonal-button.gif
:align: center
-.. MDFloatingActionButton:
-MDFloatingActionButton
-----------------------
+.. Outlined:
+Outlined
+--------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button.png
- :align: center
+.. code-block:: kv
-The above parameters for :class:`~MDIconButton` apply
-to :class:`~MDFloatingActionButton`.
+ MDButton:
+ style: "outlined"
-To change :class:`~MDFloatingActionButton` background, use the
-``md_bg_color`` parameter:
+ MDButtonText:
+ text: "Outlined"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/outlined-button.gif
+ :align: center
+
+.. Text:
+Text
+--------
.. code-block:: kv
- MDFloatingActionButton:
- icon: "android"
- md_bg_color: app.theme_cls.primary_color
+ MDButton:
+ style: "text"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-md-bg-color.png
+ MDButtonText:
+ text: "Text"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button.gif
:align: center
-Material design style 3
------------------------
+Customization of buttons
+========================
-.. code-block:: python
+Text positioning and button size
+--------------------------------
- from kivy.lang import Builder
+.. code-block:: kv
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFloatingActionButton
+ MDButton:
+ style: "tonal"
+ theme_width: "Custom"
+ height: "56dp"
+ size_hint_x: .5
- KV = '''
- MDScreen:
- md_bg_color: "#f7f2fa"
+ MDButtonIcon:
+ x: text.x - (self.width + dp(10))
+ icon: "plus"
- MDBoxLayout:
- id: box
- spacing: "56dp"
- adaptive_size: True
+ MDButtonText:
+ id: text
+ text: "Tonal"
pos_hint: {"center_x": .5, "center_y": .5}
- '''
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/positioning-size-button.png
+ :align: center
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.material_style = "M3"
- return Builder.load_string(KV)
-
- def on_start(self):
- data = {
- "standard": {"md_bg_color": "#fefbff", "text_color": "#6851a5"},
- "small": {"md_bg_color": "#e9dff7", "text_color": "#211c29"},
- "large": {"md_bg_color": "#f8d7e3", "text_color": "#311021"},
- }
- for type_button in data.keys():
- self.root.ids.box.add_widget(
- MDFloatingActionButton(
- icon="pencil",
- type=type_button,
- theme_icon_color="Custom",
- md_bg_color=data[type_button]["md_bg_color"],
- icon_color=data[type_button]["text_color"],
- )
- )
+Font of the button text
+-----------------------
+.. code-block:: kv
- Example().run()
+ MDButton:
+ style: "filled"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.png
- :align: center
+ MDButtonIcon:
+ icon: "plus"
-.. MDFlatButton:
-MDFlatButton
-------------
+ MDButtonText:
+ text: "Filled"
+ font_style: "Title"
-To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter:
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-style-button-text.png
+ :align: center
.. code-block:: kv
- MDFlatButton:
- text: "MDFlatButton"
- theme_text_color: "Custom"
- text_color: "orange"
+ MDButton:
+ style: "elevated"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png
+ MDButtonText:
+ text: "Elevated"
+ theme_font_name: "Custom"
+ font_name: "path/to/font.ttf"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-button-text.png
:align: center
-Or use markup:
+Custom button color
+-------------------
.. code-block:: kv
- MDFlatButton:
- text: "[color=#00ffcc]MDFlatButton[/color]"
+ MDButton:
+ style: "elevated"
+ theme_shadow_color: "Custom"
+ shadow_color: "red"
-To specify the font size and font name, use the parameters as in the usual
-`Kivy` buttons:
+ MDButtonIcon:
+ icon: "plus"
+ theme_icon_color: "Custom"
+ icon_color: "green"
-.. code-block:: kv
+ MDButtonText:
+ text: "Elevated"
+ theme_text_color: "Custom"
+ text_color: "red"
- MDFlatButton:
- text: "MDFlatButton"
- font_size: "18sp"
- font_name: "path/to/font"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-color-button.png
+ :align: center
-.. MDRaisedButton:
-MDRaisedButton
---------------
+Icon buttons
+============
-This button is similar to the :class:`~MDFlatButton` button except that you
-can set the background color for :class:`~MDRaisedButton`:
+.. rubric:: Use icon buttons when a compact button is required, such as in a
+ toolbar or image list. There are two types of icon buttons: standard and
+ contained.
-.. code-block:: kv
+.. seealso::
- MDRaisedButton:
- text: "MDRaisedButton"
- md_bg_color: "red"
+ `Material Design spec, Icon buttons `_
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-buttons.png
:align: center
-.. MDRectangleFlatButton:
-MDRectangleFlatButton
----------------------
+1. Standard icon button
+2. Contained icon button (including filled, filled tonal, and outlined styles)
+
+- StandardIcon_
+- FilledIcon_
+- TonalIcon_
+- OutlinedIcon_
+
+.. StandardIcon:
+StandardIcon
+------------
.. code-block:: kv
- MDRectangleFlatButton:
- text: "MDRectangleFlatButton"
- theme_text_color: "Custom"
- text_color: "white"
- line_color: "red"
+ MDIconButton:
+ icon: "heart-outline"
+ style: "standard"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-standard.gif
:align: center
-.. MDRectangleFlatIconButton:
-MDRectangleFlatIconButton
--------------------------
-
-Button parameters :class:`~MDRectangleFlatIconButton` are the same as
-button :class:`~MDRectangleFlatButton`, with the addition of the
-``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.
+.. FilledIcon:
+FilledIcon
+----------
.. code-block:: kv
- MDRectangleFlatIconButton:
- icon: "android"
- text: "MDRectangleFlatIconButton"
- theme_text_color: "Custom"
- text_color: "white"
- line_color: "red"
- theme_icon_color: "Custom"
- icon_color: "orange"
+ MDIconButton:
+ icon: "heart-outline"
+ style: "filled"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-filled.gif
:align: center
-Without border
---------------
+.. TonalIcon:
+TonalIcon
+---------
-.. code-block:: python
+.. code-block:: kv
- from kivymd.app import MDApp
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.button import MDRectangleFlatIconButton
+ MDIconButton:
+ icon: "heart-outline"
+ style: "tonal"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-tonal.gif
+ :align: center
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return (
- MDScreen(
- MDRectangleFlatIconButton(
- text="MDRectangleFlatIconButton",
- icon="language-python",
- line_color=(0, 0, 0, 0),
- pos_hint={"center_x": .5, "center_y": .5},
- )
- )
- )
+.. OutlinedIcon:
+OutlinedIcon
+------------
+.. code-block:: kv
- Example().run()
+ MDIconButton:
+ icon: "heart-outline"
+ style: "outlined"
-.. code-block:: kv
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-outlined.gif
+ :align: center
+
+Custom icon size
+----------------
- MDRectangleFlatIconButton:
- text: "MDRectangleFlatIconButton"
- icon: "language-python"
- line_color: 0, 0, 0, 0
- pos_hint: {"center_x": .5, "center_y": .5}
+.. code-block:: kv
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-without-border.png
+ MDIconButton:
+ icon: "heart-outline"
+ style: "tonal"
+ theme_font_size: "Custom"
+ font_size: "48sp"
+ radius: [self.height / 2, ]
+ size_hint: None, None
+ size: "84dp", "84dp"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-size.png
:align: center
-.. MDRoundFlatButton:
-MDRoundFlatButton
------------------
+Custom button color
+-------------------
.. code-block:: kv
- MDRoundFlatButton:
- text: "MDRoundFlatButton"
- text_color: "white"
+ MDIconButton:
+ icon: "heart-outline"
+ style: "tonal"
+ theme_bg_color: "Custom"
+ md_bg_color: "brown"
+ theme_icon_color: "Custom"
+ icon_color: "white"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-color.png
:align: center
-.. MDRoundFlatIconButton:
-MDRoundFlatIconButton
----------------------
+FAB buttons
+===========
-Button parameters :class:`~MDRoundFlatIconButton` are the same as
-button :class:`~MDRoundFlatButton`, with the addition of the
-``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`:
+.. rubric:: The FAB represents the most important action on a screen.
+ It puts key actions within reach.
-.. code-block:: kv
+.. seealso::
- MDRoundFlatIconButton:
- text: "MDRoundFlatIconButton"
- icon: "android"
- text_color: "white"
+ `Material Design spec, FAB buttons `_
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-buttons.png
:align: center
-.. MDFillRoundFlatButton:
-MDFillRoundFlatButton
----------------------
+1. Standard FAB
+2. Small FAB
+3. Large FAB
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-button.png
- :align: center
+There are three sizes of floating action buttons: FAB, small FAB, and large FAB:
-Button parameters :class:`~MDFillRoundFlatButton` are the same as
-button :class:`~MDRaisedButton`.
+- Standard_
+- Small_
+- Large_
-.. MDFillRoundFlatIconButton:
-MDFillRoundFlatIconButton
--------------------------
+.. Standard:
+Standard
+--------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-icon-button.png
+.. code-block:: kv
+
+ MDFabButton:
+ icon: "pencil-outline"
+ style: "standard"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-standard.gif
:align: center
-Button parameters :class:`~MDFillRoundFlatIconButton` are the same as
-button :class:`~MDRaisedButton`, with the addition of the
-``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`.
+.. Small:
+Small
+-----
+
+.. code-block:: kv
-.. note:: Notice that the width of the :class:`~MDFillRoundFlatIconButton`
- button matches the size of the button text.
+ MDFabButton:
+ icon: "pencil-outline"
+ style: "small"
-.. MDTextButton:
-MDTextButton
-------------
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-small.png
+ :align: center
+
+.. Large:
+Large
+-----
.. code-block:: kv
- MDTextButton:
- text: "MDTextButton"
- custom_color: "white"
+ MDFabButton:
+ icon: "pencil-outline"
+ style: "large"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-large.gif
:align: center
-.. MDFloatingActionButtonSpeedDial:
-MDFloatingActionButtonSpeedDial
--------------------------------
+Additional color mappings
+-------------------------
+
+FABs can use other combinations of container and icon colors. The color
+mappings below provide the same legibility and functionality as the default,
+so the color mapping you use depends on style alone.
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-color-mapping.png
+ :align: center
-.. Note:: See the full list of arguments in the class
- :class:`~MDFloatingActionButtonSpeedDial`.
+1. Surface
+2. Secondary
+3. Tertiary
.. code-block:: python
from kivy.lang import Builder
from kivymd.app import MDApp
+ from kivymd.uix.button import MDFabButton
KV = '''
MDScreen:
+ md_bg_color: app.theme_cls.surfaceColor
- MDFloatingActionButtonSpeedDial:
- data: app.data
- root_button_anim: True
+ MDBoxLayout:
+ id: box
+ adaptive_size: True
+ spacing: "32dp"
+ pos_hint: {"center_x": .5, "center_y": .5}
'''
class Example(MDApp):
- data = {
- 'Python': 'language-python',
- 'PHP': 'language-php',
- 'C++': 'language-cpp',
- }
-
def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
+ self.theme_cls.primary_palette = "Green"
return Builder.load_string(KV)
+ def on_start(self):
+ styles = {
+ "standard": "surface",
+ "small": "secondary",
+ "large": "tertiary",
+ }
+ for style in styles.keys():
+ self.root.ids.box.add_widget(
+ MDFabButton(
+ style=style, icon="pencil-outline", color_map=styles[style]
+ )
+ )
+
Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-color-mapping.png
:align: center
-Or without KV Language:
-
-.. tabs::
+Extended FAB
+============
- .. tab:: Imperative python style
+.. rubric:: Extended floating action buttons (extended FABs) help people take
+ primary actions. They're wider than FABs to accommodate a text label and
+ larger target area.
- .. code-block:: python
+.. seealso::
- from kivymd.uix.screen import MDScreen
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFloatingActionButtonSpeedDial
+ `Material Design spec, FAB buttons `_
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/extended-fab-button.png
+ :align: center
- class Example(MDApp):
- data = {
- 'Python': 'language-python',
- 'PHP': 'language-php',
- 'C++': 'language-cpp',
- }
+1. Extended FAB with both icon and label text
+2. Extended FAB without icon
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- screen = MDScreen()
- speed_dial = MDFloatingActionButtonSpeedDial()
- speed_dial.data = self.data
- speed_dial.root_button_anim = True
- screen.add_widget(speed_dial)
- return screen
+With icon
+---------
+.. code-block:: python
- Example().run()
+ from kivy.lang import Builder
- .. tab:: Declarative python style
+ from kivymd.app import MDApp
- .. code-block:: python
+ KV = '''
+ MDScreen:
+ md_bg_color: app.theme_cls.surfaceColor
+ on_touch_down:
+ if not btn.collide_point(*args[1].pos): \\
+ btn.fab_state = "expand" \\
+ if btn.fab_state == "collapse" else "collapse"
+
+ MDExtendedFabButton:
+ id: btn
+ icon: "heart"
+ text: "Compose"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ '''
- from kivymd.uix.screen import MDScreen
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFloatingActionButtonSpeedDial
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.primary_palette = "Green"
+ return Builder.load_string(KV)
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return (
- MDScreen(
- MDFloatingActionButtonSpeedDial(
- data={
- 'Python': 'language-python',
- 'PHP': 'language-php',
- 'C++': 'language-cpp',
- },
- root_button_anim=True,
- )
- )
- )
+ Example().run()
- Example().run()
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/extended-fab-button-icon.gif
+ :align: center
-You can use various types of animation of labels for buttons on the stack:
+Without icon
+------------
.. code-block:: kv
- MDFloatingActionButtonSpeedDial:
- hint_animation: True
+ MDExtendedFabButton:
+ text: "Compose"
+ fab_state: "expand"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/extended-fab-button-without-icon.png
:align: center
-You can set your color values for background, text of buttons etc:
+API break
+=========
+
+1.2.0 version
+-------------
.. code-block:: kv
- MDFloatingActionButtonSpeedDial:
- hint_animation: True
- bg_hint_color: app.theme_cls.primary_dark
+ MDFloatingActionButton:
+ icon: "plus"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png
- :align: center
+.. code-block:: kv
-Binds to individual buttons
----------------------------
+ MDRoundFlatButton:
+ text: "Outlined"
-.. tabs::
+.. code-block:: kv
- .. tab:: Declarative KV style
+ MDRoundFlatIconButton:
+ text: "Outlined with icon"
+ icon: "plus"
- .. code-block:: python
+.. code-block:: kv
- from kivy.lang import Builder
- from kivy.properties import DictProperty
+ MDFillRoundFlatButton
+ text: "Filled"
- from kivymd.app import MDApp
+.. code-block:: kv
- KV = '''
- MDScreen:
+ MDFillRoundFlatIconButton
+ text: "Filled with icon"
+ icon: "plus"
- MDFloatingActionButtonSpeedDial:
- id: speed_dial
- data: app.data
- root_button_anim: True
- hint_animation: True
- '''
+2.0.0 version
+-------------
- class Example(MDApp):
- data = DictProperty()
+.. note:: `MDFloatingActionButtonSpeedDial` type buttons were removed
+ in version `2.0.0`.
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- self.data = {
- 'Python': 'language-python',
- 'JS': [
- 'language-javascript',
- "on_press", lambda x: print("pressed JS"),
- "on_release", lambda x: print(
- "stack_buttons",
- self.root.ids.speed_dial.stack_buttons
- )
- ],
- 'PHP': [
- 'language-php',
- "on_press", lambda x: print("pressed PHP"),
- "on_release", self.callback
- ],
- 'C++': [
- 'language-cpp',
- "on_press", lambda x: print("pressed C++"),
- "on_release", lambda x: self.callback()
- ],
- }
- return Builder.load_string(KV)
+.. code-block:: kv
- def callback(self, *args):
- print(args)
+ MDFabButton:
+ icon: "plus"
+.. code-block:: kv
- Example().run()
+ MDButton:
+ style: "outlined"
- .. tab:: Declarative python style
+ MDButtonText:
+ text: "Outlined"
- .. code-block:: python
+.. code-block:: kv
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFloatingActionButtonSpeedDial
- from kivymd.uix.screen import MDScreen
+ MDButton:
+ style: "outlined"
+ MDButtonIcon:
+ icon: "plus"
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return (
- MDScreen(
- MDFloatingActionButtonSpeedDial(
- id="speed_dial",
- hint_animation=True,
- root_button_anim=True,
- )
- )
- )
+ MDButtonText:
+ text: "Outlined with icon"
+
+.. code-block:: kv
- def on_start(self):
- data = {
- "Python": "language-python",
- "JS": [
- "language-javascript",
- "on_press", lambda x: print("pressed JS"),
- "on_release", lambda x: print(
- "stack_buttons",
- self.root.ids.speed_dial.stack_buttons
- )
- ],
- "PHP": [
- "language-php",
- "on_press", lambda x: print("pressed PHP"),
- "on_release", self.callback
- ],
- "C++": [
- "language-cpp",
- "on_press", lambda x: print("pressed C++"),
- "on_release", lambda x: self.callback()
- ],
- }
- self.root.ids.speed_dial.data = data
-
- def callback(self, *args):
- print(args)
+ MDButton:
+ style: "filled"
+ MDButtonText:
+ text: "Filled"
- Example().run()
+.. code-block:: kv
+
+ MDButton:
+ style: "filled"
+
+ MDButtonIcon:
+ icon: "plus"
+
+ MDButtonText:
+ text: "Filled"
"""
from __future__ import annotations
__all__ = (
- "BaseButton",
"MDIconButton",
- "MDFloatingActionButton",
- "MDFlatButton",
- "MDRaisedButton",
- "MDRectangleFlatButton",
- "MDRectangleFlatIconButton",
- "MDRoundFlatButton",
- "MDRoundFlatIconButton",
- "MDFillRoundFlatButton",
- "MDFillRoundFlatIconButton",
- "MDTextButton",
- "MDFloatingActionButtonSpeedDial",
+ "MDButtonText",
+ "MDButtonIcon",
+ "MDFabButton",
+ "MDExtendedFabButton",
+ "MDExtendedFabButtonIcon",
+ "MDExtendedFabButtonText",
+ "MDButton",
+ "BaseButton",
+ "BaseFabButton",
)
import os
-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.metrics import dp, sp
+from kivy.metrics import dp
from kivy.properties import (
- BooleanProperty,
- BoundedNumericProperty,
ColorProperty,
- DictProperty,
NumericProperty,
- ObjectProperty,
OptionProperty,
- StringProperty,
VariableListProperty,
+ ObjectProperty,
)
-from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.behaviors import ButtonBehavior
-from kivy.uix.floatlayout import FloatLayout
-from kivy.weakproxy import WeakProxy
+from kivy.uix.relativelayout import RelativeLayout
+from kivymd.uix.label import MDIcon, MDLabel
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,
DeclarativeBehavior,
RectangularRippleBehavior,
- RotateBehavior,
+ BackgroundColorBehavior,
)
-from kivymd.uix.label import MDLabel
-from kivymd.uix.tooltip import MDTooltip
+from kivymd.uix.behaviors.motion_behavior import MotionExtendedFabButtonBehavior
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
with open(
os.path.join(uix_path, "button", "button.kv"), encoding="utf-8"
@@ -704,157 +696,129 @@ def callback(self, *args):
Builder.load_string(kv_file.read())
-theme_text_color_options = (
- "Primary",
- "Secondary",
- "Hint",
- "Error",
- "Custom",
- "ContrastParentBackground",
-)
-
-
-class BaseButton(
- DeclarativeBehavior,
- RectangularRippleBehavior,
- ThemableBehavior,
- ButtonBehavior,
- AnchorLayout,
-):
+class BaseFabButton:
"""
- Base class for all buttons.
+ Implements the basic properties for the
+ :class:`~MDExtendedFabButton` and :class:`~MDFabButton` classes.
- For more information, see in the
- :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.
+ .. versionadded:: 2.0.0
"""
- padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)])
+ color_map = OptionProperty(
+ "surface", options=("surface", "secondary", "tertiary")
+ )
"""
- Padding between the widget box and its children, in pixels:
- [padding_left, padding_top, padding_right, padding_bottom].
-
- padding also accepts a two argument form [padding_horizontal,
- padding_vertical] and a one argument form [padding].
+ Additional color mappings.
- .. versionadded:: 1.0.0
-
- :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
- and defaults to [16dp, 8dp, 16dp, 8dp].
- """
+ Available options are: 'surface', 'secondary', 'tertiary'.
- halign = OptionProperty("center", options=("left", "center", "right"))
+ :attr:`color_map` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'secondary'`.
"""
- Horizontal anchor.
- .. versionadded:: 1.0.0
-
- :attr:`anchor_x` is an :class:`~kivy.properties.OptionProperty`
- and defaults to 'center'. It accepts values of 'left', 'center' or 'right'.
+ icon_color_disabled = ColorProperty(None)
"""
+ The icon color in (r, g, b, a) or string format of the list item when
+ the widget item is disabled.
- valign = OptionProperty("center", options=("top", "center", "bottom"))
+ :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- Vertical anchor.
- .. versionadded:: 1.0.0
-
- :attr:`anchor_y` is an :class:`~kivy.properties.OptionProperty`
- and defaults to 'center'. It accepts values of 'top', 'center' or 'bottom'.
+ style = OptionProperty("standard", options=("standard", "small", "large"))
"""
+ Button type.
- text = StringProperty("")
- """
- Button text.
+ Available options are: 'standard', 'small', 'large'.
- :attr:`text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+ :attr:`style` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'standard'`.
"""
- icon = StringProperty("")
+ fab_state = OptionProperty("collapse", options=("collapse", "expand"))
"""
- Button icon.
+ The state of the button.
- :attr:`icon` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
+ Available options are: 'collapse' or 'expand'.
- font_style = OptionProperty("Body1", options=theme_font_styles)
+ :attr:`fab_state` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to "collapse".
"""
- Button text font style.
- Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`,
- `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`,
- `'Caption'`, `'Overline'`, `'Icon'`.
-
- :attr:`font_style` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Body1'`.
+ md_bg_color_disabled = ColorProperty(None)
"""
+ The background color in (r, g, b, a) or string format of the list item when
+ the list button is disabled.
- theme_text_color = OptionProperty(None, options=theme_text_color_options)
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- Button text type. Available options are: (`"Primary"`, `"Secondary"`,
- `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).
- :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `None` (set by button class).
+ radius = VariableListProperty(
+ [
+ dp(16),
+ ],
+ length=4,
+ )
"""
+ Canvas radius.
- theme_icon_color = OptionProperty(None, options=theme_text_color_options)
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[dp(16), dp(16), dp(16), dp(16)]`.
"""
- Button icon type. Available options are: (`"Primary"`, `"Secondary"`,
- `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).
- .. versionadded:: 1.0.0
-
- :attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `None` (set by button subclass).
- """
- text_color = ColorProperty(None)
+class BaseButton(
+ DeclarativeBehavior,
+ BackgroundColorBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ ThemableBehavior,
+ StateLayerBehavior,
+):
"""
- Button text color in (r, g, b, a) or string format.
+ Base button class.
- :attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior`
+ classes documentation.
"""
- icon_color = ColorProperty(None)
+ md_bg_color_disabled = ColorProperty(None)
"""
- Button icon color in (r, g, b, a) or string format.
+ The background color in (r, g, b, a) or string format of the button when
+ the button is disabled.
- :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- font_name = StringProperty()
+ shadow_radius = VariableListProperty([0, 0, 0, 0])
"""
- Button text font name.
+ Button shadow radius.
- :attr:`font_name` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+ :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[0, 0, 0, 0]`.
"""
- font_size = NumericProperty("14sp")
+ md_bg_color = ColorProperty(None)
"""
- Button text font size.
+ Button background color in (r, g, b, a) or string format.
- :attr:`font_size` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `14sp`.
+ :attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- icon_size = NumericProperty()
+ line_color = ColorProperty(None)
"""
- Icon font size.
- Use this parameter as the font size, that is, in sp units.
-
- .. versionadded:: 1.0.0
+ Outlined color.
- :attr:`icon_size` is a :class:`~kivy.properties.NumericProperty`
+ :attr:`line_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
@@ -866,1502 +830,369 @@ class BaseButton(
and defaults to `1`.
"""
- line_color = ColorProperty(None)
- """
- Line color in (r, g, b, a) or string format for button border.
+ def on_press(self, *args) -> None:
+ """Fired when the button is pressed."""
- :attr:`line_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
+ self._on_press(args)
- line_color_disabled = ColorProperty(None)
- """
- Disabled line color in (r, g, b, a) or string format for button border.
+ def on_release(self, *args) -> None:
+ """
+ Fired when the button is released
+ (i.e. the touch/click that pressed the button goes away).
+ """
- .. versionadded:: 1.0.0
+ self._on_release(args)
- :attr:`line_color_disabled` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
- md_bg_color = ColorProperty(None)
+class MDButton(BaseButton, CommonElevationBehavior, RelativeLayout):
"""
- Button background color in (r, g, b, a) or string format.
+ Base class for all buttons.
- :attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
+ .. versionadded:: 2.2.0
- md_bg_color_disabled = ColorProperty(None)
- """
- The background color in (r, g, b, a) or string format of the button when
- the button is disabled.
-
- :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~BaseButton` and
+ :class:`~kivy.uix.relativelayout.RelativeLayout`
+ classes documentation.
"""
- disabled_color = ColorProperty(None)
+ style = OptionProperty(
+ "elevated", options=("elevated", "filled", "tonal", "outlined", "text")
+ )
"""
- The color of the text and icon when the button is disabled,
- in (r, g, b, a) or string format.
+ Button type.
- .. versionadded:: 1.0.0
+ Available options are: 'filled', 'elevated', 'outlined', 'tonal', 'text'.
- :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ :attr:`style` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'elevated'`.
"""
- rounded_button = BooleanProperty(False)
+ radius = VariableListProperty(
+ [
+ dp(20),
+ ]
+ )
"""
- Should the button have fully rounded corners (e.g. like M3 buttons)?
+ Button radius.
- .. versionadded:: 1.0.0
-
- :attr:`rounded_button` is a :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[dp(20), dp(20), dp(20), dp(20)]`.
"""
- # Note - _radius must be > 0 to avoid rendering issues.
- _radius = BoundedNumericProperty(dp(4), min=0.0999, errorvalue=0.1)
- # Properties used for rendering.
- _disabled_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
- _md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
- _md_bg_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0])
- _line_color = ColorProperty([0.0, 0.0, 0.0, 0.0])
- _line_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0])
- _theme_text_color = OptionProperty(None, options=theme_text_color_options)
- _theme_icon_color = OptionProperty(None, options=theme_text_color_options)
- _text_color = ColorProperty(None)
- _icon_color = ColorProperty(None)
-
- # Defaults which can be overridden in subclasses
- _min_width = NumericProperty(dp(64))
- _min_height = NumericProperty(dp(36))
-
- # Default colors - set to None to use primary theme colors
- _default_md_bg_color = [0.0, 0.0, 0.0, 0.0]
- _default_md_bg_color_disabled = [0.0, 0.0, 0.0, 0.0]
- _default_line_color = [0.0, 0.0, 0.0, 0.0]
- _default_line_color_disabled = [0.0, 0.0, 0.0, 0.0]
- _default_theme_text_color = StringProperty("Primary")
- _default_theme_icon_color = StringProperty("Primary")
- _default_text_color = ColorProperty(None)
- _default_icon_color = ColorProperty(None)
-
- _animation_fade_bg = ObjectProperty(None, allownone=True)
+ # kivymd.uix.button.button.MDButtonIcon object.
+ _button_icon = ObjectProperty()
+ # kivymd.uix.button.button.MDButtonText object.
+ _button_text = ObjectProperty()
+
+ _icon_left_pad = dp(16)
+ _spacing_between_icon_text = dp(10)
+ _text_right_pad = dp(24)
+ _text_left_pad = dp(24)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.theme_cls.bind(
- primary_palette=self.set_all_colors,
- theme_style=self.set_all_colors,
- )
- self.bind(
- md_bg_color=self.set_button_colors,
- md_bg_color_disabled=self.set_button_colors,
- line_color=self.set_button_colors,
- line_color_disabled=self.set_button_colors,
- theme_text_color=self.set_text_color,
- text_color=self.set_text_color,
- theme_icon_color=self.set_icon_color,
- icon_color=self.set_icon_color,
- disabled_color=self.set_disabled_color,
- rounded_button=self.set_radius,
- height=self.set_radius,
- )
- Clock.schedule_once(self.set_all_colors)
- Clock.schedule_once(self.set_radius)
-
- def set_disabled_color(self, *args):
- """
- Sets the color for the icon, text and line of the button when button
- is disabled.
- """
-
- if self.disabled:
- disabled_color = (
- self.disabled_color
- if self.disabled_color
- else self.theme_cls.disabled_hint_text_color
- )
- self._disabled_color = disabled_color
- # Button icon color.
- if "lbl_ic" in self.ids:
- self.ids.lbl_ic.disabled_color = disabled_color
- # Button text color.
- if "lbl_txt" in self.ids:
- self.ids.lbl_txt.disabled_color = disabled_color
- else:
- self._disabled_color = self._line_color
-
- def set_all_colors(self, *args) -> None:
- """Set all button colours."""
-
- self.set_button_colors()
- self.set_text_color()
- self.set_icon_color()
-
- def set_button_colors(self, *args) -> None:
- """Set all button colours (except text/icons)."""
-
- # Set main color
- _md_bg_color = (
- self.md_bg_color
- or self._default_md_bg_color
- or self.theme_cls.primary_color
- )
-
- # Set disabled color
- _md_bg_color_disabled = (
- self.md_bg_color_disabled
- or (
- [sum(self.md_bg_color[0:3]) / 3.0] * 3
- + [0.38 if self.theme_cls.theme_style == "Light" else 0.5]
- if self.md_bg_color
- else None
- )
- or self._default_md_bg_color_disabled
- or self.theme_cls.disabled_primary_color
- )
-
- # Set line color
- _line_color = (
- self.line_color
- or self._default_line_color
- or self.theme_cls.primary_color
- )
-
- # Set disabled line color
- _line_color_disabled = (
- self.line_color_disabled
- or (
- [sum(self.line_color[0:3]) / 3.0] * 3
- + [0.38 if self.theme_cls.theme_style == "Light" else 0.5]
- if self.line_color
- else None
+ Clock.schedule_once(self.adjust_width, 0.2)
+ Clock.schedule_once(self.adjust_pos, 0.2)
+
+ def adjust_pos(self, *args) -> None:
+ """Adjusts the pos of the button according to the content."""
+
+ if self._button_icon and self._button_text:
+ self._button_text.x = (
+ self._button_icon.x
+ + self._spacing_between_icon_text
+ + self._icon_left_pad
+ + dp(2)
)
- or self._default_line_color_disabled
- or self.theme_cls.disabled_primary_color
- )
-
- if self.theme_cls.theme_style_switch_animation:
- Animation(
- _md_bg_color=_md_bg_color,
- _md_bg_color_disabled=_md_bg_color_disabled,
- _line_color=_line_color,
- _line_color_disabled=_line_color_disabled,
- d=self.theme_cls.theme_style_switch_animation_duration,
- t="linear",
- ).start(self)
- else:
- self._md_bg_color = _md_bg_color
- self._md_bg_color_disabled = _md_bg_color_disabled
- self._line_color = _line_color
- self._line_color_disabled = _line_color_disabled
-
- def set_text_color(self, *args) -> None:
- """
- Set _theme_text_color and _text_color based on defaults and options.
- """
-
- self._theme_text_color = (
- self.theme_text_color or self._default_theme_text_color
- )
- if self._default_text_color == "PrimaryHue":
- default_text_color = text_colors[self.theme_cls.primary_palette][
- self.theme_cls.primary_hue
- ]
- elif self._default_text_color == "Primary":
- default_text_color = self.theme_cls.primary_color
- else:
- default_text_color = self.theme_cls.text_color
- self._text_color = self.text_color or default_text_color
-
- def set_icon_color(self, *args) -> None:
- """
- Set _theme_icon_color and _icon_color based on defaults and options.
- """
-
- self._theme_icon_color = (
- (self.theme_icon_color or self._default_theme_icon_color)
- if not self.disabled
- else "Custom"
- )
- if self._default_icon_color == "PrimaryHue":
- default_icon_color = text_colors[self.theme_cls.primary_palette][
- self.theme_cls.primary_hue
- ]
- elif self._default_icon_color == "Primary":
- default_icon_color = self.theme_cls.primary_color
- else:
- default_icon_color = self.theme_cls.text_color
- self._icon_color = self.icon_color or default_icon_color
-
- def set_radius(self, *args) -> None:
- """
- Set the radius, if we are a rounded button, based on the
- current height.
- """
-
- if self.rounded_button:
- self._radius = self.height / 2
-
- # Touch events that cause transparent buttons to fade to background
- def on_touch_down(self, touch):
- """
- Animates fade to background on press, for buttons with no
- background color.
- """
-
- if touch.is_mouse_scrolling:
- return False
- elif not self.collide_point(touch.x, touch.y):
- return False
- elif self in touch.ud:
- return False
- elif self.disabled:
- return False
- else:
- if self._md_bg_color[3] == 0.0:
- self._animation_fade_bg = Animation(
- duration=0.5, _md_bg_color=[0.0, 0.0, 0.0, 0.1]
+ elif not self._button_icon and self._button_text:
+ self._button_text.x = self._text_left_pad
+
+ def adjust_width(self, *args) -> None:
+ """Adjusts the width of the button according to the content."""
+
+ if self._button_icon and self._button_text:
+ if self.theme_width == "Primary":
+ self.width = (
+ self._button_icon.texture_size[0]
+ + self._button_text.texture_size[0]
+ + self._icon_left_pad
+ + self._spacing_between_icon_text
+ + self._text_right_pad
+ )
+ elif not self._button_icon and self._button_text:
+ if self.theme_width == "Primary":
+ self.width = (
+ self._button_text.texture_size[0]
+ + self._text_left_pad
+ + self._text_right_pad
+ )
+ elif self._button_icon and not self._button_text:
+ if self.theme_width == "Primary":
+ self.width = (
+ dp(48)
+ + self._button_icon.texture_size[0]
+ - self._spacing_between_icon_text
)
- self._animation_fade_bg.start(self)
- return super().on_touch_down(touch)
-
- def on_touch_up(self, touch):
- """Animates return to original background on touch release."""
-
- if not self.disabled and self._animation_fade_bg:
- self._animation_fade_bg.stop_property(self, "_md_bg_color")
- self._animation_fade_bg = None
- md_bg_color = (
- self.md_bg_color
- or self._default_md_bg_color
- or self.theme_cls.primary_color
- )
- Animation(duration=0.05, _md_bg_color=md_bg_color).start(self)
- return super().on_touch_up(touch)
-
- def on_disabled(self, instance_button, disabled_value: bool) -> None:
- if hasattr(super(), "on_disabled"):
- if self.disabled is True:
- Animation.cancel_all(self, "elevation")
- super().on_disabled(instance_button, disabled_value)
- Clock.schedule_once(self.set_disabled_color)
-
-class ButtonElevationBehaviour(CommonElevationBehavior):
- """
- Implements elevation behavior as well as the recommended down/disabled
- colors for raised buttons.
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDButtonText):
+ self._button_text = widget
+ widget.bind(
+ text=lambda x, y: Clock.schedule_once(self.adjust_width, 0.2)
+ )
+ widget._button = self
+ elif isinstance(widget, MDButtonIcon):
+ self._button_icon = widget
+ widget._button = self
+ if isinstance(widget, (MDButtonIcon, MDButtonText)):
+ return super().add_widget(widget)
- The minimum elevation for any raised button is `'1dp'`,
- by default, set to `'2dp'`.
+ def set_properties_widget(self) -> None:
+ """Fired `on_release/on_press/on_enter/on_leave` events."""
- The `_elevation_raised` is automatically computed and is set to
- `self.elevation + 6` each time `self.elevation` is updated.
- """
+ super().set_properties_widget()
- _elevation_raised = NumericProperty()
- _anim_raised = ObjectProperty(None, allownone=True)
- _default_elevation = 2
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- if self.elevation == 0:
- self.elevation = self._default_elevation
- if hasattr(self, "radius"):
- self.bind(_radius=self.setter("radius"))
- Clock.schedule_once(self.create_anim_raised)
- self.on_disabled(self, self.disabled)
-
- def create_anim_raised(self, *args) -> None:
- 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:
- if touch.is_mouse_scrolling:
- return False
- if not self.collide_point(touch.x, touch.y):
- return False
- if self in touch.ud:
- return False
- 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 self in touch.ud:
- self.stop_elevation_anim()
- return super().on_touch_up(touch)
- return super().on_touch_up(touch)
-
- def stop_elevation_anim(self):
- Animation.cancel_all(self, "elevation")
- if self._anim_raised and self.elevation:
- self.elevation = self._elevation_raised
-
-
-class ButtonContentsText:
- """Contents for :class:`~BaseButton` class consisting of a single label."""
-
-
-class ButtonContentsIcon:
- """
- Contents for a round BaseButton consisting of an :class:`~MDIcon` class.
- """
-
- _min_width = NumericProperty(0)
-
- def on_text_color(self, instance_button, color: list) -> None:
- """
- Set icon_color equal to text_color.
- For backwards compatibility - can use text_color instead
- of icon_color.
- """
-
- if color:
- self.icon_color = color
-
-
-class ButtonContentsIconText:
- """
- Contents for :class:`~BaseButton` class consisting of a
- :class:`~kivy.uix.boxlayout.BoxLayout` with an icon and a label.
- """
-
- padding = VariableListProperty([dp(12), dp(8), dp(16), dp(8)])
- """
- Padding between the widget box and its children, in pixels:
- [padding_left, padding_top, padding_right, padding_bottom].
-
- padding also accepts a two argument form [padding_horizontal,
- padding_vertical] and a one argument form [padding].
-
- .. versionadded:: 1.0.0
-
- :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
- and defaults to [12dp, 8dp, 16dp, 8dp].
- """
-
-
-# Old MD Button classes
-
-
-class OldButtonIconMixin:
- """Backwards-compatibility for icons."""
-
- icon = StringProperty("android")
-
- def on_icon_color(self, instance_button, color: list) -> None:
- """
- If we are setting an icon color, set theme_icon_color to Custom.
- For backwards compatibility (before theme_icon_color existed).
- """
-
- if color and (self.theme_text_color == "Custom"):
- self.theme_icon_color = "Custom"
-
-
-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)])
- """
- Padding between the widget box and its children, in pixels:
- [padding_left, padding_top, padding_right, padding_bottom].
-
- padding also accepts a two argument form [padding_horizontal,
- padding_vertical] and a one argument form [padding].
-
- .. versionadded:: 1.0.0
-
- :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
- and defaults to [8dp, 8dp, 8dp, 8dp].
- """
-
-
-class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText):
- """
- A flat button with (by default) a primary color fill and matching
- color text.
+ if self._state == self.state_hover and self.focus_behavior:
+ if self.style == "filled":
+ self.elevation_level = 1
+ self.shadow_softness = 0
+ elif self.style == "tonal":
+ self.elevation_level = 1
+ else:
+ self.elevation_level = 2
+ self.shadow_softness = 2
+ elif self._state == self.state_press:
+ if self.style == "elevated":
+ self.elevation_level = 2
+ self.shadow_softness = 2
+ elif self.style == "tonal":
+ self.elevation_level = 0
+ elif self.style == "filled":
+ self.elevation_level = 0
+ self.shadow_softness = 0
+ elif not self._state:
+ if self.style == "elevated":
+ self.elevation_level = 1
+ self.shadow_softness = 0
+ elif self.style == "tonal":
+ self.elevation_level = 0
+ elif self.style == "filled":
+ self.elevation_level = 0
+ self.shadow_softness = 0
+
+
+class MDButtonText(MDLabel):
+ """
+ The class implements the text for the :class:`~MDButton` class.
For more information, see in the
- :class:`~BaseButton` and
- :class:`~ButtonElevationBehaviour` and
- :class:`~ButtonContentsText`
- classes documentation.
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
"""
- # FIXME: Move the underlying attributes to the :class:`~BaseButton` class.
- # This applies to all classes of buttons that have similar attributes.
- _default_md_bg_color = None
- _default_md_bg_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_text_color = "PrimaryHue"
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.shadow_softness = RAISED_BUTTON_SOFTNESS
- self.shadow_offset = RAISED_BUTTON_OFFSET
- # self.shadow_radius = self._radius * 2
+ # kivymd.uix.button.button.MDButton object.
+ _button = ObjectProperty()
-class MDRectangleFlatButton(BaseButton, ButtonContentsText):
+class MDButtonIcon(MDIcon):
"""
- A flat button with (by default) a primary color border and primary
- color text.
+ The class implements an icon for the :class:`~MDButton` class.
For more information, see in the
- :class:`~BaseButton` and :class:`~ButtonContentsText`
- classes documentation.
+ :class:`~kivymd.uix.label.label.MDIcon` class documentation.
"""
- _default_line_color = None
- _default_line_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_text_color = "Primary"
+ # kivymd.uix.button.button.MDButton object.
+ _button = ObjectProperty()
-class MDRectangleFlatIconButton(
- BaseButton, OldButtonIconMixin, ButtonContentsIconText
-):
+class MDIconButton(RectangularRippleBehavior, ButtonBehavior, MDIcon):
"""
- A flat button with (by default) a primary color border, primary color text
- and a primary color icon on the left.
+ Base class for icon buttons.
For more information, see in the
- :class:`~BaseButton` and
- :class:`~OldButtonIconMixin` and
- :class:`~ButtonContentsIconText`
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivy.uix.label.label.MDIcon`
classes documentation.
"""
- _default_line_color = None
- _default_line_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_theme_icon_color = "Custom"
- _default_text_color = "Primary"
- _default_icon_color = "Primary"
-
-
-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.
+ style = OptionProperty(
+ "standard", options=("standard", "filled", "tonal", "outlined")
+ )
"""
+ Button type.
- _default_line_color = None
- _default_line_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_text_color = "Primary"
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.rounded_button = True
+ .. versionadded:: 2.0.0
+ Available options are: 'standard', 'filled', 'tonal', 'outlined'.
-class MDRoundFlatIconButton(
- BaseButton, OldButtonIconMixin, ButtonContentsIconText
-):
+ :attr:`style` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'standard'`.
"""
- 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.
+ md_bg_color_disabled = ColorProperty(None)
"""
+ The background color in (r, g, b, a) or string format of the list item when
+ the list button is disabled.
- _default_line_color = None
- _default_line_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_theme_icon_color = "Custom"
- _default_text_color = "Primary"
- _default_icon_color = "Primary"
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.rounded_button = True
-
-
-class MDFillRoundFlatButton(BaseButton, ButtonContentsText):
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- 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.
- """
+ _line_color = ColorProperty(None)
- _default_md_bg_color = None
- _default_md_bg_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_text_color = "PrimaryHue"
+ def on_line_color(self, instance, value) -> None:
+ """Fired when the values of :attr:`line_color` change."""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.rounded_button = True
+ if not self.disabled and self.theme_line_color == "Custom":
+ self._line_color = value
-class MDFillRoundFlatIconButton(
- BaseButton, OldButtonIconMixin, ButtonContentsIconText
+class MDFabButton(
+ BaseFabButton,
+ CommonElevationBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ MDIcon,
):
"""
- A flat button with (by default) rounded corners, a primary color fill,
- primary color text and a primary color icon on the left.
+ Base class for FAB buttons.
For more information, see in the
- :class:`~BaseButton` and
- :class:`~OldButtonIconMixin` and
- :class:`~ButtonContentsIconText`
+ :class:`~BaseFabButton` and
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
- _default_md_bg_color = None
- _default_md_bg_color_disabled = None
- _default_theme_text_color = "Custom"
- _default_theme_icon_color = "Custom"
- _default_text_color = "PrimaryHue"
- _default_icon_color = "PrimaryHue"
+ def on_press(self, *args) -> None:
+ """Fired when the button is pressed."""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.rounded_button = True
-
-
-class MDIconButton(BaseButton, OldButtonIconMixin, ButtonContentsIcon):
- """
- 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")
- """
- Button icon.
+ self._on_press(args)
- :attr:`icon` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'checkbox-blank-circle'`.
- """
-
- _min_width = NumericProperty(0)
- _default_icon_pad = max(dp(48) - sp(24), 0)
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.rounded_button = True
- # FIXME: GraphicException: Invalid width value, must be > 0
- self.line_width = 0.001
- Clock.schedule_once(self.set_size)
-
- def set_size(self, interval: Union[int, float]) -> None:
+ def on_release(self, *args) -> None:
"""
- Sets the icon width/height based on the current `icon_size`
- attribute, or the default value if it is zero. The icon size
- is set to `(48, 48)` for an icon with the default font_size 24sp.
+ Fired when the button is released
+ (i.e. the touch/click that pressed the button goes away).
"""
- diameter = self._default_icon_pad + (self.icon_size or sp(24))
- self.width = diameter
- self.height = diameter
-
-
-class MDFloatingActionButton(
- BaseButton, OldButtonIconMixin, ButtonElevationBehaviour, ButtonContentsIcon
-):
- """
- Implementation
- `FAB `_
- 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"])
- """
- Type of M3 button.
- .. versionadded:: 1.0.0
+ self._on_release(args)
- Available options are: 'small', 'large', 'standard'.
+ def set_properties_widget(self) -> None:
+ """Fired `on_release/on_press/on_enter/on_leave` events."""
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-types.png
- :align: center
+ super().set_properties_widget()
- :attr:`type` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'standard'`.
- """
+ if not self.disabled:
+ if (
+ self._state == self.state_hover
+ and self.focus_behavior
+ or not self._state
+ ):
+ self.elevation_level = 2
+ self.shadow_softness = 2
+ if self._state == self.state_press:
+ self.elevation_level = 3
+ self.shadow_softness = 4
- _default_md_bg_color = None
- _default_md_bg_color_disabled = None
- _default_theme_icon_color = "Custom"
- _default_icon_color = "PrimaryHue"
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # FIXME: GraphicException: Invalid width value, must be > 0
- self.line_width = 0.001
- self.theme_cls.bind(material_style=self.set_size_and_radius)
- Clock.schedule_once(self.set_size)
- Clock.schedule_once(self.set__radius)
- Clock.schedule_once(self.set_font_size)
-
- def set_font_size(self, *args) -> None:
- if self.theme_cls.material_style == "M3":
- if self.type == "large":
- self.icon_size = "36sp"
- else:
- self.icon_size = 0
-
- 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 = 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":
- self._radius = dp(12)
- elif self.type == "standard":
- self._radius = dp(16)
- elif self.type == "large":
- self._radius = dp(28)
-
- self.shadow_radius = self._radius
-
- def set_size_and_radius(self, *args) -> None:
- self.set_size(args)
- self.set__radius(args)
-
- def set_size(self, *args) -> None:
- if self.theme_cls.material_style == "M2":
- self.size = dp(56), dp(56)
- else:
- if self.type == "small":
- self.size = dp(40), dp(40)
- elif self.type == "standard":
- self.size = dp(56), dp(56)
- elif self.type == "large":
- self.size = dp(96), dp(96)
-
- def on_type(self, instance_md_floating_action_button, type: str) -> None:
- self.set_size()
- self.set_font_size()
-
-
-class MDTextButton(ButtonBehavior, MDLabel):
+class MDExtendedFabButtonIcon(MDIcon):
"""
- Text button class.
+ Implements an icon for the :class:`~MDExtendedFabButton` class.
- For more information, see in the
- :class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.label.MDLabel` classes documentation.
+ .. versionadded:: 2.0.0
"""
- color = ColorProperty(None)
- """
- Button color in (r, g, b, a) or string format.
- :attr:`color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- color_disabled = ColorProperty(None)
+class MDExtendedFabButtonText(MDLabel):
"""
- Button color disabled in (r, g, b, a) or string format.
+ Implements the text for the class :class:`~MDExtendedFabButton' class.
- :attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ .. versionadded:: 2.0.0
"""
- _color = ColorProperty(None) # last current button text color
-
- def animation_label(self) -> None:
- def set_default_state_label(*args):
- Animation(opacity=1, d=0.1, t="in_out_cubic").start(self)
-
- anim = Animation(opacity=0.5, d=0.2, t="in_out_cubic")
- anim.bind(on_complete=set_default_state_label)
- anim.start(self)
-
- def on_press(self, *args):
- self.animation_label()
- return super().on_press(*args)
-
- def on_disabled(self, instance_button, disabled_value) -> None:
- if disabled_value:
- if not self.color_disabled:
- self.color_disabled = self.theme_cls.disabled_hint_text_color
- self._color = self.color
- self.text_color = self.color_disabled
- else:
- self.text_color = self._color
-
-
-# SpeedDial classes
-
-
-class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip):
- _canvas_width = NumericProperty(0)
- _padding_right = NumericProperty(0)
- _bg_color = ColorProperty(None)
- def set_size(self, interval: Union[int, float]) -> None:
- self.width = "46dp"
- self.height = "46dp"
-
-
-class MDFloatingBottomButton(BaseFloatingBottomButton):
- _bg_color = ColorProperty(None)
-
-
-class MDFloatingRootButton(RotateBehavior, MDFloatingActionButton):
- rotate_value_angle = NumericProperty(0)
-
-
-class MDFloatingLabel(MDLabel):
- bg_color = ColorProperty([0, 0, 0, 0])
-
-
-class MDFloatingActionButtonSpeedDial(
- DeclarativeBehavior, ThemableBehavior, FloatLayout
+class MDExtendedFabButton(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ MotionExtendedFabButtonBehavior,
+ CommonElevationBehavior,
+ StateLayerBehavior,
+ BaseFabButton,
+ ButtonBehavior,
+ RelativeLayout,
):
"""
- For more information, see in the
- :class:`~kivy.uix.floatlayout.FloatLayout` class documentation.
+ Base class for Extended FAB buttons.
+
+ .. versionadded:: 2.0.0
For more information, see in the
- :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
- :class:`~kivy.uix.floatlayout.FloatLayout`
- lasses documentation.
+ :class:`~kivymd.uix.behaviors.motion_behavior.MotionExtendedFabButtonBehavior` and
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and
+ :class:`~BaseFabButton` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivy.uix.relativelayout.RelativeLayout`
+ classes documentation.
:Events:
- :attr:`on_open`
- Called when a stack is opened.
- :attr:`on_close`
- Called when a stack is closed.
- :attr:`on_press_stack_button`
- Called at the on_press event for the stack button.
- :attr:`on_release_stack_button`
- Called at the on_press event for the stack button.
- """
-
- icon = StringProperty("plus")
- """
- Root button icon name.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- icon: "pencil"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-icon.png
- :align: center
-
- :attr:`icon` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'plus'`.
- """
-
- anchor = OptionProperty("right", option=["right"])
- """
- Stack anchor. Available options are: `'right'`.
-
- :attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
- and defaults to `'right'`.
- """
-
- label_text_color = ColorProperty(None)
- """
- Color of floating text labels in (r, g, b, a) or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- label_text_color: "orange"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-text-color.png
- :align: center
-
- :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- label_bg_color = ColorProperty([0, 0, 0, 0])
- """
- Background color of floating text labels in (r, g, b, a) or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- label_text_color: "black"
- label_bg_color: "orange"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-bg-color.png
- :align: center
-
- :attr:`label_bg_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0]`.
- """
-
- label_radius = VariableListProperty([0], length=4)
- """
- The radius of the background of floating text labels.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- label_text_color: "black"
- label_bg_color: "orange"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-radius.png
- :align: center
-
- :attr:`label_radius` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0]`.
- """
-
- data = DictProperty()
- """
- Must be a dictionary.
-
- .. code-block:: python
-
- {
- 'name-icon': 'Text label',
- ...,
- ...,
- }
- """
-
- right_pad = BooleanProperty(False)
- """
- If `True`, the background for the floating text label will increase by the
- number of pixels specified in the :attr:`~right_pad_value` parameter.
-
- Works only if the :attr:`~hint_animation` parameter is set to `True`.
-
- .. rubric:: False
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- hint_animation: True
- right_pad: False
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif
- :align: center
-
- .. rubric:: True
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- hint_animation: True
- right_pad: True
- right_pad_value: "10dp"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif
- :align: center
-
- :attr:`right_pad` is a :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- right_pad_value = NumericProperty(0)
- """
- See :attr:`~right_pad` parameter for more information.
-
- :attr:`right_pad_value` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
-
- root_button_anim = BooleanProperty(False)
- """
- If ``True`` then the root button will rotate 45 degrees when the stack
- is opened.
-
- :attr:`root_button_anim` is a :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- opening_transition = StringProperty("out_cubic")
- """
- The name of the stack opening animation type.
-
- :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'out_cubic'`.
- """
-
- closing_transition = StringProperty("out_cubic")
- """
- The name of the stack closing animation type.
-
- :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'out_cubic'`.
- """
-
- opening_transition_button_rotation = StringProperty("out_cubic")
- """
- The name of the animation type to rotate the root button when opening the
- stack.
-
- :attr:`opening_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'out_cubic'`.
- """
-
- closing_transition_button_rotation = StringProperty("out_cubic")
- """
- The name of the animation type to rotate the root button when closing the
- stack.
-
- :attr:`closing_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'out_cubic'`.
- """
-
- opening_time = NumericProperty(0.5)
- """
- Time required for the stack to go to: attr:`state` `'open'`.
-
- :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- closing_time = NumericProperty(0.2)
- """
- Time required for the stack to go to: attr:`state` `'close'`.
-
- :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- opening_time_button_rotation = NumericProperty(0.2)
- """
- Time required to rotate the root button 45 degrees during the stack
- opening animation.
-
- :attr:`opening_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- closing_time_button_rotation = NumericProperty(0.2)
- """
- Time required to rotate the root button 0 degrees during the stack
- closing animation.
-
- :attr:`closing_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- state = OptionProperty("close", options=("close", "open"))
- """
- Indicates whether the stack is closed or open.
- Available options are: `'close'`, `'open'`.
-
- :attr:`state` is a :class:`~kivy.properties.OptionProperty`
- and defaults to `'close'`.
- """
-
- bg_color_root_button = ColorProperty(None)
- """
- Background color of root button in (r, g, b, a) or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- bg_color_root_button: "red"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-root-button.png
- :align: center
-
- :attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- bg_color_stack_button = ColorProperty(None)
- """
- Background color of the stack buttons in (r, g, b, a) or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- bg_color_root_button: "red"
- bg_color_stack_button: "red"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-stack-button.png
- :align: center
-
- :attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- color_icon_stack_button = ColorProperty(None)
- """
- The color icon of the stack buttons in (r, g, b, a) or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- bg_color_root_button: "red"
- bg_color_stack_button: "red"
- color_icon_stack_button: "white"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-stack-button.png
- :align: center
-
- :attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- color_icon_root_button = ColorProperty(None)
- """
- The color icon of the root button in (r, g, b, a) or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- bg_color_root_button: "red"
- bg_color_stack_button: "red"
- color_icon_stack_button: "white"
- color_icon_root_button: self.color_icon_stack_button
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-root-button.png
- :align: center
-
- :attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- bg_hint_color = ColorProperty(None)
- """
- Background color for the floating text of the buttons in (r, g, b, a)
- or string format.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- bg_hint_color: "red"
- hint_animation: True
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-hint-color.png
- :align: center
-
- :attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- hint_animation = BooleanProperty(False)
- """
- Whether to use button extension animation to display floating text.
-
- :attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
+ `on_collapse`
+ Fired when the button is collapsed.
+ `on_expand`
+ Fired when the button is expanded.
"""
- stack_buttons = DictProperty()
-
- _label_pos_y_set = False
- _anim_buttons_data = {}
- _anim_labels_data = {}
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.register_event_type("on_open")
- self.register_event_type("on_close")
- self.register_event_type("on_press_stack_button")
- self.register_event_type("on_release_stack_button")
- Window.bind(on_resize=self._update_pos_buttons)
-
- def on_open(self, *args):
- """Called when a stack is opened."""
-
- def on_close(self, *args):
- """Called when a stack is closed."""
-
- def on_leave(self, instance_button: MDFloatingBottomButton) -> None:
- """Called when the mouse cursor goes outside the button of stack."""
-
- if self.state == "open":
- for widget in self.children:
- if isinstance(widget, MDFloatingLabel) and self.hint_animation:
- Animation.cancel_all(widget)
- for item in self.data.items():
- if widget.text in item:
- Animation(
- _canvas_width=0,
- _padding_right=0,
- d=self.opening_time,
- t=self.opening_transition,
- _elevation=0,
- ).start(instance_button)
- Animation(
- opacity=0, d=0.1, t=self.opening_transition
- ).start(widget)
-
- def on_enter(self, instance_button: MDFloatingBottomButton) -> None:
- """Called when the mouse cursor is over a button from the stack."""
-
- if self.state == "open":
- for widget in self.children:
- if isinstance(widget, MDFloatingLabel) and self.hint_animation:
- Animation.cancel_all(widget)
- for item in self.data.items():
- if widget.text in item:
- Animation(
- _canvas_width=widget.width + dp(24),
- _padding_right=self.right_pad_value
- if self.right_pad
- else 0,
- d=self.opening_time,
- t=self.opening_transition,
- ).start(instance_button)
- if (
- instance_button.icon
- == self.data[f"{widget.text}"]
- or instance_button.icon
- == self.data[f"{widget.text}"][0]
- ):
- Animation(
- opacity=1,
- d=self.opening_time,
- t=self.opening_transition,
- ).start(widget)
- else:
- Animation(
- opacity=0, d=0.1, t=self.opening_transition
- ).start(widget)
-
- def on_data(self, instance_speed_dial, data: dict) -> None:
- """Creates a stack of buttons."""
-
- def on_data(*args):
- # Bottom buttons.
- for name, parameters in data.items():
- name_icon = (
- parameters if (type(parameters) is str) else parameters[0]
- )
-
- bottom_button = MDFloatingBottomButton(
- icon=name_icon,
- on_enter=self.on_enter,
- on_leave=self.on_leave,
- opacity=0,
- )
- bottom_button.bind(
- on_press=lambda x: self.dispatch("on_press_stack_button"),
- on_release=lambda x: self.dispatch(
- "on_release_stack_button"
- ),
- )
-
- if "on_press" in parameters:
- callback = parameters[parameters.index("on_press") + 1]
- bottom_button.bind(on_press=callback)
-
- if "on_release" in parameters:
- callback = parameters[parameters.index("on_release") + 1]
- bottom_button.bind(on_release=callback)
-
- self.set_pos_bottom_buttons(bottom_button)
- self.add_widget(bottom_button)
- self.stack_buttons[name] = WeakProxy(bottom_button)
- # Labels.
- floating_text = name
- if floating_text:
- label = MDFloatingLabel(text=floating_text, opacity=0)
- label.bg_color = self.label_bg_color
- label.radius = self.label_radius
- label.text_color = (
- self.label_text_color
- if self.label_text_color
- else self.theme_cls.text_color
- )
- self.add_widget(label)
- # Top root button.
- root_button = MDFloatingRootButton(on_release=self.open_stack)
- root_button.icon = self.icon
- self.set_pos_root_button(root_button)
- self.add_widget(root_button)
-
- self.clear_widgets()
- self.stack_buttons = {}
- self._anim_buttons_data = {}
- self._anim_labels_data = {}
- self._label_pos_y_set = False
- Clock.schedule_once(on_data)
-
- def on_icon(self, instance_speed_dial, name_icon: str) -> None:
- self._set_button_property(MDFloatingRootButton, "icon", name_icon)
-
- def on_label_text_color(
- self, instance_speed_dial, color: list | str
- ) -> None:
- for widget in self.children:
- if isinstance(widget, MDFloatingLabel):
- widget.text_color = color
-
- def on_color_icon_stack_button(
- self, instance_speed_dial, color: list
- ) -> None:
- self._set_button_property(MDFloatingBottomButton, "icon_color", color)
-
- def on_hint_animation(self, instance_speed_dial, value: bool) -> None:
- for widget in self.children:
- if isinstance(widget, MDFloatingLabel):
- widget.md_bg_color = (0, 0, 0, 0)
-
- def on_bg_hint_color(self, instance_speed_dial, color: list) -> None:
- setattr(MDFloatingBottomButton, "_bg_color", color)
-
- def on_color_icon_root_button(
- self, instance_speed_dial, color: list
- ) -> None:
- self._set_button_property(MDFloatingRootButton, "icon_color", color)
-
- def on_bg_color_stack_button(
- self, instance_speed_dial, color: list
- ) -> None:
- self._set_button_property(MDFloatingBottomButton, "md_bg_color", color)
-
- def on_bg_color_root_button(self, instance_speed_dial, color: list) -> None:
- self._set_button_property(MDFloatingRootButton, "md_bg_color", color)
-
- def on_press_stack_button(self, *args) -> None:
- """
- Called at the on_press event for the stack button.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- on_press_stack_button: print(*args)
-
- .. versionadded:: 1.1.0
- """
-
- def on_release_stack_button(self, *args) -> None:
- """
- Called at the on_release event for the stack button.
-
- .. code-block:: kv
-
- MDFloatingActionButtonSpeedDial:
- on_release_stack_button: print(*args)
-
- .. versionadded:: 1.1.0
- """
-
- def set_pos_labels(self, instance_floating_label: MDFloatingLabel) -> None:
- """
- Sets the position of the floating labels.
- Called when the application's root window is resized.
- """
-
- if self.anchor == "right":
- instance_floating_label.x = (
- Window.width - instance_floating_label.width - dp(86)
- )
-
- def set_pos_root_button(
- self, instance_floating_root_button: MDFloatingRootButton
- ) -> None:
- """
- Sets the position of the root button.
- Called when the application's root window is resized.
- """
-
- def set_pos_root_button(*args):
- if self.anchor == "right":
- instance_floating_root_button.y = dp(20)
- instance_floating_root_button.x = self.parent.width - (
- dp(56) + dp(20)
- )
-
- Clock.schedule_once(set_pos_root_button)
-
- def set_pos_bottom_buttons(
- self, instance_floating_bottom_button: MDFloatingBottomButton
- ) -> None:
- """
- Sets the position of the bottom buttons in a stack.
- Called when the application's root window is resized.
- """
+ _icon = ObjectProperty()
+ _label = ObjectProperty()
- if self.anchor == "right":
- if self.state != "open":
- instance_floating_bottom_button.y = (
- instance_floating_bottom_button.height / 2
- )
- instance_floating_bottom_button.x = Window.width - (
- instance_floating_bottom_button.height
- + instance_floating_bottom_button.width / 2
- )
-
- def open_stack(
- self, instance_floating_root_button: MDFloatingRootButton
- ) -> None:
- """Opens a button stack."""
-
- for widget in self.children:
- if isinstance(widget, MDFloatingLabel):
- Animation.cancel_all(widget)
-
- if self.state != "open":
- y = 0
- label_position = dp(54)
- anim_buttons_data = {}
- anim_labels_data = {}
-
- for widget in self.children:
- if isinstance(widget, MDFloatingBottomButton):
- # Sets new button positions.
- y += dp(56)
- widget.y = widget.y * 2 + y
- if not self._anim_buttons_data:
- anim_buttons_data[widget] = Animation(
- opacity=1,
- d=self.opening_time,
- t=self.opening_transition,
- )
- elif isinstance(widget, MDFloatingLabel):
- # Sets new labels positions.
- label_position += dp(56)
- # Sets the position of signatures only once.
- if not self._label_pos_y_set:
- widget.y = widget.y * 2 + label_position
- widget.x = Window.width - widget.width - dp(86)
- if not self._anim_labels_data:
- anim_labels_data[widget] = Animation(
- opacity=1, d=self.opening_time
- )
- elif (
- isinstance(widget, MDFloatingRootButton)
- and self.root_button_anim
- ):
- # Rotates the root button 45 degrees.
- Animation(
- rotate_value_angle=-45,
- d=self.opening_time_button_rotation,
- t=self.opening_transition_button_rotation,
- ).start(widget)
-
- if anim_buttons_data:
- self._anim_buttons_data = anim_buttons_data
- if anim_labels_data and not self.hint_animation:
- self._anim_labels_data = anim_labels_data
-
- self.state = "open"
- self.dispatch("on_open")
- self.do_animation_open_stack(self._anim_buttons_data)
- self.do_animation_open_stack(self._anim_labels_data)
- if not self._label_pos_y_set:
- self._label_pos_y_set = True
- else:
- self.close_stack()
-
- def do_animation_open_stack(self, anim_data: dict) -> None:
- """
- :param anim_data:
- {
- :
- ,
- :
- ,
- ...,
- }
- """
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_event_type("on_collapse")
+ self.register_event_type("on_expand")
+ Clock.schedule_once(self._set_text_pos, 0.5)
+
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDExtendedFabButtonIcon):
+ self._icon = widget
+ elif isinstance(widget, MDExtendedFabButtonText):
+ self._label = widget
+ widget.opacity = 0
+
+ return super().add_widget(widget)
+
+ def on_collapse(self, *args):
+ """Fired when the button is collapsed."""
+
+ def on_expand(self, *args):
+ """Fired when the button is expanded."""
+
+ def on_fab_state(self, instance, state: str) -> None:
+ """Fired when the :attr:`fab_state` value changes."""
+
+ if state == "expand":
+ Clock.schedule_once(self.expand)
+ Clock.schedule_once(lambda x: self.dispatch("on_expand"))
+ elif state == "collapse":
+ Clock.schedule_once(self.collapse)
+ Clock.schedule_once(lambda x: self.dispatch("on_collapse"))
+
+ def on__x(self, instance, value) -> None:
+ self._label.x = (
+ self._icon.x + self._icon.texture_size[0] + dp(24) - value
+ )
- def on_progress(animation, widget, value):
- if value >= 0.1:
- animation_open_stack()
-
- def animation_open_stack(*args):
- try:
- widget = next(widgets_list)
- animation = anim_data[widget]
- animation.bind(on_progress=on_progress)
- animation.start(widget)
- except StopIteration:
- pass
-
- widgets_list = iter(list(anim_data.keys()))
- animation_open_stack()
-
- def close_stack(self):
- """Closes the button stack."""
-
- for widget in self.children:
- if isinstance(widget, MDFloatingBottomButton):
- Animation(
- y=widget.height / 2,
- d=self.closing_time,
- t=self.closing_transition,
- opacity=0,
- ).start(widget)
- elif isinstance(widget, MDFloatingLabel):
- if widget.opacity > 0:
- Animation(opacity=0, d=0.1).start(widget)
- elif (
- isinstance(widget, MDFloatingRootButton)
- and self.root_button_anim
- ):
- Animation(
- rotate_value_angle=0,
- d=self.closing_time_button_rotation,
- t=self.closing_transition_button_rotation,
- ).start(widget)
- self.state = "close"
- self.dispatch("on_close")
-
- def _update_pos_buttons(self, instance, width, height):
- # Updates button positions when resizing screen.
- for widget in self.children:
- if isinstance(widget, MDFloatingBottomButton):
- self.set_pos_bottom_buttons(widget)
- elif isinstance(widget, MDFloatingRootButton):
- self.set_pos_root_button(widget)
- elif isinstance(widget, MDFloatingLabel):
- self.set_pos_labels(widget)
-
- def _set_button_property(
- self, instance, property_name: str, property_value: str | list
- ):
- def set_count_widget(*args):
- if self.children:
- for widget in self.children:
- if isinstance(widget, instance):
- setattr(instance, property_name, property_value)
- Clock.unschedule(set_count_widget)
- break
-
- Clock.schedule_interval(set_count_widget, 0)
+ def _set_text_pos(self, *args):
+ if self._icon and self._label:
+ self._label.x = self._icon.x + self._icon.texture_size[0] + dp(24)
+ elif not self._icon and self._label:
+ self._label.opacity = 1
+ self.width = self._label.texture_size[0] + dp(32)
+ self._label.pos_hint = {"center_x": 0.5}
diff --git a/kivymd/uix/card/__init__.py b/kivymd/uix/card/__init__.py
index 7577fc990..9bcb032bd 100644
--- a/kivymd/uix/card/__init__.py
+++ b/kivymd/uix/card/__init__.py
@@ -4,5 +4,4 @@
MDCardSwipe,
MDCardSwipeFrontBox,
MDCardSwipeLayerBox,
- MDSeparator,
)
diff --git a/kivymd/uix/card/card.kv b/kivymd/uix/card/card.kv
index 7ed37bd78..3669d9b9f 100644
--- a/kivymd/uix/card/card.kv
+++ b/kivymd/uix/card/card.kv
@@ -1,9 +1,78 @@
:
- md_bg_color: app.theme_cls.divider_color
+ md_bg_color:
+ app.theme_cls.secondaryContainerColor \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color
-
+
+ shadow_radius: [dp(14), ]
+ shadow_color:
+ ( \
+ self.theme_cls.shadowColor \
+ if self.theme_shadow_color == "Primary" else \
+ self.shadow_color \
+ ) \
+ if not self.disabled else \
+ { \
+ "filled": \
+ self.theme_cls.shadowColor[:-1] + [.5] \
+ if self.theme_shadow_color == "Primary" else \
+ self.shadow_color[:-1] + [.5], \
+ "outlined": self.theme_cls.transparentColor, \
+ "elevated": self.theme_cls.shadowColor[:-1] + [.5] \
+ if self.theme_shadow_color == "Primary" else \
+ self.shadow_color[:-1] + [.5],
+ }[self.style]
+ shadow_offset:
+ ( \
+ { \
+ "filled": [0, 0], \
+ "outlined": [0, 0], \
+ "elevated": [0, -1], \
+ }[self.style] \
+ if not self.disabled else \
+ { \
+ "filled": [0, -1], \
+ "outlined": [0, 0], \
+ "elevated": [0, -1], \
+ }[self.style] \
+ ) \
+ if self.theme_shadow_offset == "Primary" else self.shadow_offset
+ elevation_level:
+ (
+ { \
+ "filled": 0, \
+ "outlined": 0, \
+ "elevated": self.elevation_level if self.elevation_level else 1, \
+ }[self.style] \
+ ) \
+ if not self.disabled else \
+ { \
+ "filled": 1, \
+ "outlined": 0, \
+ "elevated": self.elevation_level if self.elevation_level else 1, \
+ }[self.style]
+ elevation: self.elevation_levels[self.elevation_level]
+ line_color:
+ (\
+ ( \
+ self.theme_cls.outlineColor \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.button_outlined_opacity_value_disabled_line] \
+ ) \
+ if self.style == "outlined" else \
+ self.theme_cls.transparentColor \
+ ) \
+ if self.theme_line_color == "Primary" else self.line_color
md_bg_color:
- self.theme_cls.divider_color \
- if not root.color \
- else root.color
+ ( \
+ { \
+ "filled": app.theme_cls.surfaceContainerHighestColor, \
+ "outlined": app.theme_cls.surfaceColor, \
+ "elevated": app.theme_cls.surfaceContainerLowColor, \
+ }[self.style] \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color \
+ )
diff --git a/kivymd/uix/card/card.py b/kivymd/uix/card/card.py
index 7e0bdf9ac..ab4cd5ee3 100755
--- a/kivymd/uix/card/card.py
+++ b/kivymd/uix/card/card.py
@@ -4,8 +4,7 @@
.. seealso::
- `Material Design spec, Cards `_ and
- `Material Design 3 spec, Cards `_
+ `Material Design 3 spec, Cards `_
.. rubric:: Cards contain content and actions about a single subject.
@@ -18,16 +17,25 @@
- MDCardSwipe_
.. note:: :class:`~MDCard` inherited from
- :class:`~kivy.uix.boxlayout.BoxLayout`. You can use all parameters and
- attributes of the :class:`~kivy.uix.boxlayout.BoxLayout` class in the
+ :class:`~kivymd.uix.boxlayout.MDBoxLayout`. You can use all parameters and
+ attributes of the :class:`~kivymd.uix.boxlayout.MDBoxLayout` class in the
:class:`~MDCard` class.
.. MDCard:
MDCard
------
-An example of the implementation of a card in the style of material design version 3
-------------------------------------------------------------------------------------
+There are three types of cards: elevated, filled, and outlined:
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/available-cards.png
+ :align: center
+
+1. Elevated card
+2. Filled card
+3. Outlined card
+
+Example
+-------
.. tabs::
@@ -42,10 +50,10 @@
from kivymd.uix.card import MDCard
KV = '''
-
- padding: 4
+
+ padding: "4dp"
size_hint: None, None
- size: "200dp", "100dp"
+ size: "240dp", "100dp"
MDRelativeLayout:
@@ -54,7 +62,6 @@
pos_hint: {"top": 1, "right": 1}
MDLabel:
- id: label
text: root.text
adaptive_size: True
color: "grey"
@@ -63,39 +70,31 @@
MDScreen:
+ theme_bg_color: "Custom"
+ md_bg_color: self.theme_cls.backgroundColor
MDBoxLayout:
id: box
adaptive_size: True
- spacing: "56dp"
+ spacing: "12dp"
pos_hint: {"center_x": .5, "center_y": .5}
'''
- class MD3Card(MDCard):
- '''Implements a material design v3 card.'''
+ class MyCard(MDCard):
+ '''Implements a material card.'''
text = StringProperty()
class Example(MDApp):
def build(self):
- self.theme_cls.material_style = "M3"
return Builder.load_string(KV)
def on_start(self):
- styles = {
- "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4"
- }
- for style in styles.keys():
+ for style in ("elevated", "filled", "outlined"):
self.root.ids.box.add_widget(
- MD3Card(
- line_color=(0.2, 0.2, 0.2, 0.8),
- style=style,
- text=style.capitalize(),
- md_bg_color=styles[style],
- shadow_offset=(0, -1),
- )
+ MyCard(style=style, text=style.capitalize())
)
@@ -114,31 +113,29 @@ def on_start(self):
from kivymd.uix.screen import MDScreen
- class MD3Card(MDCard):
- '''Implements a material design v3 card.'''
+ class MyCard(MDCard):
+ '''Implements a material card.'''
class Example(MDApp):
def build(self):
- self.theme_cls.material_style = "M3"
return (
MDScreen(
MDBoxLayout(
id="box",
adaptive_size=True,
- spacing="56dp",
+ spacing="12dp",
pos_hint={"center_x": 0.5, "center_y": 0.5},
- )
+ ),
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.backgroundColor,
)
)
def on_start(self):
- styles = {
- "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4"
- }
- for style in styles.keys():
+ for style in ("elevated", "filled", "outlined"):
self.root.ids.box.add_widget(
- MD3Card(
+ MyCard(
MDRelativeLayout(
MDIconButton(
icon="dots-vertical",
@@ -147,22 +144,114 @@ def on_start(self):
MDLabel(
text=style.capitalize(),
adaptive_size=True,
- color="grey",
pos=("12dp", "12dp"),
),
),
- line_color=(0.2, 0.2, 0.2, 0.8),
style=style,
- text=style.capitalize(),
- md_bg_color=styles[style],
- shadow_offset=(0, -1),
+ padding="4dp",
+ size_hint=(None, None),
+ size=("240dp", "100dp"),
+ ripple_behavior=True,
)
)
Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards-m3.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-usage.png
+ :align: center
+
+Elevated
+--------
+
+.. code-block:: kv
+
+ MDCard
+ style: "elevated"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-elevated.png
+ :align: center
+
+Filled
+------
+
+.. code-block:: kv
+
+ MDCard
+ style: "filled"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-filled.png
+ :align: center
+
+Outlined
+--------
+
+.. code-block:: kv
+
+ MDCard
+ style: "outlined"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-outlined.png
+ :align: center
+
+
+Customization of card
+=====================
+
+.. code-block:: kv
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+ KV = '''
+ MDScreen:
+ theme_bg_color: "Custom"
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDCard:
+ style: "elevated"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ padding: "4dp"
+ size_hint: None, None
+ size: "240dp", "100dp"
+ # Sets custom properties.
+ theme_shadow_color: "Custom"
+ shadow_color: "green"
+ theme_bg_color: "Custom"
+ md_bg_color: "white"
+ md_bg_color_disabled: "grey"
+ theme_shadow_offset: "Custom"
+ shadow_offset: (1, -2)
+ theme_shadow_softness: "Custom"
+ shadow_softness: 1
+ theme_elevation_level: "Custom"
+ elevation_level: 2
+
+ MDRelativeLayout:
+
+ MDIconButton:
+ icon: "dots-vertical"
+ pos_hint: {"top": 1, "right": 1}
+
+ MDLabel:
+ text: "Elevated"
+ adaptive_size: True
+ color: "grey"
+ pos: "12dp", "12dp"
+ bold: True
+ '''
+
+
+ class Example(MDApp):
+ def build(self):
+ self.theme_cls.primary_palette = "Green"
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-customization.png
:align: center
.. MDCardSwipe:
@@ -196,8 +285,8 @@ class SwipeToDeleteItem(MDCardSwipe):
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sceleton-mdcard-swiper.png
:align: center
-End full code
--------------
+Example
+-------
.. tabs::
@@ -212,16 +301,20 @@ class SwipeToDeleteItem(MDCardSwipe):
from kivymd.uix.card import MDCardSwipe
KV = '''
-
+ :
size_hint_y: None
height: content.height
MDCardSwipeLayerBox:
- # Content under the card.
+ padding: "8dp"
+
+ MDIconButton:
+ icon: "trash-can"
+ pos_hint: {"center_y": .5}
+ on_release: app.remove_item(root)
MDCardSwipeFrontBox:
- # Content of card.
OneLineListItem:
id: content
text: root.text
@@ -230,39 +323,34 @@ class SwipeToDeleteItem(MDCardSwipe):
MDScreen:
- MDBoxLayout:
- orientation: "vertical"
-
- MDTopAppBar:
- elevation: 4
- title: "MDCardSwipe"
+ MDScrollView:
- MDScrollView:
- scroll_timeout : 100
-
- MDList:
- id: md_list
- padding: 0
+ MDList:
+ id: md_list
+ padding: 0
'''
class SwipeToDeleteItem(MDCardSwipe):
- '''Card with `swipe-to-delete` behavior.'''
-
text = StringProperty()
class Example(MDApp):
- def build(self):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Orange"
- return Builder.load_string(KV)
+ self.screen = Builder.load_string(KV)
- def on_start(self):
- '''Creates a list of cards.'''
+ def build(self):
+ return self.screen
+ def remove_item(self, instance):
+ self.screen.ids.md_list.remove_widget(instance)
+
+ def on_start(self):
for i in range(20):
- self.root.ids.md_list.add_widget(
+ self.screen.ids.md_list.add_widget(
SwipeToDeleteItem(text=f"One-line item {i}")
)
@@ -274,14 +362,12 @@ def on_start(self):
.. code-block:: python
from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import (
MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox
)
from kivymd.uix.list import MDList, OneLineListItem
from kivymd.uix.screen import MDScreen
from kivymd.uix.scrollview import MDScrollView
- from kivymd.uix.toolbar import MDTopAppBar
class Example(MDApp):
@@ -290,20 +376,13 @@ def build(self):
self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
- MDBoxLayout(
- MDTopAppBar(
- elevation=4,
- title="MDCardSwipe",
+ MDScrollView(
+ MDList(
+ id="md_list",
+ padding=0,
),
- MDScrollView(
- MDList(
- id="md_list",
- ),
- id="scroll",
- scroll_timeout=100,
- ),
- id="box",
- orientation="vertical",
+ id="scroll",
+ scroll_timeout=100,
),
)
)
@@ -312,7 +391,7 @@ def on_start(self):
'''Creates a list of cards.'''
for i in range(20):
- self.root.ids.box.ids.scroll.ids.md_list.add_widget(
+ self.root.ids.scroll.ids.md_list.add_widget(
MDCardSwipe(
MDCardSwipeLayerBox(),
MDCardSwipeFrontBox(
@@ -323,7 +402,7 @@ def on_start(self):
)
),
size_hint_y=None,
- height="52dp",
+ height="48dp",
)
)
@@ -355,19 +434,11 @@ def on_start(self):
# By default, the parameter is "hand"
- type_swipe: "hand"
+ type_swipe: "hand" # "auto"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hand-mdcard-swipe.gif
:align: center
-.. code-block:: kv
-
- :
- type_swipe: "auto"
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif
- :align: center
-
Removing an item using the ``type_swipe = "auto"`` parameter
------------------------------------------------------------
@@ -410,9 +481,10 @@ def on_swipe_complete(self, instance):
def on_swipe_complete(self, instance):
self.root.ids.box.ids.scroll.ids.md_list.remove_widget(instance)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/autodelete-mdcard-swipe.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif
:align: center
+
Add content to the bottom layer of the card
-------------------------------------------
@@ -431,256 +503,20 @@ def on_swipe_complete(self, instance):
pos_hint: {"center_y": .5}
on_release: app.remove_item(root)
-End full code
--------------
-
-.. tabs::
-
- .. tab:: Declarative KV styles
-
- .. code-block:: python
-
- from kivy.lang import Builder
- from kivy.properties import StringProperty
-
- from kivymd.app import MDApp
- from kivymd.uix.card import MDCardSwipe
-
- KV = '''
- :
- size_hint_y: None
- height: content.height
-
- MDCardSwipeLayerBox:
- padding: "8dp"
-
- MDIconButton:
- icon: "trash-can"
- pos_hint: {"center_y": .5}
- on_release: app.remove_item(root)
-
- MDCardSwipeFrontBox:
-
- OneLineListItem:
- id: content
- text: root.text
- _no_ripple_effect: True
-
-
- MDScreen:
-
- MDBoxLayout:
- orientation: "vertical"
-
- MDTopAppBar:
- elevation: 4
- title: "MDCardSwipe"
-
- MDScrollView:
-
- MDList:
- id: md_list
- padding: 0
- '''
-
-
- class SwipeToDeleteItem(MDCardSwipe):
- text = StringProperty()
-
-
- class Example(MDApp):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- self.screen = Builder.load_string(KV)
-
- def build(self):
- return self.screen
-
- def remove_item(self, instance):
- self.screen.ids.md_list.remove_widget(instance)
-
- def on_start(self):
- for i in range(20):
- self.screen.ids.md_list.add_widget(
- SwipeToDeleteItem(text=f"One-line item {i}")
- )
-
-
- Example().run()
-
- .. tab:: Decralative python styles
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.button import MDIconButton
- from kivymd.uix.card import (
- MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox
- )
- from kivymd.uix.list import MDList, OneLineListItem
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.scrollview import MDScrollView
- from kivymd.uix.toolbar import MDTopAppBar
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return (
- MDScreen(
- MDBoxLayout(
- MDTopAppBar(
- elevation=4,
- title="MDCardSwipe",
- ),
- MDScrollView(
- MDList(
- id="md_list",
- ),
- id="scroll",
- scroll_timeout=100,
- ),
- id="box",
- orientation="vertical",
- ),
- )
- )
-
- def on_start(self):
- '''Creates a list of cards.'''
-
- for i in range(20):
- self.root.ids.box.ids.scroll.ids.md_list.add_widget(
- MDCardSwipe(
- MDCardSwipeLayerBox(
- MDIconButton(
- icon="trash-can",
- pos_hint={"center_y": 0.5},
- on_release=self.remove_item,
- ),
- ),
- MDCardSwipeFrontBox(
- OneLineListItem(
- id="content",
- text=f"One-line item {i}",
- _no_ripple_effect=True,
- )
- ),
- size_hint_y=None,
- height="52dp",
- )
- )
-
- def remove_item(self, instance):
- self.root.ids.box.ids.scroll.ids.md_list.remove_widget(
- instance.parent.parent
- )
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/handdelete-mdcard-swipe.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdcard-swipe-content.png
:align: center
-
-Focus behavior
---------------
-
-.. code-block:: kv
-
- MDCard:
- focus_behavior: True
-
-.. tabs::
-
- .. tab:: Declarative KV styles
-
- .. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
-
- KV = '''
- MDScreen:
-
- MDCard:
- size_hint: .7, .4
- focus_behavior: True
- pos_hint: {"center_x": .5, "center_y": .5}
- md_bg_color: "darkgrey"
- unfocus_color: "darkgrey"
- focus_color: "grey"
- elevation: 6
- '''
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
-
-
- Example().run()
-
- .. tab:: Declarative python styles
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.card import MDCard
- from kivymd.uix.screen import MDScreen
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return (
- MDScreen(
- MDCard(
- size_hint=(0.7, 0.4),
- focus_behavior=True,
- pos_hint={"center_x": 0.5, "center_y": 0.5},
- md_bg_color="darkgrey",
- unfocus_color="darkgrey",
- focus_color="grey",
- elevation=6,
- ),
- )
- )
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif
- :align: center
-
-Ripple behavior
----------------
-
-.. code-block:: kv
-
- MDCard:
- ripple_behavior: True
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-behavior.gif
- :align: center
-
"""
+from __future__ import annotations
+
__all__ = (
"MDCard",
"MDCardSwipe",
"MDCardSwipeFrontBox",
"MDCardSwipeLayerBox",
- "MDSeparator",
)
import os
-from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
@@ -688,67 +524,32 @@ def build(self):
from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
- ColorProperty,
NumericProperty,
OptionProperty,
StringProperty,
VariableListProperty,
+ ColorProperty,
)
+from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
-from kivy.utils import get_color_from_hex
from kivymd import uix_path
-from kivymd.color_definitions import colors
-from kivymd.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,
DeclarativeBehavior,
RectangularRippleBehavior,
+ BackgroundColorBehavior,
)
-from kivymd.uix.behaviors.focus_behavior import FocusBehavior
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.relativelayout import MDRelativeLayout
with open(
os.path.join(uix_path, "card", "card.kv"), encoding="utf-8"
) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-class MDSeparator(MDBoxLayout):
- """
- A separator line.
-
- For more information, see in the
- :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation.
- """
-
- color = ColorProperty(None)
- """
- Separator color in (r, g, b, a) or string format.
-
- :attr:`color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.on_orientation()
-
- def on_orientation(self, *args) -> None:
- self.size_hint = (
- (1, None) if self.orientation == "horizontal" else (None, 1)
- )
- if self.orientation == "horizontal":
- self.height = dp(1)
- else:
- self.width = dp(1)
+ Builder.load_string(kv_file.read(), filename="MDCard.kv")
class MDCard(
@@ -756,34 +557,28 @@ class MDCard(
MDAdaptiveWidget,
ThemableBehavior,
BackgroundColorBehavior,
- RectangularRippleBehavior,
CommonElevationBehavior,
- FocusBehavior,
+ RectangularRippleBehavior,
+ StateLayerBehavior,
+ ButtonBehavior,
BoxLayout,
):
"""
Card class.
For more information, see in the
- :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.declarative_behavior.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:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
:class:`~kivy.uix.boxlayout.BoxLayout` and
classes documentation.
"""
- focus_behavior = BooleanProperty(False)
- """
- Using focus when hovering over a card.
-
- :attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
ripple_behavior = BooleanProperty(False)
"""
Use ripple effect for card.
@@ -792,17 +587,17 @@ class MDCard(
and defaults to `False`.
"""
- radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)])
+ radius = VariableListProperty([dp(16), dp(16), dp(16), dp(16)])
"""
Card radius by default.
.. versionadded:: 1.0.0
:attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
- and defaults to `[dp(6), dp(6), dp(6), dp(6)]`.
+ and defaults to `[dp(16), dp(16), dp(16), dp(16)]`.
"""
- style = OptionProperty(None, options=("filled", "elevated", "outlined"))
+ style = OptionProperty("filled", options=("filled", "elevated", "outlined"))
"""
Card type.
@@ -811,65 +606,78 @@ class MDCard(
Available options are: 'filled', 'elevated', 'outlined'.
:attr:`style` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'elevated'`.
+ and defaults to `None`.
"""
- _bg_color_map = (
- get_color_from_hex(colors["Light"]["CardsDialogs"]),
- get_color_from_hex(colors["Dark"]["CardsDialogs"]),
- [1.0, 1.0, 1.0, 0.0],
- )
+ md_bg_color_disabled = ColorProperty(None)
+ """
+ The background color in (r, g, b, a) or string format of the card when
+ the card is disabled.
+
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.theme_cls.bind(
- material_style=self.set_style, theme_style=self.update_md_bg_color
- )
- Clock.schedule_once(self.set_style)
Clock.schedule_once(
lambda x: self.on_ripple_behavior(0, self.ripple_behavior)
)
- self.update_md_bg_color(self, self.theme_cls.theme_style)
- def update_md_bg_color(self, instance_card, theme_style: str) -> None:
- if self.md_bg_color in self._bg_color_map:
- self.md_bg_color = get_color_from_hex(
- colors[theme_style]["CardsDialogs"]
- )
-
- def set_style(self, *args) -> None:
- self.set_radius()
- self.set_elevation()
- self.set_line_color()
-
- def set_line_color(self) -> None:
- if self.theme_cls.material_style == "M3":
- if self.style == "elevated" or self.style == "filled":
- self.line_color = [0, 0, 0, 0]
-
- def set_elevation(self) -> None:
- if self.theme_cls.material_style == "M3":
- if self.style == "outlined" or self.style == "filled":
- self.elevation = CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION
- elif self.style == "elevated":
- self.elevation = CARD_STYLE_ELEVATED_M3_ELEVATION
-
- def set_radius(self) -> None:
- if (
- self.radius == [dp(6), dp(6), dp(6), dp(6)]
- and self.theme_cls.material_style == "M3"
- ):
- self.radius = [dp(16), dp(16), dp(16), dp(16)]
- elif (
- self.radius == [dp(16), dp(16), dp(16), dp(16)]
- and self.theme_cls.material_style == "M2"
- ):
- self.radius = [dp(6), dp(6), dp(6), dp(6)]
-
- def on_ripple_behavior(
- self, interval: Union[int, float], value_behavior: bool
- ) -> None:
- self._no_ripple_effect = False if value_behavior else True
+ def on_press(self, *args) -> None:
+ """Fired when the button is pressed."""
+
+ self._on_press(args)
+
+ def on_release(self, *args) -> None:
+ """
+ Fired when the button is released
+ (i.e. the touch/click that pressed the button goes away).
+ """
+
+ self._on_release(args)
+
+ def on_ripple_behavior(self, interval: int | float, value: bool) -> None:
+ """Fired when the :attr:`ripple_behavior` value changes."""
+
+ self.ripple_effect = not self.ripple_effect
+
+ def set_properties_widget(self) -> None:
+ """Fired `on_release/on_press/on_enter/on_leave` events."""
+
+ super().set_properties_widget()
+
+ if not self.disabled:
+ if self._state == self.state_hover and self.focus_behavior:
+ self._elevation_level = self.elevation_level
+ self._shadow_softness = self.shadow_softness
+ self._bg_color = self.md_bg_color
+
+ if self.style in ["filled", "outlined"]:
+ if self.theme_elevation_level == "Primary":
+ self.elevation_level = 0
+ if self.theme_shadow_softness == "Primary":
+ self.shadow_softness = 0
+ else:
+ if self.theme_elevation_level == "Primary":
+ self.elevation_level = 2
+ if self.theme_shadow_softness == "Primary":
+ self.shadow_softness = 1
+ if self.theme_shadow_offset == "Primary":
+ self.shadow_offset = [0, -2]
+ elif self._state == self.state_press:
+ if self.theme_elevation_level == "Primary":
+ self.elevation_level = 1
+ if self.theme_shadow_softness == "Primary":
+ self.shadow_softness = 0
+ elif not self._state:
+ if self.theme_elevation_level == "Primary":
+ self.elevation_level = 1
+ if self.theme_shadow_softness == "Primary":
+ self.shadow_softness = 0
+ if self.theme_shadow_offset == "Primary":
+ self.shadow_offset = [0, -1]
+ self.md_bg_color = self._bg_color
class MDCardSwipe(MDRelativeLayout):
@@ -881,7 +689,7 @@ class MDCardSwipe(MDRelativeLayout):
:Events:
:attr:`on_swipe_complete`
- Called when a swipe of card is completed.
+ Fired when a swipe of card is completed.
"""
open_progress = NumericProperty(0.0)
@@ -993,16 +801,14 @@ def __init__(self, *args, **kwargs):
self.register_event_type("on_swipe_complete")
super().__init__(*args, **kwargs)
- def add_widget(self, widget, index=0, canvas=None):
- if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)):
- return super().add_widget(widget)
-
def on_swipe_complete(self, *args):
- """Called when a swipe of card is completed."""
+ """Fired when a swipe of card is completed."""
def on_anchor(
self, instance_swipe_to_delete_item, anchor_value: str
) -> None:
+ """Fired when the value of :attr:`anchor` changes."""
+
if anchor_value == "right":
self.open_progress = 1.0
else:
@@ -1011,6 +817,8 @@ def on_anchor(
def on_open_progress(
self, instance_swipe_to_delete_item, progress_value: float
) -> None:
+ """Fired when the value of :attr:`open_progress` changes."""
+
def on_open_progress(*args):
if self.anchor == "left":
self.children[0].x = self.width * progress_value
@@ -1043,7 +851,7 @@ def on_touch_up(self, touch):
if self.collide_point(touch.x, touch.y):
if not self._to_closed:
self._opens_process = False
- self.complete_swipe()
+ self._complete_swipe()
return super().on_touch_up(touch)
def on_touch_down(self, touch):
@@ -1053,18 +861,9 @@ def on_touch_down(self, touch):
Clock.schedule_once(self.close_card, self.closing_interval)
return super().on_touch_down(touch)
- def complete_swipe(self) -> None:
- expr = (
- self.open_progress <= self.max_swipe_x
- if self.anchor == "left"
- else self.open_progress >= self.max_swipe_x
- )
- if expr:
- Clock.schedule_once(self.close_card, self.closing_interval)
- else:
- self.open_card()
-
def open_card(self) -> None:
+ """Animates the opening of the card."""
+
if self.type_swipe == "hand":
swipe_x = (
self.max_opened_x
@@ -1081,11 +880,28 @@ def open_card(self) -> None:
self.state = "opened"
def close_card(self, *args) -> None:
+ """Animates the closing of the card."""
+
anim = Animation(x=0, t=self.closing_transition, d=self.opening_time)
anim.bind(on_complete=self._reset_open_progress)
anim.start(self.children[0])
self.state = "closed"
+ def add_widget(self, widget, index=0, canvas=None):
+ if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)):
+ return super().add_widget(widget)
+
+ def _complete_swipe(self) -> None:
+ expr = (
+ self.open_progress <= self.max_swipe_x
+ if self.anchor == "left"
+ else self.open_progress >= self.max_swipe_x
+ )
+ if expr:
+ Clock.schedule_once(self.close_card, self.closing_interval)
+ else:
+ self.open_card()
+
def _on_swipe_complete(self, *args):
self.dispatch("on_swipe_complete")
@@ -1104,4 +920,9 @@ class MDCardSwipeFrontBox(MDCard):
class MDCardSwipeLayerBox(MDBoxLayout):
- pass
+ """
+ Card swipe back box.
+
+ For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout`
+ class documentation.
+ """
diff --git a/kivymd/uix/chip/__init__.py b/kivymd/uix/chip/__init__.py
index fc6a3a29c..7246a4142 100644
--- a/kivymd/uix/chip/__init__.py
+++ b/kivymd/uix/chip/__init__.py
@@ -1 +1,7 @@
-from .chip import MDChip, MDChipText # NOQA F401
+from .chip import (
+ MDChip,
+ MDChipText,
+ MDChipLeadingIcon,
+ MDChipTrailingIcon,
+ MDChipLeadingAvatar,
+) # NOQA F401
diff --git a/kivymd/uix/chip/chip.kv b/kivymd/uix/chip/chip.kv
index e008dd25e..17d4ce948 100644
--- a/kivymd/uix/chip/chip.kv
+++ b/kivymd/uix/chip/chip.kv
@@ -1,29 +1,91 @@
+
+ icon_color:
+ ( \
+ { \
+ "filter": app.theme_cls.onSurfaceVariantColor, \
+ "suggestion": app.theme_cls.onSurfaceVariantColor, \
+ "input": app.theme_cls.onSurfaceVariantColor, \
+ "assist": app.theme_cls.primaryColor, \
+ }[self._type] \
+ if self.theme_icon_color == "Primary" else self.icon_color \
+ ) \
+ if not root.disabled else self.disabled_color
+ disabled_color:
+ { \
+ "filter": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_icon], \
+ "suggestion": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_icon], \
+ "input": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_icon], \
+ "assist": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_icon], \
+ }[self._type] \
+ if not self.icon_color_disabled else self.icon_color_disabled
+
+
+
+ font_style: "Label"
+ role: "large"
+ text_color:
+ ( \
+ { \
+ "filter": app.theme_cls.onSurfaceVariantColor, \
+ "suggestion": app.theme_cls.onSurfaceVariantColor, \
+ "input": app.theme_cls.onSurfaceVariantColor, \
+ "assist": app.theme_cls.onSurfaceColor, \
+ }[self._type] \
+ if root.theme_text_color == "Primary" else root.text_color \
+ ) \
+ if not root.disabled else self.disabled_color
+ disabled_color:
+ { \
+ "filter": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_text], \
+ "suggestion": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_text], \
+ "input": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_text], \
+ "assist": app.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_text], \
+ }[self._type] \
+ if not self.text_color_disabled else self.text_color_disabled
+
+
size_hint_y: None
height: "32dp"
adaptive_width: True
radius:
- 16 \
+ dp(16) \
if self.radius == [0, 0, 0, 0] else \
- (max(self.radius) if max(self.radius) < self.height / 2 else 16)
- md_bg_color:
+ (max(self.radius) if max(self.radius) < self.height / 2 else dp(16))
+ line_color:
( \
( \
- app.theme_cls.bg_darkest \
- if app.theme_cls.theme_style == "Light" else \
- app.theme_cls.bg_light \
+ self.theme_cls.outlineColor \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.chip_opacity_value_disabled_container] \
) \
- if not self._origin_md_bg_color else \
- self._origin_md_bg_color
+ if self.type != "filter" else \
+ self.theme_cls.transparentColor \
) \
- 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 \
+ if self.theme_line_color == "Primary" else \
+ self._line_color if not self.disabled else \
+ ( \
+ self.line_color_disabled \
+ if self.line_color_disabled else \
+ self._line_color \
)
+ md_bg_color:
+ { \
+ "filter": self.theme_cls.surfaceContainerLowColor, \
+ "suggestion": self.theme_cls.surfaceContainerLowColor, \
+ "input": self.theme_cls.surfaceContainerLowColor, \
+ "assist": self.theme_cls.surfaceContainerLowColor, \
+ }[self.type] \
+ if self.theme_bg_color == "Primary" else self.md_bg_color
LeadingIconContainer:
id: leading_icon_container
diff --git a/kivymd/uix/chip/chip.py b/kivymd/uix/chip/chip.py
index c9f359310..cb72148a8 100755
--- a/kivymd/uix/chip/chip.py
+++ b/kivymd/uix/chip/chip.py
@@ -4,7 +4,7 @@
.. seealso::
- `Material Design spec, Chips `_
+ `Material Design 3 spec, Chips `_
.. rubric:: Chips can show multiple interactive elements together in the same
area, such as a list of selectable movie times, or a series of email
@@ -17,6 +17,25 @@
Usage
-----
+.. code-block:: kv
+
+ MDChip:
+
+ MDChipLeadingAvatar: # MDChipLeadingIcon
+
+ MDChipText:
+
+ MDChipTrailingIcon:
+
+Anatomy
+=======
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-anatomy.png
+ :align: center
+
+Example
+-------
+
.. tabs::
.. tab:: Declarative KV style
@@ -75,34 +94,6 @@ def build(self):
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip.png
:align: center
-Anatomy
--------
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/anatomy-chip.png
- :align: center
-
-1. Container
-2. Label text
-3. Leading icon or image (optional)
-4. Trailing remove icon (optional, input & filter chips only)
-
-Container
----------
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/radius-chip.png
- :align: center
-
-All chips are slightly rounded with an 8dp corner.
-
-Shadows and elevation
----------------------
-
-Chip containers can be elevated if the placement requires protection, such as
-on top of an image.
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadows-elevation-chip.png
- :align: center
-
The following types of chips are available:
-------------------------------------------
@@ -641,7 +632,7 @@ def build(self):
API break
=========
-1.1.1 version
+1.2.0 version
-------------
.. code-block:: python
@@ -670,7 +661,7 @@ def on_release_chip(self, instance_check):
Test().run()
-1.2.0 version
+2.0.0 version
-------------
.. code-block:: python
@@ -724,7 +715,6 @@ def on_release_chip(self, instance_check):
BooleanProperty,
ColorProperty,
OptionProperty,
- StringProperty,
VariableListProperty,
)
from kivy.uix.behaviors import ButtonBehavior
@@ -738,6 +728,7 @@ def on_release_chip(self, instance_check):
ScaleBehavior,
TouchBehavior,
)
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDIcon, MDLabel
@@ -750,6 +741,29 @@ def on_release_chip(self, instance_check):
class BaseChipIcon(
CircularRippleBehavior, ScaleBehavior, ButtonBehavior, MDIcon
):
+ icon_color = ColorProperty(None)
+ """
+ Button icon color in (r, g, b, a) or string format.
+
+ :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ icon_color_disabled = ColorProperty(None)
+ """
+ The icon color in (r, g, b, a) or string format of the chip when
+ the chip is disabled.
+
+ .. versionadded:: 2.0.0
+
+ :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ _type = OptionProperty(
+ "suggestion", options=["assist", "filter", "input", "suggestion"]
+ )
+
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.ripple_scale = 1.5
@@ -760,7 +774,8 @@ def adjust_icon_size(self, *args) -> None:
# icon size according to the standards of material design version 3.
if (
self.font_name == "Icons"
- and self.theme_cls.font_styles["Icon"][1] == self.font_size
+ and self.theme_cls.font_styles["Icon"]["large"]["font-size"]
+ == self.font_size
):
self.font_size = (
"18sp"
@@ -792,10 +807,10 @@ class MDChipLeadingAvatar(BaseChipIcon):
Implements the leading avatar for the chip.
For more information, see in the
- :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
- :class:`~kivymd.uix.behaviors.ScaleBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.label.MDIcon`
+ :class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
@@ -805,10 +820,10 @@ class MDChipLeadingIcon(BaseChipIcon):
Implements the leading icon for the chip.
For more information, see in the
- :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
- :class:`~kivymd.uix.behaviors.ScaleBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.label.MDIcon`
+ :class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
@@ -818,10 +833,10 @@ class MDChipTrailingIcon(BaseChipIcon):
Implements the trailing icon for the chip.
For more information, see in the
- :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
- :class:`~kivymd.uix.behaviors.ScaleBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.label.MDIcon`
+ :class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
"""
@@ -831,9 +846,24 @@ class MDChipText(MDLabel):
Implements the label for the chip.
For more information, see in the
- :class:`~kivymd.uix.label.MDLabel` classes documentation.
+ :class:`~kivymd.uix.label.label.MDLabel` classes documentation.
"""
+ text_color_disabled = ColorProperty(None)
+ """
+ The text color in (r, g, b, a) or string format of the chip when
+ the chip is disabled.
+
+ .. versionadded:: 2.0.0
+
+ :attr:`text_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ _type = OptionProperty(
+ "suggestion", options=["assist", "filter", "input", "suggestion"]
+ )
+
class MDChip(
MDBoxLayout,
@@ -841,16 +871,17 @@ class MDChip(
ButtonBehavior,
CommonElevationBehavior,
TouchBehavior,
+ StateLayerBehavior,
):
"""
Chip class.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDBoxLayout` and
- :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.behaviors.CommonElevationBehavior` and
- :class:`~kivymd.uix.behaviors.TouchBehavior`
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior`
classes documentation.
"""
@@ -862,23 +893,13 @@ class MDChip(
and defaults to `[dp(8), dp(8), dp(8), dp(8)]`.
"""
- text = StringProperty(deprecated=True)
- """
- Chip text.
-
- .. deprecated:: 1.2.0
-
- :attr:`text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
type = OptionProperty(
"suggestion", options=["assist", "filter", "input", "suggestion"]
)
"""
Type of chip.
- .. versionadded:: 1.2.0
+ .. versionadded:: 2.0.0
Available options are: `'assist'`, `'filter'`, `'input'`, `'suggestion'`.
@@ -886,74 +907,6 @@ class MDChip(
and defaults to `'suggestion'`.
"""
- icon_left = StringProperty(deprecated=True)
- """
- Chip left icon.
-
- .. versionadded:: 1.0.0
-
- .. deprecated:: 1.2.0
-
- :attr:`icon_left` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- icon_right = StringProperty(deprecated=True)
- """
- Chip right icon.
-
- .. versionadded:: 1.0.0
-
- .. deprecated:: 1.2.0
-
- :attr:`icon_right` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- text_color = ColorProperty(None, deprecated=True)
- """
- Chip's text color in (r, g, b, a) or string format.
-
- .. deprecated:: 1.2.0
-
- :attr:`text_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- icon_right_color = ColorProperty(None, deprecated=True)
- """
- Chip's right icon color in (r, g, b, a) or string format.
-
- .. versionadded:: 1.0.0
-
- .. deprecated:: 1.2.0
-
- :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- icon_left_color = ColorProperty(None, deprecated=True)
- """
- Chip's left icon color in (r, g, b, a) or string format.
-
- .. versionadded:: 1.0.0
-
- .. deprecated:: 1.2.0
-
- :attr:`icon_left_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- icon_check_color = ColorProperty(None)
- """
- Chip's check icon color in (r, g, b, a) or string format.
-
- .. versionadded:: 1.0.0
-
- :attr:`icon_check_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
active = BooleanProperty(False)
"""
Whether the check is marked or not.
@@ -969,12 +922,23 @@ class MDChip(
The background color of the chip in the marked state in (r, g, b, a)
or string format.
- .. versionadded:: 1.2.0
+ .. versionadded:: 2.0.0
:attr:`selected_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
+ line_color_disabled = ColorProperty(None)
+ """
+ The color of the outline in the disabled state
+
+ .. versionadded:: 2.0.0
+
+ :attr:`line_color_disabled` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ _line_color = ColorProperty(None)
_current_md_bg_color = ColorProperty(None)
# A flag that disallow ripple animation of the chip
# at the time of clicking the chip icons.
@@ -986,11 +950,19 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def on_long_touch(self, *args) -> None:
+ """Fired when the widget is pressed for a long time."""
+
if self.type == "filter":
self.active = not self.active
+ def on_line_color(self, instance, value) -> None:
+ """Fired when the values of :attr:`line_color` change."""
+
+ if not self.disabled:
+ self._line_color = value
+
def on_type(self, instance, value: str) -> None:
- """Called when the values of :attr:`type` change."""
+ """Fired when the values of :attr:`type` change."""
def adjust_padding(*args):
"""
@@ -1069,7 +1041,14 @@ def complete_anim_ripple(self, *args) -> None:
self.set_chip_bg_color(
self.selected_color
if self.selected_color
- else self.theme_cls.primary_color
+ else {
+ "filter": self.theme_cls.surfaceContainerLowColor,
+ "suggestion": self.theme_cls.surfaceContainerLowColor,
+ "input": self.theme_cls.surfaceContainerLowColor,
+ "assist": self.theme_cls.surfaceContainerLowColor,
+ }[self.type]
+ if self.theme_bg_color == "Primary"
+ else self.md_bg_color
)
else:
if (
@@ -1125,11 +1104,26 @@ def set_chip_bg_color(self, color: list | str) -> None:
Animation(md_bg_color=color, d=0.2).start(self)
self._anim_complete = not self._anim_complete
- def on_press(self, *args):
+ def on_press(self, *args) -> None:
+ """Fired when the button is pressed."""
+
if self.active:
self.active = False
+ self._on_press(args)
+
+ def on_release(self, *args) -> None:
+ """
+ Fired when the button is released
+ (i.e. the touch/click that pressed the button goes away).
+ """
+
+ self._on_release(args)
+
def add_widget(self, widget, *args, **kwargs):
+ def set_type(*args):
+ widget._type = self.type
+
def add_icon_leading_trailing(container):
if len(container.children):
type_icon = (
@@ -1213,6 +1207,7 @@ def add_icon_leading_trailing(container):
container.add_widget(widget)
if isinstance(widget, MDChipText):
+ Clock.schedule_once(set_type)
widget.adaptive_size = True
widget.pos_hint = {"center_y": 0.5}
if self.type == "suggestion":
@@ -1221,12 +1216,14 @@ def add_icon_leading_trailing(container):
lambda x: self.ids.label_container.add_widget(widget)
)
elif isinstance(widget, (MDChipLeadingIcon, MDChipLeadingAvatar)):
+ Clock.schedule_once(set_type)
Clock.schedule_once(
lambda x: add_icon_leading_trailing(
self.ids.leading_icon_container
)
)
elif isinstance(widget, MDChipTrailingIcon):
+ Clock.schedule_once(set_type)
Clock.schedule_once(
lambda x: add_icon_leading_trailing(
self.ids.trailing_icon_container
@@ -1236,6 +1233,10 @@ def add_icon_leading_trailing(container):
widget,
(LabelTextContainer, LeadingIconContainer, TrailingIconContainer),
):
+ if isinstance(
+ widget, (LeadingIconContainer, TrailingIconContainer)
+ ):
+ Clock.schedule_once(set_type)
return super().add_widget(widget)
def _set_allow_chip_ripple(
diff --git a/kivymd/uix/datatables/datatables.kv b/kivymd/uix/datatables/datatables.kv
index b5d81a7ad..59fa9b441 100644
--- a/kivymd/uix/datatables/datatables.kv
+++ b/kivymd/uix/datatables/datatables.kv
@@ -3,23 +3,23 @@
orientation: "vertical"
- md_bg_color:
- ( \
- ( \
- root.theme_cls.bg_darkest \
- if root.theme_cls.theme_style == "Light" else \
- root.theme_cls.bg_light \
- ) \
- if not root.background_color_selected_cell \
- else root.background_color_selected_cell \
- ) \
- if self.selected \
- else \
- ( \
- root.theme_cls.bg_normal \
- if not root.background_color_cell \
- else root.background_color_cell \
- )
+# md_bg_color:
+# ( \
+# ( \
+# root.theme_cls.bg_darkest \
+# if root.theme_cls.theme_style == "Light" else \
+# root.theme_cls.bg_light \
+# ) \
+# if not root.background_color_selected_cell \
+# else root.background_color_selected_cell \
+# ) \
+# if self.selected \
+# else \
+# ( \
+# root.theme_cls.bg_normal \
+# if not root.background_color_cell \
+# else root.background_color_cell \
+# )
on_press: if DEVICE_TYPE != "desktop": root.table.on_mouse_select(self)
on_enter: if DEVICE_TYPE == "desktop": root.table.on_mouse_select(self)
@@ -57,7 +57,7 @@
if root.theme_cls.theme_style == "Dark" else \
(0, 0, 0, 1)
- MDSeparator:
+ MDDivider:
@@ -83,7 +83,7 @@
if root.theme_cls.theme_style == "Dark" else \
(0, 0, 0, 1)
- MDSeparator:
+ MDDivider:
id: separator
@@ -109,10 +109,10 @@
cols_minimum: root.cols_minimum
adaptive_size: True
padding: 0, "8dp", 0, 0
- md_bg_color:
- root.theme_cls.bg_light \
- if not root.background_color_header \
- else root.background_color_header
+# md_bg_color:
+# root.theme_cls.bg_light \
+# if not root.background_color_header \
+# else root.background_color_header
MDBoxLayout:
orientation: "vertical"
@@ -132,7 +132,7 @@
CellHeader:
id: first_cell
- MDSeparator:
+ MDDivider:
diff --git a/kivymd/uix/datatables/datatables.py b/kivymd/uix/datatables/datatables.py
index 44748a551..bf3452c3d 100644
--- a/kivymd/uix/datatables/datatables.py
+++ b/kivymd/uix/datatables/datatables.py
@@ -58,11 +58,12 @@
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.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
@@ -1307,7 +1308,7 @@ def build(self):
and defaults to `False`.
"""
- elevation = NumericProperty(DATA_TABLE_ELEVATION)
+ elevation = NumericProperty(0)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
attribute.
@@ -1327,7 +1328,7 @@ def build(self):
and defaults to `[6]`.
"""
- shadow_softness = NumericProperty(DATA_TABLE_SOFTNESS)
+ shadow_softness = NumericProperty(0)
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
attribute.
@@ -1349,7 +1350,7 @@ def build(self):
and defaults to `2`.
"""
- shadow_offset = ListProperty(DATA_TABLE_OFFSET)
+ shadow_offset = ListProperty([0, 0])
"""
See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
attribute.
diff --git a/kivymd/uix/dialog/__init__.py b/kivymd/uix/dialog/__init__.py
index 40adbe46f..9fc91ded4 100644
--- a/kivymd/uix/dialog/__init__.py
+++ b/kivymd/uix/dialog/__init__.py
@@ -1 +1,8 @@
-from .dialog import BaseDialog, MDDialog # NOQA F401
+from .dialog import (
+ MDDialog,
+ MDDialogIcon,
+ MDDialogHeadlineText,
+ MDDialogSupportingText,
+ MDDialogContentContainer,
+ MDDialogButtonContainer,
+) # NOQA F401
diff --git a/kivymd/uix/dialog/dialog.kv b/kivymd/uix/dialog/dialog.kv
index 96e01a375..42eb3ae22 100644
--- a/kivymd/uix/dialog/dialog.kv
+++ b/kivymd/uix/dialog/dialog.kv
@@ -1,90 +1,114 @@
-#:import images_path kivymd.images_path
-
-
-
- background: '{}/transparent.png'.format(images_path)
-
- canvas.before:
- PushMatrix
- RoundedRectangle:
- pos: self.pos
- size: self.size
- radius: root.radius
- Scale:
- origin: self.center
- x: root._scale_x
- y: root._scale_y
- canvas.after:
- PopMatrix
-
-
-
- shadow_color: 0.0, 0.0, 0.0, 0.0
- elevation: 0
- shadow_softness: 0
- shadow_offset: 0, 0
-
-
-
- DialogContainer:
- id: container
- orientation: "vertical"
+ radius: root.radius
+ size_hint_min_x: "280dp"
+ size_hint_max_x: "560dp"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ size_hint_y: None
+ height: container.height
+ theme_shadow_color: "Custom"
+ shadow_color: self.theme_cls.transparentColor
+ focus_behavior: False
+
+ RelativeLayout:
size_hint_y: None
- height: self.minimum_height
- padding: "24dp", "24dp", "8dp", "8dp"
- radius: root.radius
- md_bg_color:
- root.theme_cls.bg_dark \
- if not root.md_bg_color else root.md_bg_color
-
- MDLabel:
- id: title
- text: root.title
- font_style: "H6"
- bold: True
- markup: True
- size_hint_y: None
- height: self.texture_size[1]
- valign: "top"
+ height: container.height
BoxLayout:
- id: spacer_top_box
- size_hint_y: None
- height: root._spacer_top
-
- MDLabel:
- id: text
- text: root.text
- font_style: "Body1"
- theme_text_color: "Custom"
- text_color: root.theme_cls.disabled_hint_text_color
+ id: container
+ orientation: "vertical"
size_hint_y: None
- height: self.texture_size[1]
- markup: True
-
- ScrollView:
- id: scroll
- size_hint_y: None
- height: root._scroll_height
-
- MDGridLayout:
- id: box_items
- adaptive_height: True
- cols: 1
+ height: self.minimum_height + button_container.height + dp(24)
+ spacing: "16dp"
+ padding: "24dp"
+ y: button_container.height + dp(24)
+
+ AnchorLayout:
+ id: icon_container
+ size_hint_y: None
+ anchor_x: "center"
+ height: self.children[0].height if self.children else 0
+
+ BoxLayout:
+ id: headline_container
+ size_hint_y: None
+ height: self.minimum_height
+
+ BoxLayout:
+ id: supporting_text_container
+ size_hint_y: None
+ height: self.minimum_height
+
+ BoxLayout:
+ id: content_container
+ size_hint_y: None
+ height: self.minimum_height
BoxLayout:
- id: spacer_bottom_box
+ id: button_container
size_hint_y: None
height: self.minimum_height
-
- AnchorLayout:
- id: root_button_box
- size_hint_y: None
- height: "52dp"
- anchor_x: "right"
-
- MDBoxLayout:
- id: button_box
- adaptive_size: True
- spacing: "8dp"
+ y: content_container.y
+
+
+
+ size_hint: None, None
+ size: "24dp", "24dp"
+ theme_font_size: "Custom"
+ font_size: "24sp"
+ icon_color:
+ self.theme_cls.secondaryColor \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color \
+ if self.icon_color else self.theme_cls.transparentColor \
+ )
+
+
+
+ adaptive_height: True
+ halign: "center"
+ font_style: "Headline"
+ role: "small"
+ markup: True
+ color:
+ self.theme_cls.onSurfaceColor \
+ if self.theme_text_color == "Primary" else ( \
+ self.text_color \
+ if self.text_color != self.theme_cls.transparentColor else \
+ self.theme_cls.onSurfaceColor \
+ )
+
+
+
+ adaptive_height: True
+ halign: "center"
+ font_style: "Body"
+ role: "medium"
+ markup: True
+ text_color:
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_text_color == "Primary" else ( \
+ self.text_color \
+ if self.text_color != self.theme_cls.transparentColor else \
+ self.theme_cls.onSurfaceVariantColor \
+ )
+
+
+
+ size_hint_y: None
+ height: self.minimum_height
+
+
+
+ size_hint_y: None
+ height: self.minimum_height
+ padding: "24dp", 0, "24dp", 0
+
+
+
+ canvas:
+ Color:
+ rgba: self.color[:-1] + [self.alpha]
+ Rectangle:
+ pos: self.pos
+ size: self.size
diff --git a/kivymd/uix/dialog/dialog.py b/kivymd/uix/dialog/dialog.py
index 9f2db53a6..a16dca1f4 100755
--- a/kivymd/uix/dialog/dialog.py
+++ b/kivymd/uix/dialog/dialog.py
@@ -4,182 +4,156 @@
.. seealso::
- `Material Design spec, Dialogs `_
+ `Material Design spec, Dialogs `_
-.. rubric:: Dialogs inform users about a task and can contain critical
- information, require decisions, or involve multiple tasks.
+.. rubric:: Dialogs provide important prompts in a user flow.
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialogs.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-preview.png
:align: center
-Usage
------
+- Use dialogs to make sure users act on information
+- Two types: basic and full-screen (full-screen not provided in KivyMD)
+- Should be dedicated to completing a single task
+- Can also display information relevant to the task
+- Commonly used to confirm high-risk actions like deleting progress
+
+Anatomy
+=======
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-anatomy.png
+ :align: center
+
+Example
+=======
.. code-block:: python
from kivy.lang import Builder
+ from kivy.uix.widget import Widget
from kivymd.app import MDApp
- from kivymd.uix.button import MDFlatButton
- from kivymd.uix.dialog import MDDialog
+ from kivymd.uix.button import MDButton, MDButtonText
+ from kivymd.uix.dialog import (
+ MDDialog,
+ MDDialogIcon,
+ MDDialogHeadlineText,
+ MDDialogSupportingText,
+ MDDialogButtonContainer,
+ MDDialogContentContainer,
+ )
+ from kivymd.uix.divider import MDDivider
+ from kivymd.uix.list import (
+ MDListItem,
+ MDListItemLeadingIcon,
+ MDListItemSupportingText,
+ )
KV = '''
- MDFloatLayout:
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
- MDFlatButton:
- text: "ALERT DIALOG"
+ MDButton:
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_alert_dialog()
+
+ MDButtonText:
+ text: "Show dialog"
'''
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 show_alert_dialog(self):
- if not self.dialog:
- self.dialog = MDDialog(
- text="Discard draft?",
- buttons=[
- MDFlatButton(
- text="CANCEL",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
+ MDDialog(
+ # ----------------------------Icon-----------------------------
+ MDDialogIcon(
+ icon="refresh",
+ ),
+ # -----------------------Headline text-------------------------
+ MDDialogHeadlineText(
+ text="Reset settings?",
+ ),
+ # -----------------------Supporting text-----------------------
+ MDDialogSupportingText(
+ text="This will reset your app preferences back to their "
+ "default settings. The following accounts will also "
+ "be signed out:",
+ ),
+ # -----------------------Custom content------------------------
+ MDDialogContentContainer(
+ MDDivider(),
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="gmail",
+ ),
+ MDListItemSupportingText(
+ text="KivyMD-library@yandex.com",
+ ),
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.transparentColor,
+ ),
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="gmail",
),
- MDFlatButton(
- text="DISCARD",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
+ MDListItemSupportingText(
+ text="kivydevelopment@gmail.com",
),
- ],
- )
- self.dialog.open()
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.transparentColor,
+ ),
+ MDDivider(),
+ orientation="vertical",
+ ),
+ # ---------------------Button container------------------------
+ MDDialogButtonContainer(
+ Widget(),
+ MDButton(
+ MDButtonText(text="Cancel"),
+ style="text",
+ ),
+ MDButton(
+ MDButtonText(text="Accept"),
+ style="text",
+ ),
+ spacing="8dp",
+ ),
+ # -------------------------------------------------------------
+ ).open()
Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/alert-dialog.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-example.gif
:align: center
-"""
-__all__ = ("MDDialog", "BaseDialog")
+.. warning:: Do not try to use the MDDialog widget in KV files.
-import os
-
-from kivy.clock import Clock
-from kivy.core.window import Window
-from kivy.lang import Builder
-from kivy.metrics import dp
-from kivy.properties import (
- ColorProperty,
- ListProperty,
- NumericProperty,
- ObjectProperty,
- OptionProperty,
- StringProperty,
-)
-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, MotionDialogBehavior
-from kivymd.uix.button import BaseButton
-from kivymd.uix.card import MDSeparator
-from kivymd.uix.list import BaseListItem
-
-with open(
- os.path.join(uix_path, "dialog", "dialog.kv"), encoding="utf-8"
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-class BaseDialog(
- ThemableBehavior, MotionDialogBehavior, ModalView, CommonElevationBehavior
-):
- elevation = NumericProperty(3)
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
- attribute for more information.
-
- .. versionadded:: 1.1.0
-
- :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `3`.
- """
-
- shadow_softness = NumericProperty(24)
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
- attribute for more information.
-
- .. versionadded:: 1.1.0
-
- :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `24`.
- """
-
- shadow_offset = ListProperty((0, 4))
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
- attribute for more information.
-
- .. versionadded:: 1.1.0
-
- :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 4]`.
- """
-
- radius = ListProperty([dp(7), dp(7), dp(7), dp(7)])
- """
- Dialog corners rounding value.
+API break
+=========
- .. code-block:: python
+1.2.0 version
+-------------
- [...]
- self.dialog = MDDialog(
- text="Oops! Something seems to have gone wrong!",
- radius=[20, 7, 20, 7],
- )
- [...]
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-radius.png
- :align: center
-
- :attr:`radius` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[7, 7, 7, 7]`.
- """
-
- _scale_x = NumericProperty(1)
- _scale_y = NumericProperty(1)
+.. code-block:: python
+ from kivy.uix.widget import Widget
-class MDDialog(BaseDialog):
- """
- Dialog class.
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDFlatButton
+ from kivymd.uix.dialog import MDDialog
- 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.
-
- .. code-block:: python
+ class Example(MDApp):
+ def build(self):
+ return Widget()
- [...]
- self.dialog = MDDialog(
- title="Reset settings?",
+ def on_start(self):
+ MDDialog(
+ title="Discard draft?",
buttons=[
MDFlatButton(
text="CANCEL",
@@ -187,30 +161,35 @@ class MDDialog(BaseDialog):
text_color=self.theme_cls.primary_color,
),
MDFlatButton(
- text="ACCEPT",
+ text="DISCARD",
theme_text_color="Custom",
text_color=self.theme_cls.primary_color,
),
],
- )
- [...]
+ ).open()
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-title.png
- :align: center
- :attr:`title` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-api-break-1-2-0.png
+ :align: center
+
+.. code-block:: python
+
+ from kivy.uix.widget import Widget
+
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDFlatButton
+ from kivymd.uix.dialog import MDDialog
- text = StringProperty()
- """
- Text dialog.
- .. code-block:: python
+ class Example(MDApp):
+ def build(self):
+ return Widget()
- [...]
- self.dialog = MDDialog(
- title="Reset settings?",
+ def on_start(self):
+ MDDialog(
+ title="Discard draft?",
text="This will reset your device to its default factory settings.",
buttons=[
MDFlatButton(
@@ -219,198 +198,266 @@ class MDDialog(BaseDialog):
text_color=self.theme_cls.primary_color,
),
MDFlatButton(
- text="ACCEPT",
+ text="DISCARD",
theme_text_color="Custom",
text_color=self.theme_cls.primary_color,
),
],
- )
- [...]
+ ).open()
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-text.png
- :align: center
- :attr:`text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
+ Example().run()
- buttons = ListProperty()
- """
- List of button objects for dialog.
- Objects must be inherited from :class:`~kivymd.uix.button.BaseButton` class.
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/2-dialog-api-break-1-2-0.png
+ :align: center
- .. code-block:: python
+.. code-block:: python
- [...]
- self.dialog = MDDialog(
- text="Discard draft?",
- buttons=[
- MDFlatButton(text="CANCEL"), MDRaisedButton(text="DISCARD"),
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty
+ from kivy.uix.widget import Widget
+
+ from kivymd import images_path
+ from kivymd.app import MDApp
+ from kivymd.uix.dialog import MDDialog
+ from kivymd.uix.list import OneLineAvatarListItem
+
+ KV = '''
+ -
+
+ ImageLeftWidget:
+ source: root.source
+ '''
+
+
+ class Item(OneLineAvatarListItem):
+ divider = None
+ source = StringProperty()
+
+
+ class Example(MDApp):
+ def build(self):
+ Builder.load_string(KV)
+ return Widget()
+
+ def on_start(self):
+ MDDialog(
+ title="Set backup account",
+ type="simple",
+ items=[
+ Item(text="user01@gmail.com", source=f"{images_path}/logo/kivymd-icon-128.png"),
+ Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"),
],
- )
- [...]
+ ).open()
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-buttons.png
- :align: center
- :attr:`buttons` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
+ Example().run()
- items = ListProperty()
- """
- List of items objects for dialog.
- Objects must be inherited from :class:`~kivymd.uix.list.BaseListItem` class.
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/3-dialog-api-break-1-2-0.png
+ :align: center
- With type 'simple'
- ~~~~~~~~~~~~~~~~~~
+2.2.0 version
+-------------
- .. code-block:: python
+.. code-block:: python
- from kivy.lang import Builder
- from kivy.properties import StringProperty
+ from kivy.uix.widget import Widget
- from kivymd.app import MDApp
- from kivymd.uix.dialog import MDDialog
- from kivymd.uix.list import OneLineAvatarListItem
+ from kivymd.uix.widget import MDWidget
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDButton, MDButtonText
+ from kivymd.uix.dialog import MDDialog, MDDialogHeadlineText, MDDialogButtonContainer
- KV = '''
-
-
- ImageLeftWidget:
- source: root.source
+ class Example(MDApp):
+ def build(self):
+ return MDWidget(md_bg_color=self.theme_cls.backgroundColor)
+ def on_start(self):
+ MDDialog(
+ MDDialogHeadlineText(
+ text="Discard draft?",
+ halign="left",
+ ),
+ MDDialogButtonContainer(
+ Widget(),
+ MDButton(
+ MDButtonText(text="Cancel"),
+ style="text",
+ ),
+ MDButton(
+ MDButtonText(text="Discard"),
+ style="text",
+ ),
+ spacing="8dp",
+ ),
+ ).open()
- MDFloatLayout:
- MDFlatButton:
- text: "ALERT DIALOG"
- pos_hint: {'center_x': .5, 'center_y': .5}
- on_release: app.show_simple_dialog()
- '''
+ Example().run()
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-api-break-2-2-0.png
+ :align: center
- class Item(OneLineAvatarListItem):
- divider = None
- source = StringProperty()
+.. code-block:: python
+ from kivy.uix.widget import Widget
- class Example(MDApp):
- dialog = None
+ from kivymd.uix.widget import MDWidget
+ from kivymd.app import MDApp
+ from kivymd.uix.button import MDButton, MDButtonText
+ from kivymd.uix.dialog import MDDialog, MDDialogHeadlineText, MDDialogButtonContainer
- 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()
+ class Example(MDApp):
+ def build(self):
+ return MDWidget(md_bg_color=self.theme_cls.backgroundColor)
- Example().run()
+ def on_start(self):
+ MDDialog(
+ MDDialogHeadlineText(
+ text="Discard draft?",
+ halign="left",
+ ),
+ MDDialogSupportingText(
+ text="This will reset your device to its default factory settings.",
+ halign="left",
+ ),
+ MDDialogButtonContainer(
+ Widget(),
+ MDButton(
+ MDButtonText(text="Cancel"),
+ style="text",
+ ),
+ MDButton(
+ MDButtonText(text="Discard"),
+ style="text",
+ ),
+ spacing="8dp",
+ ),
+ ).open()
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-items.png
- :align: center
- With type 'confirmation'
- ~~~~~~~~~~~~~~~~~~~~~~~~
+ Example().run()
- .. code-block:: python
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/2-dialog-api-break-2-2-0.png
+ :align: center
+
+.. code-block:: python
- from kivy.lang import Builder
+ from kivymd import images_path
+ from kivymd.uix.widget import MDWidget
+ from kivymd.app import MDApp
+ from kivymd.uix.dialog import (
+ MDDialog,
+ MDDialogHeadlineText,
+ MDDialogContentContainer,
+ )
+ from kivymd.uix.list import (
+ MDListItem,
+ MDListItemLeadingAvatar,
+ MDListItemSupportingText,
+ )
+
+
+ class Example(MDApp):
+ def build(self):
+ return MDWidget(md_bg_color=self.theme_cls.backgroundColor)
+
+ def on_start(self):
+ MDDialog(
+ MDDialogHeadlineText(
+ text="Set backup account",
+ halign="left",
+ ),
+ MDDialogContentContainer(
+ MDListItem(
+ MDListItemLeadingAvatar(
+ source=f"{images_path}/logo/kivymd-icon-128.png",
+ ),
+ MDListItemSupportingText(
+ text="user01@gmail.com",
+ ),
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.transparentColor,
+ ),
+ MDListItem(
+ MDListItemLeadingAvatar(
+ source="data/logo/kivy-icon-128.png",
+ ),
+ MDListItemSupportingText(
+ text="user01@gmail.com",
+ ),
+ theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.transparentColor,
+ ),
+ orientation="vertical",
+ ),
+ ).open()
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFlatButton
- from kivymd.uix.dialog import MDDialog
- from kivymd.uix.list import OneLineAvatarIconListItem
- KV = '''
-
- on_release: root.set_icon(check)
+ Example().run()
- CheckboxLeftWidget:
- id: check
- group: "check"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/3-dialog-api-break-2-2-0.png
+ :align: center
+"""
- MDFloatLayout:
+__all__ = [
+ "MDDialog",
+ "MDDialogIcon",
+ "MDDialogHeadlineText",
+ "MDDialogSupportingText",
+ "MDDialogContentContainer",
+ "MDDialogButtonContainer",
+]
- MDFlatButton:
- text: "ALERT DIALOG"
- pos_hint: {'center_x': .5, 'center_y': .5}
- on_release: app.show_confirmation_dialog()
- '''
+import os
+from kivy.core.window import Window
+from kivy.lang import Builder
+from kivy.metrics import dp
+from kivy.properties import (
+ VariableListProperty,
+ NumericProperty,
+ ColorProperty,
+ ObjectProperty,
+)
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.widget import Widget
- class ItemConfirm(OneLineAvatarIconListItem):
- divider = None
+from kivymd.uix.card import MDCard
+from kivymd.uix.label import MDIcon, MDLabel
+from kivymd import uix_path
+from kivymd.material_resources import DEVICE_TYPE
+from kivymd.uix.behaviors import MotionDialogBehavior, DeclarativeBehavior
- def set_icon(self, instance_check):
- instance_check.active = True
- check_list = instance_check.get_widgets(instance_check.group)
- for check in check_list:
- if check != instance_check:
- check.active = False
+with open(
+ os.path.join(uix_path, "dialog", "dialog.kv"), encoding="utf-8"
+) as kv_file:
+ Builder.load_string(kv_file.read())
- class Example(MDApp):
- dialog = None
+class MDDialog(MDCard, MotionDialogBehavior):
+ """
+ Dialog class.
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return Builder.load_string(KV)
+ For more information, see in the
+ :class:`~kivymd.uix.card.card.MDCard` and
+ :class:`~kivymd.uix.behaviors.motion_behavior.MotionDialogBehavior`
+ classes documentation.
- def show_confirmation_dialog(self):
- if not self.dialog:
- self.dialog = MDDialog(
- title="Phone ringtone",
- type="confirmation",
- items=[
- ItemConfirm(text="Callisto"),
- ItemConfirm(text="Luna"),
- ItemConfirm(text="Night"),
- ItemConfirm(text="Solo"),
- ItemConfirm(text="Phobos"),
- ItemConfirm(text="Diamond"),
- ItemConfirm(text="Sirena"),
- ItemConfirm(text="Red music"),
- ItemConfirm(text="Allergio"),
- ItemConfirm(text="Magic"),
- ItemConfirm(text="Tic-tac"),
- ],
- buttons=[
- MDFlatButton(
- text="CANCEL",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- MDFlatButton(
- text="OK",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- ],
- )
- self.dialog.open()
-
-
- Example().run()
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-confirmation.png
- :align: center
-
- :attr:`items` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
+ :Events:
+ `on_pre_open`:
+ Fired before the MDDialog is opened. When this event is fired
+ MDDialog is not yet added to window.
+ `on_open`:
+ Fired when the MDDialog is opened.
+ `on_pre_dismiss`:
+ Fired before the MDDialog is closed.
+ `on_dismiss`:
+ Fired when the MDDialog is closed. If the callback returns True,
+ the dismiss will be canceled.
"""
width_offset = NumericProperty(dp(48))
@@ -421,270 +468,149 @@ def show_confirmation_dialog(self):
and defaults to `dp(48)`.
"""
- type = OptionProperty(
- "alert", options=["alert", "simple", "confirmation", "custom"]
- )
- """
- Dialog type.
- Available option are `'alert'`, `'simple'`, `'confirmation'`, `'custom'`.
-
- :attr:`type` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'alert'`.
- """
-
- content_cls = ObjectProperty()
+ radius = VariableListProperty(dp(28), lenght=4)
"""
- Custom content class. This attribute is only available when :attr:`type` is
- set to `'custom'`.
-
- .. tabs::
-
- .. tab:: Declarative KV style
+ Dialog corners rounding value.
- .. code-block:: python
-
- from kivy.lang import Builder
- from kivy.uix.boxlayout import BoxLayout
-
- from kivymd.app import MDApp
- from kivymd.uix.button import MDFlatButton
- from kivymd.uix.dialog import MDDialog
-
- KV = '''
-
- orientation: "vertical"
- spacing: "12dp"
- size_hint_y: None
- height: "120dp"
-
- MDTextField:
- hint_text: "City"
-
- MDTextField:
- hint_text: "Street"
-
-
- MDFloatLayout:
-
- MDFlatButton:
- text: "ALERT DIALOG"
- pos_hint: {'center_x': .5, 'center_y': .5}
- on_release: app.show_confirmation_dialog()
- '''
-
-
- class Content(BoxLayout):
- pass
-
-
- 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 show_confirmation_dialog(self):
- if not self.dialog:
- self.dialog = MDDialog(
- title="Address:",
- type="custom",
- content_cls=Content(),
- buttons=[
- MDFlatButton(
- text="CANCEL",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- MDFlatButton(
- text="OK",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- ],
- )
- self.dialog.open()
-
-
- Example().run()
-
- .. tab:: Declarative Python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.button import MDFlatButton
- from kivymd.uix.dialog import MDDialog
- from kivymd.uix.floatlayout import MDFloatLayout
- from kivymd.uix.textfield import MDTextField
-
-
- class Example(MDApp):
- dialog = None
-
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return (
- MDFloatLayout(
- MDFlatButton(
- text="ALERT DIALOG",
- pos_hint={'center_x': 0.5, 'center_y': 0.5},
- on_release=self.show_confirmation_dialog,
- )
- )
- )
-
- def show_confirmation_dialog(self, *args):
- if not self.dialog:
- self.dialog = MDDialog(
- title="Address:",
- type="custom",
- content_cls=MDBoxLayout(
- MDTextField(
- hint_text="City",
- ),
- MDTextField(
- hint_text="Street",
- ),
- orientation="vertical",
- spacing="12dp",
- size_hint_y=None,
- height="120dp",
- ),
- buttons=[
- MDFlatButton(
- text="CANCEL",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- MDFlatButton(
- text="OK",
- theme_text_color="Custom",
- text_color=self.theme_cls.primary_color,
- ),
- ],
- )
- self.dialog.open()
-
-
- Example().run()
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png
- :align: center
-
- :attr:`content_cls` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `'None'`.
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[dp(28), dp(28), dp(28), dp(28)]`.
"""
- md_bg_color = ColorProperty(None)
+ scrim_color = ColorProperty([0, 0, 0, 0.5])
"""
- Background color in the (r, g, b, a) or string format.
+ Color for scrim in (r, g, b, a) or string format.
- :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ :attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `[0, 0, 0, 0.5]`.
"""
- _scroll_height = NumericProperty("28dp")
- _spacer_top = NumericProperty("24dp")
+ _scrim = ObjectProperty() # kivymd.uix.dialog.dialog.MDDialogScrim object
+ _is_open = False # is the dialog currently open or closed.
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_event_type("on_open")
+ self.register_event_type("on_pre_open")
+ self.register_event_type("on_dismiss")
+ self.register_event_type("on_pre_dismiss")
+ self.opacity = 0
Window.bind(on_resize=self.update_width)
- if self.size_hint == [1, 1] and (
- DEVICE_TYPE == "desktop" or DEVICE_TYPE == "tablet"
- ):
- self.size_hint = (None, None)
- self.width = min(dp(560), Window.width - self.width_offset)
- elif self.size_hint == [1, 1] and DEVICE_TYPE == "mobile":
- self.size_hint = (None, None)
- self.width = min(dp(280), Window.width - self.width_offset)
-
- if not self.title:
- self._spacer_top = 0
-
- if not self.buttons:
- self.ids.root_button_box.height = 0
- else:
- self.create_buttons()
-
- update_height = False
- if self.type in ("simple", "confirmation"):
- if self.type == "confirmation":
- self.ids.spacer_top_box.add_widget(MDSeparator())
- self.ids.spacer_bottom_box.add_widget(MDSeparator())
- self.create_items()
- if self.type == "custom":
- if self.content_cls:
- self.ids.container.remove_widget(self.ids.scroll)
- self.ids.container.remove_widget(self.ids.text)
- self.ids.spacer_top_box.add_widget(self.content_cls)
- self.ids.spacer_top_box.padding = (0, "24dp", "16dp", 0)
- update_height = True
- if self.type == "alert":
- self.ids.scroll.bar_width = 0
-
- if update_height:
- Clock.schedule_once(self.update_height)
-
def update_width(self, *args) -> None:
- self.width = max(
- self.height + self.width_offset,
+ self.size_hint_max_x = max(
+ self.width_offset,
min(
dp(560) if DEVICE_TYPE != "mobile" else dp(280),
Window.width - self.width_offset,
),
)
- def update_height(self, *args) -> None:
- self._spacer_top = self.content_cls.height + dp(24)
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDDialogIcon):
+ self.ids.icon_container.add_widget(widget)
+ elif isinstance(widget, MDDialogHeadlineText):
+ self.ids.headline_container.add_widget(widget)
+ elif isinstance(widget, MDDialogSupportingText):
+ self.ids.supporting_text_container.add_widget(widget)
+ elif isinstance(widget, MDDialogContentContainer):
+ self.ids.content_container.add_widget(widget)
+ elif isinstance(widget, MDDialogButtonContainer):
+ self.ids.button_container.add_widget(widget)
+ else:
+ return super().add_widget(widget)
+
+ def open(self) -> None:
+ """Show the dialog."""
+
+ if self._is_open:
+ return
+
+ self.dispatch("on_pre_open")
+ self._is_open = True
- def update_items(self, items: list) -> None:
- self.ids.box_items.clear_widgets()
- self.items = items
- self.create_items()
+ if not self._scrim:
+ self._scrim = MDDialogScrim(color=self.scrim_color)
- def on_open(self) -> None:
- # TODO: Add scrolling text.
- self.height = self.ids.container.height
+ Window.add_widget(self._scrim)
+ Window.add_widget(self)
super().on_open()
+ self.dispatch("on_open")
- def get_normal_height(self) -> float:
- return (
- (Window.height * 80 / 100)
- - self._spacer_top
- - dp(52)
- - self.ids.container.padding[1]
- - self.ids.container.padding[-1]
- - 100
- )
+ def on_pre_open(self, *args) -> None:
+ """Fired when a dialog pre opened."""
- def edit_padding_for_item(self, instance_item) -> None:
- instance_item.ids._left_container.x = 0
- instance_item._txt_left_pad = "56dp"
+ def on_open(self, *args) -> None:
+ """Fired when a dialog opened."""
- def create_items(self) -> None:
- if not self.text:
- self.ids.container.remove_widget(self.ids.text)
- height = 0
- else:
- height = self.ids.text.height
+ def on_dismiss(self, *args) -> None:
+ """Fired when a dialog dismiss."""
- for item in self.items:
- if issubclass(item.__class__, BaseListItem):
- height += item.height # calculate height contents
- self.edit_padding_for_item(item)
- self.ids.box_items.add_widget(item)
+ def on_pre_dismiss(self, *args) -> None:
+ """Fired when a dialog pre-dismiss."""
+
+ def on_touch_down(self, touch):
+ if not self.collide_point(*touch.pos):
+ self.dismiss()
+ return True
+ super().on_touch_down(touch)
+ return True
+
+ def dismiss(self, *args) -> None:
+ """Closes the dialog."""
+
+ self.dispatch("on_pre_dismiss")
+ super().on_dismiss()
+ self._is_open = False
+ self.dispatch("on_dismiss")
+
+
+class MDDialogIcon(MDIcon):
+ """
+ The class implements an icon.
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDIcon` class documentation.
+ """
+
+
+class MDDialogHeadlineText(MDLabel):
+ """
+ The class implements the headline text.
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
+
+
+class MDDialogSupportingText(MDLabel):
+ """
+ The class implements the supporting text.
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
+
+
+class MDDialogContentContainer(DeclarativeBehavior, BoxLayout):
+ """
+ The class implements the container for custom widgets.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation.
+ """
+
+
+class MDDialogButtonContainer(DeclarativeBehavior, BoxLayout):
+ """
+ The class implements a container for placing dialog buttons.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation.
+ """
- if height > Window.height:
- self.ids.scroll.height = self.get_normal_height()
- else:
- self.ids.scroll.height = height
- def create_buttons(self) -> None:
- for button in self.buttons:
- if issubclass(button.__class__, BaseButton):
- self.ids.button_box.add_widget(button)
+class MDDialogScrim(Widget):
+ color = ColorProperty(None)
+ alpha = NumericProperty(0)
diff --git a/kivymd/uix/divider/__init__.py b/kivymd/uix/divider/__init__.py
new file mode 100644
index 000000000..91a40756d
--- /dev/null
+++ b/kivymd/uix/divider/__init__.py
@@ -0,0 +1 @@
+from .divider import MDDivider # NOQA F401
diff --git a/kivymd/uix/divider/divider.kv b/kivymd/uix/divider/divider.kv
new file mode 100644
index 000000000..071ea9070
--- /dev/null
+++ b/kivymd/uix/divider/divider.kv
@@ -0,0 +1,10 @@
+
+ canvas:
+ Color:
+ rgba:
+ app.theme_cls.outlineVariantColor \
+ if not self.color else \
+ self.color
+ Rectangle:
+ size: self.size
+ pos: self.pos
diff --git a/kivymd/uix/divider/divider.py b/kivymd/uix/divider/divider.py
new file mode 100644
index 000000000..0d29be8b9
--- /dev/null
+++ b/kivymd/uix/divider/divider.py
@@ -0,0 +1,49 @@
+# TODO: Add doc strings.
+
+import os
+
+from kivy.lang import Builder
+from kivy.metrics import dp
+from kivy.properties import ColorProperty
+from kivy.uix.boxlayout import BoxLayout
+
+from kivymd import uix_path
+
+with open(
+ os.path.join(uix_path, "divider", "divider.kv"), encoding="utf-8"
+) as kv_file:
+ Builder.load_string(kv_file.read())
+
+
+class MDDivider(BoxLayout):
+ """
+ A separator line.
+
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
+ """
+
+ color = ColorProperty(None)
+ """
+ Separator color in (r, g, b, a) or string format.
+
+ :attr:`color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.on_orientation()
+
+ def on_orientation(self, *args) -> None:
+ """Fired when the values of :attr:`orientation` change."""
+
+ self.size_hint = (
+ (1, None) if self.orientation == "horizontal" else (None, 1)
+ )
+ if self.orientation == "horizontal":
+ self.height = dp(1)
+ else:
+ self.width = dp(1)
diff --git a/kivymd/uix/floatlayout.py b/kivymd/uix/floatlayout.py
index b64ddcfc3..7832d34f2 100644
--- a/kivymd/uix/floatlayout.py
+++ b/kivymd/uix/floatlayout.py
@@ -13,7 +13,7 @@
FloatLayout:
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
RoundedRectangle:
pos: self.pos
size: self.size
@@ -26,7 +26,7 @@
MDFloatLayout:
radius: [25, 0, 0, 0]
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
.. Warning:: For a :class:`~kivy.uix.floatlayout.FloatLayout`, the
``minimum_size`` attributes are always 0, so you cannot use
@@ -37,13 +37,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDFloatLayout(
- DeclarativeBehavior, ThemableBehavior, FloatLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ FloatLayout,
+ MDAdaptiveWidget,
):
"""
- Float layout class. For more information, see in the
- :class:`~kivy.uix.floatlayout.FloatLayout` class documentation.
+ Float layout class.
+
+ For more information see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.floatlayout.FloatLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/gridlayout.py b/kivymd/uix/gridlayout.py
index 96bfd79cb..c7d8c973b 100644
--- a/kivymd/uix/gridlayout.py
+++ b/kivymd/uix/gridlayout.py
@@ -16,7 +16,7 @@
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -28,7 +28,7 @@
MDGridLayout:
adaptive_height: True
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
Available options are:
----------------------
@@ -87,13 +87,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDGridLayout(
- DeclarativeBehavior, ThemableBehavior, GridLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ GridLayout,
+ MDAdaptiveWidget,
):
"""
- Grid layout class. For more information, see in the
- :class:`~kivy.uix.gridlayout.GridLayout` class documentation.
+ Grid layout class.
+
+ For more information see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.gridlayout.GridLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/label/label.kv b/kivymd/uix/label/label.kv
index 7fba34825..7550e52fd 100644
--- a/kivymd/uix/label/label.kv
+++ b/kivymd/uix/label/label.kv
@@ -2,67 +2,36 @@
- disabled_color: self.theme_cls.disabled_hint_text_color
text_size:
(self.width if not self.adaptive_width else None) \
if not self.adaptive_size else None, \
None
+ color:
+ self.text_color \
+ if self.text_color else \
+ self.theme_cls.onSurfaceColor
+ disabled_color:
+ app.theme_cls.onSurfaceColor[:-1] + \
+ [self.label_opacity_value_disabled_text]
+ font_size:
+ self.theme_cls.font_styles[self.font_style][self.role]["font-size"] \
+ if self.theme_font_size == "Primary" else self.font_size
+ line_height:
+ self.theme_cls.font_styles[self.font_style][self.role]["line-height"] \
+ if self.theme_line_height == "Primary" else self.line_height
+ font_name:
+ self.theme_cls.font_styles[self.font_style][self.role]["font-name"] \
+ if self.theme_font_name == "Primary" else self.font_name
-:
- canvas:
- 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 \
- if not self.source else \
- (self.x - self._size[0] / 2, self.y)
- size:
- self._size \
- if self.source else \
- self.size
-
+
font_style: "Icon"
text: u"{}".format(md_icons[root.icon]) if root.icon in md_icons else "blank"
source: None if root.icon in md_icons else root.icon
+ adaptive_size: True
+ color:
+ self.icon_color \
+ if self.icon_color else \
+ self.theme_cls.onSurfaceVariantColor
- # Badge icon.
- MDLabel:
- id: badge
- font_style: "Icon"
- adaptive_size: True
- opposite_icon_color: True
- color: root.badge_icon_color
- text:
- u"{}".format(md_icons[root.badge_icon]) \
- if root.badge_icon in md_icons else \
- ""
- pos:
- root.x + root.width / 2 + self.width / 2 - dp(6), \
- root.y + self.texture_size[1] / 2 + dp(6)
- font_size:
- ( \
- root.font_size / 1.5 \
- if not root.badge_font_size else \
- root.badge_font_size \
- ) \
- if root.badge_icon and root.badge_icon != "blank" else 0
- canvas.before:
- Color:
- rgba:
- ( \
- root.badge_bg_color \
- if root.badge_bg_color else \
- app.theme_cls.error_color \
- ) \
- if root.badge_icon else \
- (0, 0, 0, 0)
- RoundedRectangle:
- group: "badge"
- radius: [self.width / 2,]
- pos: self.pos
- size: self.size
diff --git a/kivymd/uix/label/label.py b/kivymd/uix/label/label.py
index 73bd46b68..bac48d66d 100755
--- a/kivymd/uix/label/label.py
+++ b/kivymd/uix/label/label.py
@@ -2,7 +2,7 @@
Components/Label
================
-.. rubric:: The :class:`MDLabel` widget is for rendering text.
+.. rubric:: The `MDLabel` widget is for rendering text.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label.png
:align: center
@@ -14,9 +14,8 @@
MDLabel
-------
-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:
+Example
+-------
.. tabs::
@@ -30,20 +29,21 @@
KV = '''
MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
MDLabel:
text: "MDLabel"
+ halign: "center"
'''
- class Test(MDApp):
+ class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
- Test().run()
+ Example().run()
.. tab:: Declarative Python style
@@ -59,137 +59,118 @@ def build(self):
class Test(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
return (
MDScreen(
MDLabel(
- text="MDLabel"
- )
+ text="MDLabel",
+ halign="center",
+ ),
+ md_bg_color=self.theme_cls.backgroundColor,
)
)
Test().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-left.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label-example.png
:align: center
-.. Note:: See :attr:`~kivy.uix.label.Label.halign`
- and :attr:`~kivy.uix.label.Label.valign` attributes
- of the :class:`~kivy.uix.label.Label` class
+To use a custom color for :class:`~MDLabel`, use a theme `'Custom'`.
+After that, you can specify the desired color in the ``text_color`` parameter:
.. code-block:: kv
- MDLabel:
- text: "MDLabel"
- halign: "center"
+ MDLabel:
+ text: "Custom color"
+ halign: "center"
+ theme_text_color: "Custom"
+ text_color: "red"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-center.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-custom-color.png
:align: center
-:class:`~MDLabel` color:
-------------------------
-
-:class:`~MDLabel` provides standard color themes for label color management:
-
-.. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.label import MDLabel
-
- KV = '''
- 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",
- "Secondary",
- "Hint",
- "Error",
- "ContrastParentBackground",
- ]:
- screen.add_widget(
- MDLabel(
- text=name_theme,
- halign="center",
- theme_text_color=name_theme,
- )
- )
- return screen
+:class:`~MDLabel` provides standard font styles for labels. To do this,
+specify the name of the desired style in the :attr:`~MDLabel.font_style`
+and :attr:`~MDLabel.role` parameters:
+.. code-block:: kv
- Test().run()
+ MDLabel:
+ text: "Display, role - 'large'"
+ font_style: "Display"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-theme-text-color.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style-display-large.png
:align: center
-To use a custom color for :class:`~MDLabel`, use a theme `'Custom'`.
-After that, you can specify the desired color in the ``rgba`` format
-in the ``text_color`` parameter:
-
.. code-block:: kv
MDLabel:
- text: "Custom color"
- halign: "center"
- theme_text_color: "Custom"
- text_color: "blue"
+ text: "Display, role - 'small'"
+ font_style: "Display"
+ role: "small"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-custom-color.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style-display-small.png
:align: center
-:class:`~MDLabel` provides standard font styles for labels. To do this,
-specify the name of the desired style in the :attr:`~MDLabel.font_style`
-parameter:
+.. seealso::
+
+ `Material Design spec, Typography `_
+
+
+All styles
+----------
.. code-block:: python
from kivy.lang import Builder
- from kivymd.app import MDApp
- from kivymd.uix.label import MDLabel
from kivymd.font_definitions import theme_font_styles
+ from kivymd.app import MDApp
KV = '''
- MDScrollView:
-
- MDList:
- id: box
- spacing: "8dp"
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDRecycleView:
+ id: rv
+ key_viewclass: 'viewclass'
+ key_size: 'height'
+
+ RecycleBoxLayout:
+ padding: dp(10)
+ spacing: dp(10)
+ default_size: None, dp(48)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+ orientation: "vertical"
'''
- class Test(MDApp):
+ class Example(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(
- MDLabel(
- text=f"{name_style} style",
- halign="center",
- font_style=name_style,
- adaptive_height=True,
- )
- )
- return screen
+ return Builder.load_string(KV)
+ def on_start(self):
+ for style in theme_font_styles:
+ if style != "Icon":
+ for role in theme_font_styles[style]:
+ font_size = int(theme_font_styles[style][role]["font-size"])
+ self.root.ids.rv.data.append(
+ {
+ "viewclass": "MDLabel",
+ "text": f"{style} {role} {font_size} sp",
+ "adaptive_height": "True",
+ "font_style": style,
+ "role": role,
+ }
+ )
- Test().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style.png
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label-font-style-preview.png
:align: center
Highlighting and copying labels
@@ -210,11 +191,12 @@ def build(self):
KV = '''
MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
MDLabel:
adaptive_size: True
pos_hint: {"center_x": .5, "center_y": .5}
- text: "MDLabel"
+ text: "Do a double click on me"
allow_selection: True
padding: "4dp", "4dp"
'''
@@ -223,7 +205,6 @@ def build(self):
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
return Builder.load_string(KV)
@@ -233,7 +214,7 @@ def build(self):
.. code-block:: python
- from kivy.lang.builder import Builder
+ from kivy.clock import Clock
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
@@ -241,18 +222,23 @@ def build(self):
class Example(MDApp):
+ def on_start(self):
+ def on_start(dt):
+ self.root.md_bg_color = self.theme_cls.backgroundColor
+
+ Clock.schedule_once(on_start)
+
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",
+ pos_hint={"center_x": 0.5, "center_y": 0.5},
+ text="Do a double click on me",
allow_selection=True,
padding=("4dp", "4dp"),
- )
+ ),
)
)
@@ -481,7 +467,6 @@ def open_context_menu(self, instance_label: CopyLabel) -> None:
MDIcon:
icon: "gmail"
- pos_hint: {"center_x": .5, "center_y": .5}
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon.png
:align: center
@@ -493,8 +478,9 @@ def open_context_menu(self, instance_label: CopyLabel) -> None:
MDIcon:
icon: "gmail"
- badge_icon: "numeric-10"
- pos_hint: {"center_x": .5, "center_y": .5}
+
+ MDBadge:
+ text: "10+"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-badge.png
:align: center
@@ -505,45 +491,34 @@ def open_context_menu(self, instance_label: CopyLabel) -> None:
__all__ = ("MDLabel", "MDIcon")
import os
-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.graphics import Color, RoundedRectangle
from kivy.lang import Builder
-from kivy.metrics import sp
+
from kivy.properties import (
- AliasProperty,
BooleanProperty,
ColorProperty,
- ListProperty,
- NumericProperty,
ObjectProperty,
- OptionProperty,
StringProperty,
+ VariableListProperty,
+ OptionProperty,
)
from kivy.uix.label import Label
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, TouchBehavior
-from kivymd.uix.floatlayout import MDFloatLayout
-
-__MDLabel_colors__ = {
- "Primary": "text_color",
- "Secondary": "secondary_text_color",
- "Hint": "disabled_hint_text_color",
- "Error": "error_color",
- "OP": {
- "Primary": "opposite_text_color",
- "Secondary": "opposite_secondary_text_color",
- "Hint": "opposite_disabled_hint_text_color",
- },
-}
+from kivymd.uix.behaviors import (
+ DeclarativeBehavior,
+ TouchBehavior,
+ BackgroundColorBehavior,
+)
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
+
with open(
os.path.join(uix_path, "label", "label.kv"), encoding="utf-8"
@@ -554,80 +529,68 @@ def open_context_menu(self, instance_label: CopyLabel) -> None:
class MDLabel(
DeclarativeBehavior,
ThemableBehavior,
+ BackgroundColorBehavior,
Label,
MDAdaptiveWidget,
TouchBehavior,
+ StateLayerBehavior,
):
"""
Label class.
For more information, see in the
- :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
:class:`~kivy.uix.label.Label` and
:class:`~kivymd.uix.MDAdaptiveWidget` and
- :class:`~kivymd.uix.behaviors.TouchBehavior`
+ :class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior` and
+ :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior`
classes documentation.
:Events:
`on_ref_press`
- Called when the user clicks on a word referenced with a
+ Fired 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.
+ Fired when double-tapping on the label.
`on_selection`
- Called when double-tapping on the label.
+ Fired when double-tapping on the label.
`on_cancel_selection`
- Called when the highlighting is removed from the label text.
+ Fired when the highlighting is removed from the label text.
"""
- font_style = StringProperty("Body1")
+ font_style = StringProperty("Body")
"""
Label font style.
- Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`,
- `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`,
- `'Caption'`, `'Overline'`, `'Icon'`.
+ .. versionchanged:: 2.0.0
+
+ Available vanilla font_style are: `'Display'`, `'Headline'`, `'Title'`,
+ `'Label'`, `'Body'``.
:attr:`font_style` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'Body1'`.
+ and defaults to `'Body'`.
"""
- _capitalizing = BooleanProperty(False)
-
- def _get_text(self):
- if self._capitalizing:
- return self._text.upper()
- return self._text
-
- def _set_text(self, value):
- self._text = value
+ role = OptionProperty("large", options=["large", "medium", "small"])
+ """
+ Role of font style.
- _text = StringProperty()
+ .. versionadded:: 2.0.0
- text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"])
- """Text of the label."""
+ Available options are: `'large'`, `'medium'`, `'small'`.
- theme_text_color = OptionProperty(
- "Primary",
- allownone=True,
- options=[
- "Primary",
- "Secondary",
- "Hint",
- "Error",
- "Custom",
- "ContrastParentBackground",
- ],
- )
+ :attr:`role` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `'large'`.
"""
- Label color scheme name.
- Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
- `'Custom'`, `'ContrastParentBackground'`.
+ text = StringProperty()
+ """
+ Text of the label.
- :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `None`.
+ :attr:`text` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `''`.
"""
text_color = ColorProperty(None)
@@ -690,55 +653,26 @@ def _set_text(self, value):
and defaults to `False`.
"""
- _text_color_str = StringProperty()
+ radius = VariableListProperty([0], length=4)
+ """
+ Label radius.
- parent_background = ColorProperty(None)
- can_capitalize = BooleanProperty(True)
- canvas_bg = ObjectProperty()
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[0, 0, 0, 0]`.
+ """
+
+ _canvas_bg = ObjectProperty()
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)
-
- def check_font_styles(self, interval: Union[int, float] = 0) -> bool:
- if self.font_style not in list(self.theme_cls.font_styles.keys()):
- raise ValueError(
- f"MDLabel.font_style is set to an invalid option '{self.font_style}'."
- f"Must be one of: {list(self.theme_cls.font_styles)}"
- )
- else:
- return True
-
- def update_font_style(self, instance_label, font_style: str) -> None:
- if self.check_font_styles() is True:
- font_info = self.theme_cls.font_styles[self.font_style]
- self.font_name = font_info[0]
- 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
- else:
- self._capitalizing = False
-
- # 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
+ self.theme_cls.secondaryContainerColor
if not self.color_selection
else self.color_selection
)
@@ -746,7 +680,7 @@ def do_selection(self) -> None:
def cancel_selection(self) -> None:
if self.is_selected:
self.md_bg_color = (
- self.theme_cls.bg_normal
+ self.parent.md_bg_color
if not self.color_deselection
else self.color_deselection
)
@@ -754,6 +688,8 @@ def cancel_selection(self) -> None:
self.is_selected = False
def on_double_tap(self, touch, *args) -> None:
+ """Fired by double-clicking on the widget."""
+
if self.allow_copy and self.collide_point(*touch.pos):
Clipboard.copy(self.text)
self.dispatch("on_copy")
@@ -762,73 +698,44 @@ def on_double_tap(self, touch, *args) -> None:
self.dispatch("on_selection")
self.is_selected = True
- def on_window_touch(self, *args):
+ def on_window_touch(self, *args) -> None:
+ """Fired at the on_touch_down event."""
+
if self.is_selected:
self.cancel_selection()
def on_copy(self, *args) -> None:
"""
- Called when double-tapping on the label.
+ Fired when double-tapping on the label.
.. versionadded:: 1.2.0
"""
def on_selection(self, *args) -> None:
"""
- Called when double-tapping on the label.
+ Fired 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.
+ Fired when the highlighting is removed from the label text.
.. versionadded:: 1.2.0
"""
def on_allow_selection(self, instance_label, selection: bool) -> None:
+ """Fired when the :attr:`allow_selection` value changes."""
+
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:
- op = self.opposite_colors
- if op:
- self._text_color_str = __MDLabel_colors__.get("OP", "").get(
- theme_text_color, ""
- )
- else:
- self._text_color_str = __MDLabel_colors__.get(theme_text_color, "")
- if self._text_color_str:
- self._do_update_theme_color()
- else:
- # 'Custom' and 'ContrastParentBackground' lead here, as well as the
- # generic None value it's not yet been set
- self._text_color_str = ""
- if theme_text_color == "Custom" and self.text_color:
- color = self.text_color
- elif (
- theme_text_color == "ContrastParentBackground"
- and self.parent_background
- ):
- color = get_contrast_text_color(self.parent_background)
- else:
- color = [0, 0, 0, 1]
-
- if self.theme_cls.theme_style_switch_animation:
- Animation(
- color=color,
- d=self.theme_cls.theme_style_switch_animation_duration,
- t="linear",
- ).start(self)
- else:
- self.color = color
+ def on_text_color(self, instance_label, color: list | str) -> None:
+ """Fired when the :attr:`text_color` value changes."""
- def on_text_color(self, instance_label, color: Union[list, str]) -> None:
if self.theme_text_color == "Custom":
if self.theme_cls.theme_style_switch_animation:
Animation(
@@ -839,116 +746,92 @@ def on_text_color(self, instance_label, color: Union[list, str]) -> None:
else:
self.color = self.text_color
- def on_opposite_colors(self, *args) -> None:
- self.on_theme_text_color(self, self.theme_text_color)
+ def on_md_bg_color(self, instance_label, color: list | str) -> None:
+ """Fired when the :attr:`md_bg_color` value changes."""
- 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)
- self.bind(pos=self.update_canvas_bg_pos)
+ def on_md_bg_color(*args) -> None:
+ from kivymd.uix.selectioncontrol import MDCheckbox
- def on_size(self, instance_label, size: list) -> None:
- if self.canvas_bg:
- self.canvas_bg.size = size
+ if not issubclass(self.__class__, (MDCheckbox, MDIcon)):
+ self.canvas.remove_group("Background_instruction")
- def update_canvas_bg_pos(self, instance_label, pos: list) -> None:
- if self.canvas_bg:
- self.canvas_bg.pos = pos
+ # FIXME: IndexError
+ # try:
+ # self.canvas.before.clear()
+ # except IndexError:
+ # pass
- def _do_update_theme_color(self, *args):
- if self._text_color_str:
- if not self.disabled:
- color = getattr(self.theme_cls, self._text_color_str)
- else:
- color = getattr(self.theme_cls, "disabled_hint_text_color")
+ with self.canvas.before:
+ Color(rgba=color)
+ self._canvas_bg = RoundedRectangle(
+ pos=self.pos, size=self.size, radius=self.radius
+ )
+ self.bind(pos=self.update_canvas_bg_pos)
- if self.theme_cls.theme_style_switch_animation:
- Animation(
- color=color,
- d=self.theme_cls.theme_style_switch_animation_duration,
- t="linear",
- ).start(self)
- else:
- self.color = color
+ Clock.schedule_once(on_md_bg_color)
+
+ def on_size(self, instance_label, size: list) -> None:
+ """Fired when the parent window of the application is resized."""
+ if self._canvas_bg:
+ self._canvas_bg.size = size
+
+ def update_canvas_bg_pos(self, instance_label, pos: list) -> None:
+ if self._canvas_bg:
+ self._canvas_bg.pos = pos
-class MDIcon(MDFloatLayout, MDLabel):
+
+class MDIcon(MDLabel):
"""
Icon class.
- For more information, see in the :class:`~MDLabel` and
- :class:`~kivymd.uix.floatlayout.MDFloatLayout` classes documentation.
+ For more information, see in the
+ :class:`~MDLabel` class documentation.
"""
- icon = StringProperty("android")
+ icon = StringProperty("blank")
"""
Label icon name.
:attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'android'`.
- """
-
- badge_icon = StringProperty()
+ and defaults to `'blank'`.
"""
- Label badge icon name.
- .. versionadded:: 1.0.0
-
- :attr:`badge_icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- badge_icon_color = ColorProperty([1, 1, 1, 1])
+ source = StringProperty(None, allownone=True)
"""
- Badge icon color in (r, g, b, a) or string format.
-
- .. versionadded:: 1.0.0
+ Path to icon.
- :attr:`badge_icon_color` is an :class:`~kivy.properties.ColorProperty`
+ :attr:`source` is an :class:`~kivy.properties.StringProperty`
and defaults to `None`.
"""
- badge_bg_color = ColorProperty(None)
+ icon_color = ColorProperty(None)
"""
- Badge icon background color in (r, g, b, a) or string format.
+ Icon color in (r, g, b, a) or string format.
- .. versionadded:: 1.0.0
+ .. versionadded:: 2.0.0
- :attr:`badge_bg_color` is an :class:`~kivy.properties.ColorProperty`
+ :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- badge_font_size = NumericProperty()
- """
- Badge font size.
-
- .. versionadded:: 1.0.0
-
- :attr:`badge_font_size` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
+ icon_color_disabled = ColorProperty(None)
"""
+ The icon color in (r, g, b, a) or string format of the button when
+ the button is disabled.
- source = StringProperty(None, allownone=True)
- """
- Path to icon.
+ .. versionadded:: 2.0.0
- :attr:`source` is an :class:`~kivy.properties.StringProperty`
+ :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- _size = ListProperty((0, 0))
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- Clock.schedule_once(self.adjust_size)
+ # kivymd.uix.badge.badge.MDBadge object.
+ _badge = ObjectProperty()
- def adjust_size(self, *args) -> None:
- from kivymd.uix.selectioncontrol import MDCheckbox
+ def add_widget(self, widget, index=0, canvas=None):
+ from kivymd.uix.badge import MDBadge
- if not isinstance(self, MDCheckbox):
- self.size_hint = (None, None)
- self._size = self.texture_size[1], self.texture_size[1]
- self.adaptive_size = True
+ if isinstance(widget, MDBadge):
+ self._badge = widget
+ return super().add_widget(widget)
diff --git a/kivymd/uix/list/__init__.py b/kivymd/uix/list/__init__.py
index a0e061cfe..096715aa6 100644
--- a/kivymd/uix/list/__init__.py
+++ b/kivymd/uix/list/__init__.py
@@ -1,33 +1,16 @@
# NOQA F401
from .list import (
- BaseListItem,
- CheckboxLeftWidget,
- IconLeftWidget,
- IconLeftWidgetWithoutTouch,
- IconRightWidget,
- IconRightWidgetWithoutTouch,
- ILeftBody,
- ILeftBodyTouch,
- ImageLeftWidget,
- ImageLeftWidgetWithoutTouch,
- ImageRightWidget,
- ImageRightWidgetWithoutTouch,
- IRightBody,
- IRightBodyTouch,
MDList,
- OneLineAvatarIconListItem,
- OneLineAvatarListItem,
- OneLineIconListItem,
- OneLineListItem,
- OneLineRightIconListItem,
- ThreeLineAvatarIconListItem,
- ThreeLineAvatarListItem,
- ThreeLineIconListItem,
- ThreeLineListItem,
- ThreeLineRightIconListItem,
- TwoLineAvatarIconListItem,
- TwoLineAvatarListItem,
- TwoLineIconListItem,
- TwoLineListItem,
- TwoLineRightIconListItem,
+ MDListItem,
+ BaseListItem,
+ BaseListItemText,
+ BaseListItemIcon,
+ MDListItemLeadingIcon,
+ MDListItemTrailingIcon,
+ MDListItemHeadlineText,
+ MDListItemTertiaryText,
+ MDListItemLeadingAvatar,
+ MDListItemSupportingText,
+ MDListItemTrailingCheckbox,
+ MDListItemTrailingSupportingText,
)
diff --git a/kivymd/uix/list/list.kv b/kivymd/uix/list/list.kv
index 467d1c823..f7e7bae8a 100644
--- a/kivymd/uix/list/list.kv
+++ b/kivymd/uix/list/list.kv
@@ -1,173 +1,152 @@
-#:import m_res kivymd.material_resources
-
-
cols: 1
adaptive_height: True
padding: 0, self._list_vertical_padding
-
- size_hint_y: None
-
- canvas:
+
+ # Divider.
+ canvas.after:
Color:
rgba:
( \
- self.theme_cls.divider_color \
- if root.divider is not None \
- else (0, 0, 0, 0) \
+ ( \
+ self.theme_cls.surfaceVariantColor \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor \
+ ) \
+ if self.theme_divider_color == "Primary" else \
+ self.divider_color
) \
- if not root.divider_color \
- else \
- root.divider_color
+ if self.divider else self.theme_cls.transparentColor
Line:
- points:
- ( \
- root.x ,root.y, root.x + self.width, root.y) \
- if root.divider == "Full" else \
- (root.x + root._txt_left_pad, root.y, \
- root.x + self.width - root._txt_left_pad-root._txt_right_pad, \
- root.y \
- )
- Color:
- rgba: root.bg_color if root.bg_color else (0, 0, 0, 0)
- RoundedRectangle:
- pos: self.pos
- size: self.size
- radius: root.radius
-
- BoxLayout:
- id: _text_container
- orientation: "vertical"
- pos: root.pos
- padding:
- root._txt_left_pad, root._txt_top_pad, \
- root._txt_right_pad, root._txt_bot_pad
-
- MDLabel:
- id: _lbl_primary
- text: root.text
- font_style: root.font_style
- theme_text_color: root.theme_text_color
- text_color: root.text_color
- size_hint_y: None
- height: self.texture_size[1]
- markup: True
- shorten_from: "right"
- shorten: True
-
- MDLabel:
- id: _lbl_secondary
- text: "" if root._num_lines == 1 else root.secondary_text
- font_style: root.secondary_font_style
- theme_text_color: root.secondary_theme_text_color
- text_color: root.secondary_text_color
- size_hint_y: None
- height: 0 if root._num_lines == 1 else self.texture_size[1]
- shorten: True
- shorten_from: "right"
- markup: True
-
- MDLabel:
- id: _lbl_tertiary
- text: "" if root._num_lines == 1 else root.tertiary_text
- font_style: root.tertiary_font_style
- theme_text_color: root.tertiary_theme_text_color
- text_color: root.tertiary_text_color
- size_hint_y: None
- height: 0 if root._num_lines == 1 else self.texture_size[1]
- shorten: True
- shorten_from: "right"
- markup: True
-
-
-
-
- BoxLayout:
- id: _left_container
- size_hint: None, None
- x: root.x + dp(16)
- y: root.y + root.height / 2 - self.height / 2
- size: dp(40), dp(40)
-
-
-
-
- BoxLayout:
- id: _left_container
- size_hint: None, None
- x: root.x + dp(16)
- y: root.y + root.height - root._txt_top_pad - self.height - dp(5)
- size: dp(40), dp(40)
-
-
-
-
- BoxLayout:
- id: _left_container
- size_hint: None, None
- x: root.x + dp(16)
- y: root.y + root.height / 2 - self.height / 2
- size: dp(48), dp(48)
-
-
-
-
- BoxLayout:
- id: _left_container
- size_hint: None, None
- x: root.x + dp(16)
- y: root.y + root.height - root._txt_top_pad - self.height - dp(5)
- size: dp(48), dp(48)
-
-
-
-
- BoxLayout:
- id: _right_container
- size_hint: None, None
- x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
- y: root.y + root.height / 2 - self.height / 2
- size: dp(48), dp(48)
-
-
-
-
- BoxLayout:
- id: _right_container
- size_hint: None, None
- x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
- y: root.y + root.height / 2 - self.height / 2
- size: dp(48), dp(48)
-
-
-
-
- BoxLayout:
- id: _right_container
- size_hint: None, None
- x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
- y: root.y + root.height / 2 - self.height / 2
- size: dp(48), dp(48)
+ width: 1
+ points: self.x ,self.y, self.x + self.width, self.y
+ size_hint_y: None
+ spacing: "16dp"
+ padding:
+ "16dp", \
+ "12dp" if len(text_container.children) == 3 else "8dp", \
+ "24dp", \
+ "12dp" if len(text_container.children) == 3 else "8dp"
+ # FIXME: The design of the material suggests specifying the
+ # background color of the disabled widget as the "onSurface"
+ # color when hovering the mouse cursor. But this color is very
+ # dark/light. So I chose the color "onSurfaceColor" color with 12
+ # percent transparency.
+ md_bg_color:
+ self.theme_cls.surfaceColor \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color
+ height:
+ { \
+ 0: "100dp", \
+ 1: "56dp", \
+ 2: "72dp", \
+ 3: "88dp", \
+ } \
+ [len(text_container.children)]
+ on_disabled:
+ leading_container.children[0].disabled = args[1]
-
BoxLayout:
- id: _right_container
- size_hint: None, None
- x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
- y: root.y + root.height / 2 - self.height / 2
- size: dp(48), dp(48)
+ id: leading_container
+ size_hint_x: None
+ width: 0
+ AnchorLayout:
+ anchor_y: "center"
-
+ BoxLayout:
+ id: text_container
+ orientation: "vertical"
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: "2dp"
+ on_children:
+ if leading_container.children: \
+ leading_container.children[0].pos_hint = {"top": 1} \
+ if len(args[1]) == 3 else {"center_y": .5}
BoxLayout:
- id: _right_container
- size_hint: None, None
- x: root.x + root.width - m_res.HORIZ_MARGINS - self.width
- y: root.y + root.height - root._txt_top_pad - self.height - dp(5)
- size: dp(48), dp(48)
+ id: trailing_container
+ size_hint_x: None
+ width: 0
+ on_children:
+ if text_container.children: \
+ self.children[0].pos_hint = {"top": 1} \
+ if len(text_container.children) == 3 else {"center_y": .5}
+
+
+
+ size_hint: None, None
+ size: "24dp", "24dp"
+ text_color:
+ ( \
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color \
+ if self.icon_color else \
+ self.theme_cls.transparentColor \
+ ) \
+ ) \
+ if not self.disabled else self.disabled_color
+ disabled_color:
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.icon_button_standard_opacity_value_disabled_icon] \
+ if not self.icon_color_disabled else self.icon_color_disabled
+
+
+
+ adaptive_width: True
+ font_style: "Label"
+ role: "small"
+
+
+
+ size_hint: None, None
+ size: "40dp", "40dp"
+ radius: self.height / 2
+ # FIXME: The design of the material suggests specifying the
+ # background color of the disabled widget as the "onSurface"
+ # color when hovering the mouse cursor. But this color is very
+ # dark/light. So I chose the color "onSurfaceColor" color with 12
+ # percent transparency.
+ md_bg_color:
+ self.theme_cls.primaryContainerColor \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] \
+ + ( \
+ [self._list_item.list_opacity_value_disabled_leading_avatar] \
+ if self._list_item else [0] \
+ )
+
+
+
+ adaptive_height: True
+ markup: True
+ shorten_from: "right"
+ font_style: "Body"
+ role: "medium"
+ shorten: True
+ text_color:
+ self.theme_cls.onSurfaceVariantColor \
+ if root.theme_text_color == "Primary" else \
+ ( \
+ root.text_color \
+ if root.text_color else \
+ self.theme_cls.onSurfaceVariantColor \
+ )
+
+
+
+ font_style: "Body"
+ role: "large"
+ bold: True
+ # FIXME: `RecursionError: maximum recursion depth exceeded while calling
+ # a Python object` when use `text_color` property.
+ -text_color: self.theme_cls.onSurfaceColor if root.theme_text_color == "Primary" else (root.text_color if root.text_color else self.theme_cls.onSurfaceColor)
diff --git a/kivymd/uix/list/list.py b/kivymd/uix/list/list.py
index 035ca8a1e..e05609f9a 100755
--- a/kivymd/uix/list/list.py
+++ b/kivymd/uix/list/list.py
@@ -4,146 +4,51 @@
.. seealso::
- `Material Design spec, Lists `_
+ `Material Design spec, Lists `_
.. rubric:: Lists are continuous, vertical indexes of text or images.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.png
:align: center
-The class :class:`~MDList` in combination with a :class:`~BaseListItem` like
-:class:`~OneLineListItem` will create a list that expands as items are added to
-it, working nicely with `Kivy's` :class:`~kivy.uix.scrollview.ScrollView`.
-
-Due to the variety in sizes and controls in the `Material Design spec`,
-this module suffers from a certain level of complexity to keep the widgets
-compliant, flexible and performant.
-
-For this `KivyMD` provides list items that try to cover the most common usecases,
-when those are insufficient, there's a base class called :class:`~BaseListItem`
-which you can use to create your own list items. This documentation will only
-cover the provided ones, for custom implementations please refer to this
-module's source code.
-
-`KivyMD` provides the following list items classes for use:
-
-Text only ListItems
--------------------
-
-- OneLineListItem_
-- TwoLineListItem_
-- ThreeLineListItem_
-
-ListItems with widget containers
---------------------------------
-
-These widgets will take other widgets that inherit from :class:`~ILeftBody`,
-:class:`ILeftBodyTouch`, :class:`~IRightBody` or :class:`~IRightBodyTouch` and
-put them in their corresponding container.
-
-As the name implies, :class:`~ILeftBody` and :class:`~IRightBody` will signal
-that the widget goes into the left or right container, respectively.
-
-:class:`~ILeftBodyTouch` and :class:`~IRightBodyTouch` do the same thing,
-except these widgets will also receive touch events that occur within their
-surfaces.
-
-`KivyMD` provides base classes such as :class:`~ImageLeftWidget`,
-:class:`~ImageRightWidget`, :class:`~IconRightWidget`, :class:`~IconLeftWidget`,
-based on the above classes.
-
-.. rubric:: Allows the use of items with custom widgets on the left.
-
-- OneLineAvatarListItem_
-- TwoLineAvatarListItem_
-- ThreeLineAvatarListItem_
-
-- OneLineIconListItem_
-- TwoLineIconListItem_
-- ThreeLineIconListItem_
-
-.. rubric:: It allows the use of elements with custom widgets on the left
- and the right.
-
-- OneLineAvatarIconListItem_
-- TwoLineAvatarIconListItem_
-- ThreeLineAvatarIconListItem_
-
-- OneLineRightIconListItem_
-- TwoLineRightIconListItem_
-- ThreeLineRightIconListItem_
+- Use lists to help users find a specific item and act on it;
+- Order list items in logical ways (like alphabetical or numerical);
+- Three sizes: one-line, two-line, and three-line;
+- Keep items short and easy to scan;
+- Show icons, text, and actions in a consistent format;
Usage
-----
-.. tabs::
-
- .. tab:: Declarative KV style
-
- .. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.list import OneLineListItem
-
- KV = '''
- MDScrollView:
-
- MDList:
- id: container
- '''
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
-
- def on_start(self):
- for i in range(20):
- self.root.ids.container.add_widget(
- OneLineListItem(text=f"Single-line item {i}")
- )
-
- Example().run()
+.. code-block:: kv
- .. tab:: Declarative python style
+ MDListItem:
- .. code-block:: python
+ MDListItemLeadingIcon: # MDListItemLeadingAvatar
- from kivymd.app import MDApp
- from kivymd.uix.list import OneLineListItem
+ MDListItemHeadlineText:
+ MDListItemSupportingText:
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return (
- MDScrollView(
- MDList(
- id="container"
- )
- )
- )
+ MDListItemTertiaryText:
- def on_start(self):
- for i in range(20):
- self.root.ids.container.add_widget(
- OneLineListItem(text=f"Single-line item {i}")
- )
+ MDListItemTrailingIcon: # MDListItemTrailingCheckbox
- Example().run()
+Anatomy
+-------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists-anatomy.png
:align: center
-Events of List
---------------
+Example:
+========
+
+One line list item
+------------------
.. tabs::
- .. tab:: Declarative KV style
+ .. tab:: Declarative KV styles
.. code-block:: python
@@ -152,1484 +57,567 @@ def on_start(self):
from kivymd.app import MDApp
KV = '''
- MDScrollView:
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
- MDList:
+ MDListItem:
+ pos_hint: {"center_x": .5, "center_y": .5}
+ size_hint_x: .8
- OneLineAvatarIconListItem:
- on_release: print("Click!")
-
- IconLeftWidget:
- icon: "github"
-
- OneLineAvatarIconListItem:
- on_release: print("Click 2!")
-
- IconLeftWidget:
- icon: "gitlab"
+ MDListItemHeadlineText:
+ text: "Headline"
'''
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
Example().run()
- .. tab:: Declarative python style
+ .. tab:: Declarative Python styles
.. code-block:: python
+ from kivymd.uix.list import MDListItem, MDListItemHeadlineText
+ from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
- from kivymd.uix.scrollview import MDScrollView
- from kivymd.uix.list import MDList, OneLineAvatarIconListItem, IconLeftWidget
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
return (
- MDScrollView(
- MDList(
- OneLineAvatarIconListItem(
- IconLeftWidget(
- icon="github"
- ),
- on_release=lambda x: print("Click!")
- ),
- OneLineAvatarIconListItem(
- IconLeftWidget(
- icon="gitlab"
- ),
- on_release=lambda x: print("Click 2!")
+ MDScreen(
+ MDListItem(
+ MDListItemHeadlineText(
+ text="Headline",
),
- )
+ pos_hint={"center_x": .5, "center_y": .5},
+ size_hint_x=0.8,
+ ),
+ md_bg_color=self.theme_cls.backgroundColor,
)
)
Example().run()
-.. OneLineListItem:
-OneLineListItem
----------------
-
-.. code-block:: kv
-
- OneLineListItem:
- text: "Single-line item"
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineListItem.png
- :align: center
-
-.. TwoLineListItem:
-TwoLineListItem
----------------
-
-.. code-block:: kv
-
- TwoLineListItem:
- text: "Two-line item"
- secondary_text: "Secondary text here"
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineListItem.png
- :align: center
-
-.. ThreeLineListItem:
-ThreeLineListItem
------------------
-
-.. code-block:: kv
-
- ThreeLineListItem:
- text: "Three-line item"
- secondary_text: "This is a multi-line label where you can"
- tertiary_text: "fit more text than usual"
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineListItem.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-list.gif
:align: center
-.. OneLineAvatarListItem:
-OneLineAvatarListItem
----------------------
+Two line list item
+------------------
.. tabs::
- .. tab:: Declarative KV style
+ .. tab:: Declarative KV styles
.. code-block:: kv
- OneLineAvatarListItem:
- text: "Single-line item with avatar"
+ MDListItem:
+
+ MDListItemHeadlineText:
+ text: "Headline"
- ImageLeftWidget:
- source: "kivymd/images/logo/kivymd-icon-256.png"
+ MDListItemSupportingText:
+ text: "Supporting text"
- .. tab:: Declarative python style
+ .. tab:: Declarative Python styles
.. code-block:: python
- OneLineAvatarListItem(
- ImageLeftWidget(
- source="kivymd/images/logo/kivymd-icon-256.png"
+ MDListItem(
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemSupportingText(
+ text="Supporting text",
),
- text="Single-line item with avatar",
)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarListItem.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-list.png
:align: center
-.. TwoLineAvatarListItem:
-TwoLineAvatarListItem
----------------------
+Three line list item
+--------------------
.. tabs::
- .. tab:: Declarative KV style
+ .. tab:: Declarative KV styles
.. code-block:: kv
- TwoLineAvatarListItem:
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
+ MDListItem:
+
+ MDListItemHeadlineText:
+ text: "Headline"
- ImageLeftWidget:
- source: "kivymd/images/logo/kivymd-icon-256.png"
+ MDListItemSupportingText:
+ text: "Supporting text"
- .. tab:: Declarative python style
+ MDListItemTertiaryText:
+ text: "Tertiary text"
+
+ .. tab:: Declarative Python styles
.. code-block:: python
- OneLineAvatarListItem(
- ImageLeftWidget(
- source="kivymd/images/logo/kivymd-icon-256.png"
+ MDListItem(
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemSupportingText(
+ text="Supporting text",
+ ),
+ MDListItemTertiaryText(
+ text="Tertiary text",
),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarListItem.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-list.png
:align: center
-.. ThreeLineAvatarListItem:
-ThreeLineAvatarListItem
------------------------
+List item with leading icon
+---------------------------
.. tabs::
- .. tab:: Declarative KV style
+ .. tab:: Declarative KV styles
.. code-block:: kv
- ThreeLineAvatarListItem:
- text: "Three-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
-
- ImageLeftWidget:
- source: "kivymd/images/logo/kivymd-icon-256.png"
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- OneLineAvatarListItem(
- ImageLeftWidget(
- source="kivymd/images/logo/kivymd-icon-256.png"
- ),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
- tertiary_text: "fit more text than usual"
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarListItem.png
- :align: center
-
-.. OneLineRightIconListItem:
-OneLineRightIconListItem
-------------------------
-
-.. tabs::
+ MDListItem:
- .. tab:: Declarative KV style
+ MDListItemLeadingIcon:
+ icon: "account"
- .. code-block:: kv
+ MDListItemHeadlineText:
+ text: "Headline"
- OneLineRightIconListItem:
- text: "Single-line item with avatar"
+ MDListItemSupportingText:
+ text: "Supporting text"
- ImageRightWidget:
- source: "kivymd/images/logo/kivymd-icon-256.png"
+ MDListItemTertiaryText:
+ text: "Tertiary text"
- .. tab:: Declarative python style
+ .. tab:: Declarative Python styles
.. code-block:: python
- OneLineRightIconListItem(
- ImageRightWidget(
- source="kivymd/images/logo/kivymd-icon-256.png"
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="account",
+ ),
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemSupportingText(
+ text="Supporting text",
+ ),
+ MDListItemTertiaryText(
+ text="Tertiary text",
),
- text="Single-line item with avatar",
)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineRightIconListItem.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-leading-icon-list.png
:align: center
-.. TwoLineRightIconListItem:
-TwoLineRightIconListItem
-------------------------
+List item with trailing icon
+----------------------------
.. tabs::
- .. tab:: Declarative KV style
+ .. tab:: Declarative KV styles
.. code-block:: kv
- TwoLineRightIconListItem:
- text: "Single-line item with avatar"
- secondary_text: "Secondary text here"
-
- ImageRightWidget:
- source: "kivymd/images/logo/kivymd-icon-256.png"
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- TwoLineRightIconListItem(
- ImageRightWidget(
- source="kivymd/images/logo/kivymd-icon-256.png"
- ),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineRightIconListItem.png
- :align: center
-
-.. ThreeLineRightIconListItem:
-ThreeLineRightIconListItem
---------------------------
+ MDListItem:
-.. tabs::
+ MDListItemLeadingIcon:
+ icon: "account"
- .. tab:: Declarative KV style
+ MDListItemHeadlineText:
+ text: "Headline"
- .. code-block:: kv
+ MDListItemSupportingText:
+ text: "Supporting text"
- ThreeLineRightIconListItem:
- text: "Single-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
+ MDListItemTertiaryText:
+ text: "Tertiary text"
- ImageRightWidget:
- source: "kivymd/images/logo/kivymd-icon-256.png"
+ MDListItemTrailingIcon:
+ icon: "trash-can-outline"
- .. tab:: Declarative python style
+ .. tab:: Declarative Python styles
.. code-block:: python
- ThreeLineRightIconListItem(
- ImageRightWidget(
- source="kivymd/images/logo/kivymd-icon-256.png"
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="account",
+ ),
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemSupportingText(
+ text="Supporting text",
+ ),
+ MDListItemTertiaryText(
+ text="Tertiary text",
+ ),
+ MDListItemTrailingIcon(
+ icon="trash-can-outline",
),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
- tertiary_text: "fit more text than usual",
)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineRightIconListItem.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-leading-trailing-icon-list.png
:align: center
-.. OneLineIconListItem:
-OneLineIconListItem
--------------------
+List item with trailing check
+----------------------------
.. tabs::
- .. tab:: Declarative KV style
+ .. tab:: Declarative KV styles
.. code-block:: kv
- OneLineIconListItem:
- text: "Single-line item with avatar"
-
- IconLeftWidget:
- icon: "language-python"
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- OneLineIconListItem(
- IconLeftWidget(
- icon="language-python"
- ),
- text="Single-line item with avatar"
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineIconListItem.png
- :align: center
-
-.. TwoLineIconListItem:
-TwoLineIconListItem
--------------------
+ MDListItem:
-.. tabs::
+ MDListItemLeadingIcon:
+ icon: "account"
- .. tab:: Declarative KV style
+ MDListItemHeadlineText:
+ text: "Headline"
- .. code-block:: kv
+ MDListItemSupportingText:
+ text: "Supporting text"
- TwoLineIconListItem:
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
+ MDListItemTertiaryText:
+ text: "Tertiary text"
- IconLeftWidget:
- icon: "language-python"
+ MDListItemTrailingCheckbox:
- .. tab:: Declarative python style
+ .. tab:: Declarative Python styles
.. code-block:: python
- TwoLineIconListItem(
- IconLeftWidget(
- icon="language-python"
+ MDListItem(
+ MDListItemLeadingIcon(
+ icon="account",
+ ),
+ MDListItemHeadlineText(
+ text="Headline",
+ ),
+ MDListItemSupportingText(
+ text="Supporting text",
+ ),
+ MDListItemTertiaryText(
+ text="Tertiary text",
+ ),
+ MDListItemTrailingCheckbox(
),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here"
)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineIconListItem.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-leading-trailing-check-list.png
:align: center
+"""
-.. ThreeLineIconListItem:
-ThreeLineIconListItem
----------------------
-
-.. tabs::
+from __future__ import annotations
- .. tab:: Declarative KV style
+__all__ = (
+ "BaseListItemText",
+ "BaseListItem",
+ "BaseListItemIcon",
+ "MDList",
+ "MDListItem",
+ "MDListItemHeadlineText",
+ "MDListItemSupportingText",
+ "MDListItemTrailingSupportingText",
+ "MDListItemLeadingIcon",
+ "MDListItemTrailingIcon",
+ "MDListItemTrailingCheckbox",
+ "MDListItemLeadingAvatar",
+ "MDListItemTertiaryText",
+)
- .. code-block:: kv
+import os
- ThreeLineIconListItem:
- text: "Three-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
+from kivy import Logger
+from kivy.clock import Clock
+from kivy.lang import Builder
+from kivy.properties import (
+ NumericProperty,
+ ObjectProperty,
+ BooleanProperty,
+ ColorProperty,
+)
+from kivy.uix.behaviors import ButtonBehavior
+from kivy.uix.boxlayout import BoxLayout
- IconLeftWidget:
- icon: "language-python"
+from kivymd.uix.selectioncontrol import MDCheckbox
+from kivymd import uix_path
+from kivymd.theming import ThemableBehavior
+from kivymd.uix.behaviors import (
+ CircularRippleBehavior,
+ DeclarativeBehavior,
+ RectangularRippleBehavior,
+ BackgroundColorBehavior,
+)
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
+from kivymd.uix.fitimage import FitImage
+from kivymd.uix.gridlayout import MDGridLayout
+from kivymd.uix.label import MDLabel, MDIcon
- .. tab:: Declarative python style
+with open(
+ os.path.join(uix_path, "list", "list.kv"), encoding="utf-8"
+) as kv_file:
+ Builder.load_string(kv_file.read())
- .. code-block:: python
- ThreeLineIconListItem(
- IconLeftWidget(
- icon="language-python"
- ),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
- tertiary_text: "fit more text than usual",
- )
+class MDList(MDGridLayout):
+ """
+ ListItem container.
+ Best used in conjunction with a :class:`kivy.uix.ScrollView`.
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineIconListItem.png
- :align: center
+ 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.
-.. OneLineAvatarIconListItem:
-OneLineAvatarIconListItem
--------------------------
+ For more information, see in the
+ :class:`~kivymd.uix.gridlayout.MDGridLayout` class documentation.
+ """
-.. tabs::
+ _list_vertical_padding = NumericProperty("8dp")
- .. tab:: Declarative KV style
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.adaptive_height = True
- .. code-block:: kv
- OneLineAvatarIconListItem:
- text: "One-line item with avatar"
+class BaseListItem(
+ DeclarativeBehavior,
+ BackgroundColorBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ ThemableBehavior,
+ StateLayerBehavior,
+):
+ """
+ Base class for list items.
- IconLeftWidget:
- icon: "plus"
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.behaviors.state_layer_behavior.StateLayerBehavior`
+ classes documentation.
+ """
- IconRightWidget:
- icon: "minus"
+ divider = BooleanProperty(False)
+ """
+ Should I use divider for a list item.
- .. tab:: Declarative python style
+ :attr:`divider` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
- .. code-block:: python
+ divider_color = ColorProperty(None)
+ """
+ The divider color in (r, g, b, a) or string format.
- OneLineAvatarIconListItem(
- IconLeftWidget(
- icon="plus"
- ),
- IconRightWidget(
- icon="minus"
- ),
- text="Single-line item with avatar",
- )
+ :attr:`divider_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarIconListItem.png
- :align: center
+ md_bg_color_disabled = ColorProperty(None)
+ """
+ The background color in (r, g, b, a) or string format of the list item when
+ the list item is disabled.
-.. TwoLineAvatarIconListItem:
-TwoLineAvatarIconListItem
--------------------------
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
-.. tabs::
- .. tab:: Declarative KV style
+class BaseListItemText(MDLabel):
+ """
+ Base class for text labels of a list item.
- .. code-block:: kv
+ For more information, see in the :class:`~kivymd.uix.label.label.MDLabel`
+ class documentation.
+ """
- TwoLineAvatarIconListItem:
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
- IconLeftWidget:
- icon: "plus"
+class BaseListItemIcon(MDIcon):
+ """
+ Base class for leading/trailing icon of list item.
- IconRightWidget:
- icon: "minus"
+ For more information, see in the :class:`~kivymd.uix.label.label.MDIcon`
+ class documentation.
+ """
- .. tab:: Declarative python style
+ icon_color = ColorProperty(None)
+ """
+ Icon color in (r, g, b, a) or string format.
- .. code-block:: python
+ :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
- TwoLineAvatarIconListItem(
- IconLeftWidget(
- icon="plus"
- ),
- IconRightWidget(
- icon="minus"
- ),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
- )
+ icon_color_disabled = ColorProperty(None)
+ """
+ The icon color in (r, g, b, a) or string format of the list item when
+ the list item is disabled.
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarIconListItem.png
- :align: center
+ :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
-.. ThreeLineAvatarIconListItem:
-ThreeLineAvatarIconListItem
----------------------------
-.. tabs::
+class MDListItemHeadlineText(BaseListItemText):
+ """
+ Implements a class for headline text of list item.
- .. tab:: Declarative KV style
+ For more information, see in the :class:`~BaseListItemText`
+ class documentation.
+ """
- .. code-block:: kv
- ThreeLineAvatarIconListItem:
- text: "Three-line item with avatar"
- secondary_text: "Secondary text here"
- tertiary_text: "fit more text than usual"
+class MDListItemSupportingText(BaseListItemText):
+ """
+ Implements a class for secondary text of list item.
- IconLeftWidget:
- icon: "plus"
+ For more information, see in the :class:`~BaseListItemText`
+ class documentation.
+ """
- IconRightWidget:
- icon: "minus"
- .. tab:: Declarative python style
+class MDListItemTertiaryText(BaseListItemText):
+ """
+ Implements a class for tertiary text of list item.
- .. code-block:: python
+ For more information, see in the :class:`~BaseListItemText`
+ class documentation.
+ """
- ThreeLineAvatarIconListItem(
- IconLeftWidget(
- icon="plus"
- ),
- IconRightWidget(
- icon="minus"
- ),
- text="Single-line item with avatar",
- secondary_text: "Secondary text here",
- tertiary_text: "fit more text than usual",
- )
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarIconListItem.png
- :align: center
+class MDListItemTrailingSupportingText(BaseListItemText):
+ """
+ Implements a class for trailing text of list item.
-Custom list item
-----------------
+ For more information, see in the :class:`~BaseListItemText`
+ class documentation.
+ """
-.. tabs::
- .. tab:: Declarative KV style
+class MDListItemLeadingIcon(BaseListItemIcon):
+ """
+ Implements a class for leading icon class.
- .. code-block:: python
+ For more information, see in the :class:`~BaseListItemIcon`
+ class documentation.
+ """
- from kivy.lang import Builder
- from kivy.properties import StringProperty
- from kivymd.app import MDApp
- from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
- from kivymd.uix.selectioncontrol import MDCheckbox
- from kivymd.icon_definitions import md_icons
+class MDListItemLeadingAvatar(CircularRippleBehavior, ButtonBehavior, FitImage):
+ """
+ Implements a class for leading avatar class.
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivymd.uix.fitimage.fitimage.FitImage`
+ classes documentation.
+ """
- KV = '''
- :
+ _list_item = ObjectProperty()
- IconLeftWidget:
- icon: root.icon
- RightCheckbox:
+class MDListItemTrailingIcon(BaseListItemIcon):
+ """
+ Implements a class for trailing icon class.
+ For more information, see in the :class:`~BaseListItemIcon`
+ class documentation.
+ """
- MDScrollView:
- MDList:
- id: scroll
- '''
+class MDListItemTrailingCheckbox(MDCheckbox):
+ """
+ Implements a class for trailing checkbox class.
+ For more information, see in the
+ :class:`~kivymd.uix.selectioncontrol.selectioncontrol.MDCheckbox`
+ class documentation.
+ """
- class ListItemWithCheckbox(OneLineAvatarIconListItem):
- '''Custom list item.'''
- icon = StringProperty("android")
+class MDListItem(BaseListItem, BoxLayout):
+ """
+ Implements a list item.
+ For more information, see in the
+ :class:`~BaseListItem` and
+ :class:`~kivy.uix.boxlayout.BoxLayout`
+ classes documentation.
+ """
- class RightCheckbox(IRightBodyTouch, MDCheckbox):
- '''Custom right container.'''
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(
+ widget,
+ (
+ MDListItemHeadlineText,
+ MDListItemSupportingText,
+ MDListItemTertiaryText,
+ ),
+ ):
+ if len(self.ids.text_container.children) < 3:
+ self.ids.text_container.add_widget(widget)
+ elif len(self.ids.text_container.children) > 3:
+ self._set_warnings(widget)
+ elif isinstance(
+ widget, (MDListItemLeadingIcon, MDListItemLeadingAvatar)
+ ):
+ if not self.ids.leading_container.children:
+ widget._list_item = self
+ self.ids.leading_container.add_widget(widget)
+ Clock.schedule_once(
+ lambda x: self._set_with_container(
+ self.ids.leading_container, widget
+ )
+ )
+ else:
+ self._set_warnings(widget)
+ elif isinstance(
+ widget,
+ (
+ MDListItemTrailingIcon,
+ MDListItemTrailingCheckbox,
+ MDListItemTrailingSupportingText,
+ ),
+ ):
+ if not self.ids.trailing_container.children:
+ self.ids.trailing_container.add_widget(widget)
+ Clock.schedule_once(
+ lambda x: self._set_with_container(
+ self.ids.trailing_container, widget
+ )
+ )
+ else:
+ self._set_warnings(widget)
+ else:
+ return super().add_widget(widget)
+ def _set_warnings(self, widget):
+ Logger.warning(
+ f"KivyMD: "
+ f"Do not use more than one <{widget.__class__.__name__}> "
+ f"widget. This is contrary to the material design rules "
+ f"of version 3"
+ )
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
-
- def on_start(self):
- icons = list(md_icons.keys())
- for i in range(30):
- self.root.ids.scroll.add_widget(
- ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
- )
-
-
- Example().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
- from kivymd.uix.selectioncontrol import MDCheckbox
- from kivymd.uix.scrollview import MDScrollView
- from kivymd.uix.list import MDList
- from kivymd.icon_definitions import md_icons
-
-
- class RightCheckbox(IRightBodyTouch, MDCheckbox):
- '''Custom right container.'''
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return (
- MDScrollView(
- MDList(
- id="scroll"
- )
- )
- )
-
- def on_start(self):
- icons = list(md_icons.keys())
- for i in range(30):
- self.root.ids.scroll.add_widget(
- OneLineAvatarIconListItem(
- IconLeftWidget(
- icon=icons[i]
- ),
- RightCheckbox(),
- text=f"Item {i}",
- )
- )
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-item.png
- :align: center
-
-.. tabs::
-
- .. tab:: Declarative KV style
-
- .. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.list import IRightBodyTouch
-
- KV = '''
- OneLineAvatarIconListItem:
- text: "One-line item with avatar"
- on_size:
- self.ids._right_container.width = container.width
- self.ids._right_container.x = container.width
-
- IconLeftWidget:
- icon: "cog"
-
- YourContainer:
- id: container
-
- MDIconButton:
- icon: "minus"
-
- MDIconButton:
- icon: "plus"
- '''
-
-
- class YourContainer(IRightBodyTouch, MDBoxLayout):
- adaptive_width = True
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
-
-
- Example().run()
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.list import IRightBodyTouch
- from kivymd.uix.button import MDIconButton
- from kivymd.uix.list import OneLineAvatarIconListItem, IconLeftWidget
-
-
- class YourContainer(IRightBodyTouch, MDBoxLayout):
- adaptive_width = True
-
-
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return (
- OneLineAvatarIconListItem(
- IconLeftWidget(
- icon="cog"
- ),
- YourContainer(
- MDIconButton(
- icon="minus"
- ),
- MDIconButton(
- icon="plus"
- ),
- id="container"
- ),
- text="One-line item with avatar"
- )
- )
-
- def on_start(self):
- container = self.root.ids.container
- self.root.ids._right_container.width = container.width
- container.x = container.width
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-right-container.png
- :align: center
-
-Behavior
---------
-
-When using the `AvatarListItem` and `IconListItem` classes, when an icon is clicked,
-the event of this icon is triggered:
-
-.. tabs::
-
- .. tab:: Declarative KV style
-
- .. code-block:: kv
-
- OneLineIconListItem:
- text: "Single-line item with icon"
-
- IconLeftWidget:
- icon: "language-python"
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- OneLineIconListItem(
- IconLeftWidget(
- icon="language-python"
- ),
- text="Single-line item with avatar",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-trigger.gif
- :align: center
-
-You can disable the icon event using the `WithoutTouch` classes:
-
-.. tabs::
-
- .. tab:: Declarative KV style
-
- .. code-block:: kv
-
- OneLineIconListItem:
- text: "Single-line item with icon"
-
- IconLeftWidgetWithoutTouch:
- icon: "language-python"
-
- .. tab:: Declarative python style
-
- .. code-block:: python
-
- OneLineIconListItem(
- IconLeftWidgetWithoutTouch(
- icon="language-python"
- ),
- text="Single-line item with avatar",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-without-trigger.gif
- :align: center
-"""
-
-__all__ = (
- "BaseListItem",
- "MDList",
- "ILeftBodyTouch",
- "IRightBodyTouch",
- "OneLineListItem",
- "TwoLineListItem",
- "ThreeLineListItem",
- "OneLineAvatarListItem",
- "TwoLineAvatarListItem",
- "ThreeLineAvatarListItem",
- "OneLineIconListItem",
- "TwoLineIconListItem",
- "ThreeLineIconListItem",
- "OneLineRightIconListItem",
- "TwoLineRightIconListItem",
- "ThreeLineRightIconListItem",
- "OneLineAvatarIconListItem",
- "TwoLineAvatarIconListItem",
- "ThreeLineAvatarIconListItem",
- "ImageLeftWidget",
- "ImageRightWidget",
- "IconRightWidget",
- "IconLeftWidget",
- "CheckboxLeftWidget",
- "IconLeftWidgetWithoutTouch",
- "IconRightWidgetWithoutTouch",
- "ImageRightWidgetWithoutTouch",
- "ImageLeftWidgetWithoutTouch",
-)
-
-import os
-
-from kivy.lang import Builder
-from kivy.metrics import dp
-from kivy.properties import (
- BooleanProperty,
- ColorProperty,
- ListProperty,
- NumericProperty,
- OptionProperty,
- StringProperty,
- VariableListProperty,
-)
-from kivy.uix.behaviors import ButtonBehavior
-from kivy.uix.floatlayout import FloatLayout
-
-import kivymd.material_resources as m_res
-from kivymd import uix_path
-from kivymd.theming import ThemableBehavior
-from kivymd.uix.behaviors import (
- CircularRippleBehavior,
- DeclarativeBehavior,
- RectangularRippleBehavior,
-)
-from kivymd.uix.button import MDIconButton
-from kivymd.uix.fitimage import FitImage
-from kivymd.uix.gridlayout import MDGridLayout
-from kivymd.uix.selectioncontrol import MDCheckbox
-
-with open(
- os.path.join(uix_path, "list", "list.kv"), encoding="utf-8"
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-class MDList(MDGridLayout):
- """
- ListItem container. Best used in conjunction with a
- :class:`kivy.uix.ScrollView`.
-
- 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")
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.adaptive_height = True
-
-
-class BaseListItem(
- DeclarativeBehavior,
- ThemableBehavior,
- RectangularRippleBehavior,
- ButtonBehavior,
- FloatLayout,
-):
- """
- 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()
- """
- Text shown in the first line.
-
- :attr:`text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- text_color = ColorProperty(None)
- """
- Text color in (r, g, b, a) or string format used
- if :attr:`~theme_text_color` is set to `'Custom'`.
-
- :attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- font_style = StringProperty("Subtitle1")
- """
- Text font style.
- See `font-definitions `_
- for more information.
-
- :attr:`font_style` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Subtitle1'`.
- """
-
- theme_text_color = StringProperty("Primary", allownone=True)
- """
- The name of the color scheme for for the primary text.
-
- :attr:`theme_text_color` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Primary'`.
- """
-
- secondary_text = StringProperty()
- """
- Text shown in the second line.
-
- :attr:`secondary_text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- tertiary_text = StringProperty()
- """
- The text is displayed on the third line.
-
- :attr:`tertiary_text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- secondary_text_color = ColorProperty(None)
- """
- Text color in (r, g, b, a) or string format used for secondary text
- if :attr:`~secondary_theme_text_color` is set to `'Custom'`.
-
- :attr:`secondary_text_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- tertiary_text_color = ColorProperty(None)
- """
- Text color in (r, g, b, a) or string format used for tertiary text
- if :attr:`~tertiary_theme_text_color` is set to 'Custom'.
-
- :attr:`tertiary_text_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- secondary_theme_text_color = StringProperty("Secondary", allownone=True)
- """
- The name of the color scheme for for the secondary text.
-
- :attr:`secondary_theme_text_color` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Secondary'`.
- """
-
- tertiary_theme_text_color = StringProperty("Secondary", allownone=True)
- """
- The name of the color scheme for for the tertiary text.
-
- :attr:`tertiary_theme_text_color` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Secondary'`.
- """
-
- secondary_font_style = StringProperty("Body1")
- """
- Font style for secondary line.
- See `font-definitions `_
- for more information.
-
- :attr:`secondary_font_style` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Body1'`.
- """
-
- tertiary_font_style = StringProperty("Body1")
- """
- Font style for tertiary line.
- See `font-definitions `_
- for more information.
-
- :attr:`tertiary_font_style` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'Body1'`.
- """
-
- divider = OptionProperty(
- "Full", options=["Full", "Inset", None], allownone=True
- )
- """
- Divider mode. Available options are: `'Full'`, `'Inset'`
- and default to `'Full'`.
-
- :attr:`divider` is a :class:`~kivy.properties.OptionProperty`
- and defaults to `'Full'`.
- """
-
- divider_color = ColorProperty(None)
- """
- Divider color in (r, g, b, a) or string format.
-
- .. versionadded:: 1.0.0
-
- :attr:`divider_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- bg_color = ColorProperty(None)
- """
- Background color for list item in (r, g, b, a) or string format.
-
- :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- radius = VariableListProperty([0], length=4)
- """
- Canvas radius.
-
- .. code-block:: python
-
- # Top left corner slice.
- MDBoxLayout:
- md_bg_color: app.theme_cls.primary_color
- radius: [25, 0, 0, 0]
-
- :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
- and defaults to `[0, 0, 0, 0]`.
- """
-
- _txt_left_pad = NumericProperty("16dp")
- _txt_top_pad = NumericProperty()
- _txt_bot_pad = NumericProperty()
- _txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS)
- _num_lines = 3
- _no_ripple_effect = BooleanProperty(False)
- _touchable_widgets = ListProperty()
-
- def on_touch_down(self, touch):
- if self.propagate_touch_to_touchable_widgets(touch, "down"):
- return
- super().on_touch_down(touch)
-
- def on_touch_move(self, touch, *args):
- if self.propagate_touch_to_touchable_widgets(touch, "move", *args):
- return
- super().on_touch_move(touch, *args)
-
- def on_touch_up(self, touch):
- if self.propagate_touch_to_touchable_widgets(touch, "up"):
- return
- super().on_touch_up(touch)
-
- def propagate_touch_to_touchable_widgets(self, touch, touch_event, *args):
- triggered = False
- for i in self._touchable_widgets:
- if i.collide_point(touch.x, touch.y):
- triggered = True
- if touch_event == "down":
- i.on_touch_down(touch)
- elif touch_event == "move":
- i.on_touch_move(touch, *args)
- elif touch_event == "up":
- i.on_touch_up(touch)
- return triggered
-
- def add_widget(self, widget):
- if issubclass(widget.__class__, ILeftBody):
- self.ids._left_container.add_widget(widget)
- elif issubclass(widget.__class__, ILeftBodyTouch):
- self.ids._left_container.add_widget(widget)
- self._touchable_widgets.append(widget)
- elif issubclass(widget.__class__, IRightBody):
- self.ids._right_container.add_widget(widget)
- elif issubclass(widget.__class__, IRightBodyTouch):
- self.ids._right_container.add_widget(widget)
- self._touchable_widgets.append(widget)
- else:
- return super().add_widget(widget)
-
- def remove_widget(self, widget):
- super().remove_widget(widget)
- if widget in self._touchable_widgets:
- self._touchable_widgets.remove(widget)
-
-
-class ILeftBody:
- """
- Pseudo-interface for widgets that go in the left container for
- ListItems that support it.
-
- Implements nothing and requires no implementation, for annotation only.
- """
-
-
-class ILeftBodyTouch:
- """
- Same as :class:`~ILeftBody`, but allows the widget to receive touch
- events instead of triggering the ListItem's ripple effect.
- """
-
-
-class IRightBody:
- """
- Pseudo-interface for widgets that go in the right container for
- ListItems that support it.
-
- Implements nothing and requires no implementation, for annotation only.
- """
-
-
-class IRightBodyTouch:
- """
- Same as :class:`~IRightBody`, but allows the widget to receive touch
- events instead of triggering the ``ListItem``'s ripple effect
- """
-
-
-class OneLineListItem(BaseListItem):
- """
- 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")
- _height = NumericProperty()
- _num_lines = 1
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.height = dp(48) if not self._height else self._height
-
-
-class TwoLineListItem(BaseListItem):
- """
- 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")
- _height = NumericProperty()
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.height = dp(72) if not self._height else self._height
-
-
-class ThreeLineListItem(BaseListItem):
- """
- 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")
- _height = NumericProperty()
- _num_lines = 3
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.height = dp(88) if not self._height else self._height
-
-
-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")
- _height = NumericProperty()
- _num_lines = 1
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.height = dp(56) if not self._height else self._height
-
-
-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()
- _num_lines = 2
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.height = dp(72) if not self._height else self._height
-
-
-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):
- super().__init__(*args, **kwargs)
-
-
-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()
- _num_lines = 2
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.height = dp(72) if not self._height else self._height
-
-
-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):
- super().__init__(*args, **kwargs)
- self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS
-
-
-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()
- _num_lines = 2
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.height = dp(72) if not self._height else self._height
-
-
-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):
- super().__init__(**kwargs)
- self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS
-
-
-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):
- super().__init__(*args, **kwargs)
- self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS
-
-
-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):
- super().__init__(*args, **kwargs)
- self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS
-
-
-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):
- super().__init__(*args, **kwargs)
- self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS
-
-
-class TouchBehavior:
- def on_release(self):
- if issubclass(self.parent.parent.__class__, BaseListItem):
- self.parent.parent.dispatch("on_release")
-
-
-class ImageLeftWidget(
- CircularRippleBehavior, ButtonBehavior, ILeftBodyTouch, FitImage
-):
- """
- 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
- """
-
- _no_ripple_effect = True
-
-
-class ImageRightWidget(
- CircularRippleBehavior, ButtonBehavior, IRightBodyTouch, FitImage
-):
- """
- 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
- """
-
- _no_ripple_effect = True
-
-
-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
- """
-
- pos_hint = {"center_y": 0.5}
- _no_ripple_effect = True
-
-
-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
- """
-
- pos_hint = {"center_y": 0.5}
- _no_ripple_effect = True
-
-
-class CheckboxLeftWidget(ILeftBodyTouch, MDCheckbox):
- """
- 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.
- """
+ def _set_with_container(self, container, widget):
+ container.width = widget.width
diff --git a/kivymd/uix/menu/menu.kv b/kivymd/uix/menu/menu.kv
index a53c0715e..8a52ba411 100644
--- a/kivymd/uix/menu/menu.kv
+++ b/kivymd/uix/menu/menu.kv
@@ -22,7 +22,6 @@
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
@@ -34,25 +33,26 @@
+ container.padding[2] \
+ container.spacing \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
MDTrailingTextContainer:
id: trailing_container
text: root.trailing_text
adaptive_width: True
- theme_text_color: "Custom" if root.trailing_text_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.trailing_text_color \
- if root.trailing_text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.trailing_text_color else \
+ root.trailing_text_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -74,16 +74,15 @@
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
- theme_text_color: "Custom" if root.leading_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.leading_icon_color \
- if root.leading_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.leading_icon_color else \
+ root.leading_icon_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
@@ -97,10 +96,11 @@
+ container.spacing \
+ dp(18) \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
Widget:
@@ -108,16 +108,16 @@
id: trailing_container
text: root.trailing_text
adaptive_width: True
- theme_text_color: "Custom" if root.trailing_text_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.trailing_text_color \
- if root.trailing_text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.trailing_text_color else \
+ root.trailing_text_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -140,7 +140,6 @@
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:
@@ -152,10 +151,11 @@
+ container.spacing \
+ dp(18) \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
Widget:
@@ -165,16 +165,16 @@
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
icon: root.trailing_icon
- theme_text_color: "Custom" if root.trailing_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.trailing_icon_color \
- if root.trailing_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.trailing_icon_color else \
+ root.trailing_icon_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -190,21 +190,21 @@
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
- theme_text_color: "Custom" if root.trailing_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.trailing_icon_color \
- if root.trailing_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.trailing_icon_color else \
+ root.trailing_icon_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"
+ theme_text_color: "Custom"
text_color:
- root.trailing_text_color \
- if root.trailing_text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
@@ -222,7 +222,6 @@
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:
@@ -233,10 +232,11 @@
+ container.padding[2] \
+ container.spacing \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
MDTrailingIconTextContainer:
id: trailing_container
@@ -245,10 +245,10 @@
trailing_text_color: root.trailing_text_color
trailing_icon_color: root.trailing_icon_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -263,18 +263,18 @@
text: root.text
valign: "center"
padding_x: "12dp"
- theme_text_color: "Custom" if root.text_color else "Primary"
shorten: True
shorten_from: "right"
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -296,16 +296,15 @@
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
- theme_text_color: "Custom" if root.leading_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.leading_icon_color \
- if root.leading_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.leading_icon_color else \
+ root.leading_icon_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
@@ -319,10 +318,11 @@
+ container.spacing \
+ dp(18) \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
Widget:
@@ -333,10 +333,10 @@
trailing_icon_color: root.trailing_icon_color
trailing_text_color: root.trailing_text_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -358,11 +358,11 @@
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
- theme_text_color: "Custom" if root.leading_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.leading_icon_color \
- if root.leading_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.leading_icon_color else \
+ root.leading_icon_color
MDLabel:
id: label
@@ -371,7 +371,6 @@
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:
@@ -384,10 +383,11 @@
+ container.spacing \
+ dp(18) \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
Widget:
@@ -397,16 +397,16 @@
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
icon: root.trailing_icon
- theme_text_color: "Custom" if root.trailing_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.trailing_icon_color \
- if root.trailing_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.trailing_icon_color else \
+ root.trailing_icon_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -428,11 +428,11 @@
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_y": .5}
- theme_text_color: "Custom" if root.leading_icon_color else "Primary"
+ theme_text_color: "Custom"
text_color:
- root.leading_icon_color \
- if root.leading_icon_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.leading_icon_color else \
+ root.leading_icon_color
MDLabel:
id: label
@@ -441,7 +441,6 @@
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:
@@ -452,15 +451,16 @@
+ container.padding[2] \
+ container.spacing \
)
+ theme_text_color: "Custom"
text_color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+ app.theme_cls.onSurfaceVariantColor \
+ if not root.text_color else \
+ root.text_color
- MDSeparator:
+ MDDivider:
md_bg_color:
( \
- self.theme_cls.divider_color \
+ app.theme_cls.outlineVariantColor \
if not root.divider_color \
else root.divider_color \
) \
@@ -470,14 +470,18 @@
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
+ focus_behavior: False
+ style: "elevated"
+ elevation_level: 2
+ shadow_softness: 1.5
+ shadow_radius: 4
+ theme_bg_color: root.theme_bg_color
+# md_bg_color:
+# app.theme_cls.surfaceContainerColor \
+# if root.theme_bg_color == "Primary" else \
+# root.md_bg_color
+
MDBoxLayout:
id: content_header
diff --git a/kivymd/uix/menu/menu.py b/kivymd/uix/menu/menu.py
index 012eef466..b8acebc05 100755
--- a/kivymd/uix/menu/menu.py
+++ b/kivymd/uix/menu/menu.py
@@ -636,14 +636,14 @@ def build(self):
)
from kivy.uix.recycleview import RecycleView
-import kivymd.material_resources as m_res
from kivymd import uix_path
from kivymd.uix.behaviors import StencilBehavior, RectangularRippleBehavior
from kivymd.uix.behaviors.motion_behavior import MotionDropDownMenuBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
-from kivymd.uix.list import IRightBody
+
+# from kivymd.uix.list import IRightBody
with open(
os.path.join(uix_path, "menu", "menu.kv"), encoding="utf-8"
@@ -762,7 +762,7 @@ class BaseDropdownItem(RectangularRippleBehavior, ButtonBehavior, MDBoxLayout):
"""
-class MDTrailingTextContainer(BaseDropdownItem, IRightBody, MDLabel):
+class MDTrailingTextContainer(BaseDropdownItem, MDLabel):
"""
Implements a container for trailing text.
@@ -775,7 +775,7 @@ class MDTrailingTextContainer(BaseDropdownItem, IRightBody, MDLabel):
"""
-class MDTrailingIconTextContainer(BaseDropdownItem, IRightBody, MDBoxLayout):
+class MDTrailingIconTextContainer(BaseDropdownItem, MDBoxLayout):
"""
Implements a container for trailing icons and trailing text.
@@ -1058,42 +1058,6 @@ class MDDropdownMenu(MotionDropDownMenuBehavior, StencilBehavior, MDCard):
and defaults to `'[dp(7)]'`.
"""
- elevation = NumericProperty(m_res.DROP_DOWN_MENU_ELEVATION)
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation`
- attribute.
-
- :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `2`.
- """
-
- shadow_radius = VariableListProperty([6], length=4)
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_radius`
- attribute.
-
- :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty`
- and defaults to `[6]`.
- """
-
- shadow_softness = NumericProperty(m_res.DROP_DOWN_MENU_SOFTNESS)
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness`
- attribute.
-
- :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `6`.
- """
-
- shadow_offset = ListProperty(m_res.DROP_DOWN_MENU_OFFSET)
- """
- See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset`
- attribute.
-
- :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
- and defaults to `(0, -2)`.
- """
-
_items = []
_start_coords = []
_tar_x = 0
@@ -1424,7 +1388,7 @@ def _remove_menu(self, *args):
from kivy.metrics import dp
from kivymd.app import MDApp
- from kivymd.uix.button import MDRaisedButton
+ from kivymd.uix.button import MDButton
from kivymd.uix.screen import MDScreen
class Test(MDApp):
@@ -1452,7 +1416,11 @@ def on_start(self):
]
for pos_hint in pos_hints:
self.screen.add_widget(
- MDRaisedButton(pos_hint=pos_hint, on_release=self.open_menu)
+ MDButton(
+ text="Press me",
+ pos_hint=pos_hint,
+ on_release=self.open_menu,
+ )
)
def build(self):
diff --git a/kivymd/uix/navigationbar/__init__.py b/kivymd/uix/navigationbar/__init__.py
new file mode 100644
index 000000000..a492e0200
--- /dev/null
+++ b/kivymd/uix/navigationbar/__init__.py
@@ -0,0 +1,7 @@
+# NOQA F401
+from .navigationbar import (
+ MDNavigationBar,
+ MDNavigationItem,
+ MDNavigationItemLabel,
+ MDNavigationItemIcon,
+)
diff --git a/kivymd/uix/navigationbar/navigationbar.kv b/kivymd/uix/navigationbar/navigationbar.kv
new file mode 100644
index 000000000..719d06491
--- /dev/null
+++ b/kivymd/uix/navigationbar/navigationbar.kv
@@ -0,0 +1,133 @@
+
+ size_hint_y: None
+ height: "80dp"
+ elevation_level: 0
+ md_bg_color:
+ self.theme_cls.surfaceContainerColor \
+ if root.theme_bg_color == "Primary" else \
+ root.md_bg_color
+
+
+
+ size_hint: None, None
+ size: "24sp", "24sp"
+ # theme_text_color: "Custom"
+ icon_color:
+ ( \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.active else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ if self.theme_icon_color == "Primary" else \
+ ( \
+ self.icon_color_active if self.icon_color_active else \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.active else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ ) \
+ if self.parent.parent.active else \
+ ( \
+ self.icon_color_normal if self.icon_color_normal else \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.active else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ ) \
+ ) \
+ if self.parent else self.theme_cls.transparentColor
+ on_icon:
+ if self.icon not in md_icons.keys(): \
+ self.size_hint = (None, None); \
+ self.width = self.font_size; \
+ self.height = self.font_size
+
+ canvas.before:
+ Color:
+ rgba:
+ ( \
+ ( \
+ self.theme_cls.secondaryContainerColor \
+ if not self.parent.parent.indicator_color else \
+ self.parent.parent.indicator_color \
+ ) \
+ if self.parent.parent.active else \
+ self.theme_cls.transparentColor \
+ ) \
+ if self.parent else self.theme_cls.transparentColor
+ RoundedRectangle:
+ radius: [16,]
+ size:
+ ( \
+ (self.parent.parent._selected_region_width, dp(32)) \
+ ) \
+ if self.parent else (0, dp(32))
+ pos:
+ ( \
+ (self.center_x - self.parent.parent._selected_region_width / 2, \
+ self.center_y - dp(16)) \
+ ) \
+ if self.parent else (0, 0)
+
+
+
+ adaptive_size: True
+ role: "medium"
+ text_color:
+ ( \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.active else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ if self.theme_text_color == "Primary" else \
+ ( \
+ self.text_color_active if self.text_color_active else \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.active else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ ) \
+ if self.parent.parent.active else \
+ ( \
+ self.text_color_normal if self.text_color_normal else \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.active else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ ) \
+ ) \
+ if self.parent else self.theme_cls.transparentColor
+
+
+
+
+ MDNavigationItemIconContainer:
+ id: icon_container
+ size_hint: None, None
+ size: self.minimum_size
+ pos_hint: {"center_x": .5}
+ y:
+ ( \
+ (root.parent.height - (self.height + dp(16))) \
+ if len(label_container.children) else \
+ (root.parent.height / 2 - self.height / 2) \
+ ) \
+ if root.parent else 0
+
+ MDNavigationItemLabelContainer:
+ id: label_container
+ size_hint: None, None
+ size: self.minimum_size
+ pos_hint: {"center_x": .5}
+ y:
+ "16dp" \
+ if len(icon_container.children) else \
+ ( \
+ (root.parent.height / 2 - self.height / 2) if root.parent else 0 \
+ )
diff --git a/kivymd/uix/navigationbar/navigationbar.py b/kivymd/uix/navigationbar/navigationbar.py
new file mode 100644
index 000000000..e2f45cc05
--- /dev/null
+++ b/kivymd/uix/navigationbar/navigationbar.py
@@ -0,0 +1,624 @@
+"""
+Components/Navigation bar
+=========================
+
+.. seealso::
+
+ `Material Design 3 spec, Navigation bar `_
+
+.. rubric:: Bottom navigation bars allow movement between primary destinations in an app:
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png
+ :align: center
+
+Usage
+-----
+
+.. code-block:: kv
+
+
+
+ MDNavigationBar:
+
+ MDNavigationItem:
+
+ MDNavigationItemIcon:
+
+ MDNavigationItemLabel:
+
+ [...]
+
+Anatomy
+=======
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigationbar-item-anatomy.png
+ :align: center
+
+Example
+-------
+
+.. tabs::
+
+ .. tab:: Declarative KV style
+
+ .. code-block:: python
+
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty
+
+ from kivymd.app import MDApp
+ from kivymd.uix.navigationbar import MDNavigationBar, MDNavigationItem
+ from kivymd.uix.screen import MDScreen
+
+
+ class BaseMDNavigationItem(MDNavigationItem):
+ icon = StringProperty()
+ text = StringProperty()
+
+
+ class BaseScreen(MDScreen):
+ image_size = StringProperty()
+
+
+ KV = '''
+
+
+ MDNavigationItemIcon:
+ icon: root.icon
+
+ MDNavigationItemLabel:
+ text: root.text
+
+
+
+
+ FitImage:
+ source: f"https://picsum.photos/{root.image_size}/{root.image_size}"
+ size_hint: .9, .9
+ pos_hint: {"center_x": .5, "center_y": .5}
+ radius: dp(24)
+
+
+ MDBoxLayout:
+ orientation: "vertical"
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDScreenManager:
+ id: screen_manager
+
+ BaseScreen:
+ name: "Screen 1"
+ image_size: "1024"
+
+ BaseScreen:
+ name: "Screen 2"
+ image_size: "800"
+
+ BaseScreen:
+ name: "Screen 3"
+ image_size: "600"
+
+
+ MDNavigationBar:
+ on_switch_tabs: app.on_switch_tabs(*args)
+
+ BaseMDNavigationItem
+ icon: "gmail"
+ text: "Screen 1"
+ active: True
+
+ BaseMDNavigationItem
+ icon: "twitter"
+ text: "Screen 2"
+
+ BaseMDNavigationItem
+ icon: "linkedin"
+ text: "Screen 3"
+ '''
+
+
+ class Example(MDApp):
+ def on_switch_tabs(
+ self,
+ bar: MDNavigationBar,
+ item: MDNavigationItem,
+ item_icon: str,
+ item_text: str,
+ ):
+ self.root.ids.screen_manager.current = item_text
+
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Example().run()
+
+ .. tab:: Declarative python style
+
+ .. code-block:: python
+
+ from kivy.metrics import dp
+ from kivy.properties import StringProperty
+
+ from kivymd.uix.fitimage import FitImage
+ from kivymd.uix.screen import MDScreen
+ from kivymd.uix.screenmanager import MDScreenManager
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.navigationbar import (
+ MDNavigationBar,
+ MDNavigationItem,
+ MDNavigationItemLabel,
+ MDNavigationItemIcon,
+ )
+ from kivymd.app import MDApp
+
+
+ class BaseMDNavigationItem(MDNavigationItem):
+ icon = StringProperty()
+ text = StringProperty()
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.add_widget(MDNavigationItemIcon(icon=self.icon))
+ self.add_widget(MDNavigationItemLabel(text=self.text))
+
+
+ class BaseScreen(MDScreen):
+ image_size = StringProperty()
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.add_widget(
+ FitImage(
+ source=f"https://picsum.photos/{self.image_size}/{self.image_size}",
+ size_hint=(0.9, 0.9),
+ pos_hint={"center_x": 0.5, "center_y": 0.5},
+ radius=dp(24),
+ ),
+ )
+
+
+ class Example(MDApp):
+ def on_switch_tabs(
+ self,
+ bar: MDNavigationBar,
+ item: MDNavigationItem,
+ item_icon: str,
+ item_text: str,
+ ):
+ self.root.get_ids().screen_manager.current = item_text
+
+ def build(self):
+ return MDBoxLayout(
+ MDScreenManager(
+ BaseScreen(
+ name="Screen 1",
+ image_size="1024",
+ ),
+ BaseScreen(
+ name="Screen 2",
+ image_size="800",
+ ),
+ BaseScreen(
+ name="Screen 3",
+ image_size="600",
+ ),
+ id="screen_manager",
+ ),
+ MDNavigationBar(
+ BaseMDNavigationItem(
+ icon="gmail",
+ text="Screen 1",
+ active=True,
+ ),
+ BaseMDNavigationItem(
+ icon="twitter",
+ text="Screen 2",
+ ),
+ BaseMDNavigationItem(
+ icon="linkedin",
+ text="Screen 3",
+ ),
+ on_switch_tabs=self.on_switch_tabs,
+ ),
+ orientation="vertical",
+ md_bg_color=self.theme_cls.backgroundColor,
+ )
+
+
+ Example().run()
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigationbar-usage.gif
+ :align: center
+
+API break
+=========
+
+1.2.0 version
+-------------
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+
+ from kivymd.app import MDApp
+
+
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(
+ '''
+ MDScreen:
+
+ MDBottomNavigation:
+
+ MDBottomNavigationItem:
+ name: 'screen 1'
+ text: 'Mail'
+ icon: 'gmail'
+ badge_icon: "numeric-10"
+
+ MDLabel:
+ text: 'Screen 1'
+ halign: 'center'
+
+ MDBottomNavigationItem:
+ name: 'screen 2'
+ text: 'Twitter'
+ icon: 'twitter'
+
+ MDLabel:
+ text: 'Screen 2'
+ halign: 'center'
+ '''
+ )
+
+
+ Example().run()
+
+2.0.0 version
+-------------
+
+MDNavigationBar in version 2.0.0 no longer provides a screen manager for
+content placement. You have to implement it yourself. This is due to the fact
+that when using MDNavigationBar and MDTabs widgets at the same time, there
+were conflicts between their screen managers.
+
+.. code-block:: python
+
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty
+
+ from kivymd.app import MDApp
+ from kivymd.uix.navigationbar import MDNavigationBar, MDNavigationItem
+ from kivymd.uix.screen import MDScreen
+
+
+ class BaseMDNavigationItem(MDNavigationItem):
+ icon = StringProperty()
+ text = StringProperty()
+
+
+ class BaseScreen(MDScreen):
+ ...
+
+
+ KV = '''
+
+
+ MDNavigationItemIcon:
+ icon: root.icon
+
+ MDNavigationItemLabel:
+ text: root.text
+
+
+
+
+ MDLabel:
+ text: root.name
+ halign: "center"
+
+
+ MDBoxLayout:
+ orientation: "vertical"
+ md_bg_color: self.theme_cls.backgroundColor
+
+ MDScreenManager:
+ id: screen_manager
+
+ BaseScreen:
+ name: "Screen 1"
+
+ BaseScreen:
+ name: "Screen 2"
+
+
+ MDNavigationBar:
+ on_switch_tabs: app.on_switch_tabs(*args)
+
+ BaseMDNavigationItem
+ icon: "gmail"
+ text: "Screen 1"
+ active: True
+
+ BaseMDNavigationItem
+ icon: "twitter"
+ text: "Screen 2"
+ '''
+
+
+ class Example(MDApp):
+ def on_switch_tabs(
+ self,
+ bar: MDNavigationBar,
+ item: MDNavigationItem,
+ item_icon: str,
+ item_text: str,
+ ):
+ self.root.ids.screen_manager.current = item_text
+
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Example().run()
+"""
+
+from __future__ import annotations
+
+__all__ = (
+ "MDNavigationItem",
+ "MDNavigationBar",
+ "MDNavigationItemLabel",
+ "MDNavigationItemIcon",
+)
+
+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,
+ NumericProperty,
+ StringProperty,
+)
+from kivy.uix.behaviors import ButtonBehavior
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.relativelayout import RelativeLayout
+
+from kivymd.uix.label import MDLabel, MDIcon
+from kivymd.uix.boxlayout import MDBoxLayout
+from kivymd import uix_path
+from kivymd.uix.behaviors import (
+ DeclarativeBehavior,
+ CommonElevationBehavior,
+ RectangularRippleBehavior,
+)
+from kivymd.utils.set_bars_colors import set_bars_colors
+
+with open(
+ os.path.join(uix_path, "navigationbar", "navigationbar.kv"),
+ encoding="utf-8",
+) as kv_file:
+ Builder.load_string(kv_file.read())
+
+
+class MDNavigationItemLabel(MDLabel):
+ """
+ Implements a text label for the :class:`~MDNavigationItem` class.
+
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
+
+ text_color_active = ColorProperty(None)
+ """
+ Item icon color in (r, g, b, a) or string format.
+
+ :attr:`text_color_active` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ text_color_normal = ColorProperty(None)
+ """
+ Item icon color in (r, g, b, a) or string format.
+
+ :attr:`text_color_normal` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+
+class MDNavigationItemIcon(MDIcon):
+ """
+ Implements a icon for the :class:`~MDNavigationItem` class.
+
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDIcon` class documentation.
+ """
+
+ icon_color_active = ColorProperty(None)
+ """
+ Item icon color in (r, g, b, a) or string format.
+
+ :attr:`icon_color_active` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ icon_color_normal = ColorProperty(None)
+ """
+ Item icon color in (r, g, b, a) or string format.
+
+ :attr:`icon_color_normal` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+
+class MDNavigationItem(
+ DeclarativeBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ RelativeLayout,
+):
+ """
+ Bottom item class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivy.uix.anchorlayout.AnchorLayout` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior`
+ classes documentation.
+
+ .. versionchanged:: 2.0.0
+ Rename class from `MDBottomNavigationItem` to `MDNavigationItem`.
+ """
+
+ active = BooleanProperty(False)
+ """
+ Indicates if the bar item is active or inactive.
+
+ :attr:`active` is a :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ indicator_color = ColorProperty(None)
+ """
+ The background color in (r, g, b, a) or string format of the highlighted
+ item.
+
+ .. versionadded:: 1.0.0
+
+ .. versionchanged:: 2.0.0
+ Rename property from `selected_color_background` to `indicator_color`.
+
+ :attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ indicator_transition = StringProperty("in_out_sine")
+ """
+ Animation type of the active element indicator.
+
+ :attr:`indicator_transition` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `'in_out_sine'`.
+ """
+
+ indicator_duration = NumericProperty(0.1)
+ """
+ Duration of animation of the active element indicator.
+
+ :attr:`indicator_duration` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.1`.
+ """
+
+ _selected_region_width = NumericProperty(dp(0))
+
+ def on_active(self, instance, value) -> None:
+ """Fired when the values of :attr:`active` change."""
+
+ def on_active(*args):
+ Animation(
+ _selected_region_width=dp(64) if value else 0,
+ t=self.indicator_transition,
+ d=self.indicator_duration,
+ ).start(self)
+
+ Clock.schedule_once(on_active)
+
+ def on_release(self) -> None:
+ """Fired when clicking on a panel item."""
+
+ self.parent.set_active_item(self)
+
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDNavigationItemLabel):
+ self.ids.label_container.add_widget(widget)
+ elif isinstance(widget, MDNavigationItemIcon):
+ self.ids.icon_container.add_widget(widget)
+ elif isinstance(
+ widget,
+ (MDNavigationItemIconContainer, MDNavigationItemLabelContainer),
+ ):
+ return super().add_widget(widget)
+
+
+class MDNavigationBar(CommonElevationBehavior, MDBoxLayout):
+ """
+ A navigation bar class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivymd.uix.boxlayout.MDBoxLayout`
+ classes documentation.
+
+ :Events:
+ :attr:`on_switch_tabs`
+ Fired when switching tabs.
+
+ .. versionadded:: 1.0.0
+
+ .. versionchanged:: 2.0.0
+ Rename class from `MDBottomNavigation` to `MDNavigationBar`.
+ """
+
+ set_bars_color = BooleanProperty(False)
+ """
+ If `True` the background color of the navigation bar will be set
+ automatically according to the current color of the toolbar.
+
+ .. versionadded:: 1.0.0
+
+ :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.register_event_type("on_switch_tabs")
+ super().__init__(*args, **kwargs)
+ Clock.schedule_once(self.set_status_bar_color)
+
+ def set_active_item(self, item: MDNavigationItem) -> None:
+ """Sets the currently active element on the panel."""
+
+ for widget in self.children:
+ if item is widget:
+ widget.active = True
+ self.dispatch(
+ "on_switch_tabs",
+ widget,
+ widget.ids.icon_container.children[0].icon
+ if len(widget.ids.icon_container.children)
+ else "",
+ widget.ids.label_container.children[0].text
+ if len(widget.ids.label_container.children)
+ else "",
+ )
+ else:
+ widget.active = False
+
+ def set_status_bar_color(self, interval: int | float) -> None:
+ """Sets the color of the lower system navigation bar."""
+
+ if self.set_bars_color:
+ set_bars_colors(self.md_bg_color, None, self.theme_cls.theme_style)
+
+ def on_switch_tabs(
+ self, item: MDNavigationItem, item_icon: str, item_text: str
+ ) -> None:
+ """Fired when switching tabs."""
+
+
+class MDNavigationItemIconContainer(BoxLayout):
+ pass
+
+
+class MDNavigationItemLabelContainer(BoxLayout):
+ pass
diff --git a/kivymd/uix/navigationdrawer/__init__.py b/kivymd/uix/navigationdrawer/__init__.py
index 1e859f918..db6485ffa 100644
--- a/kivymd/uix/navigationdrawer/__init__.py
+++ b/kivymd/uix/navigationdrawer/__init__.py
@@ -4,6 +4,9 @@
MDNavigationDrawerDivider,
MDNavigationDrawerHeader,
MDNavigationDrawerItem,
+ MDNavigationDrawerItemLeadingIcon,
+ MDNavigationDrawerItemText,
+ MDNavigationDrawerItemTrailingText,
MDNavigationDrawerLabel,
MDNavigationDrawerMenu,
MDNavigationLayout,
diff --git a/kivymd/uix/navigationdrawer/navigationdrawer.kv b/kivymd/uix/navigationdrawer/navigationdrawer.kv
index dbb484aaa..702fce195 100644
--- a/kivymd/uix/navigationdrawer/navigationdrawer.kv
+++ b/kivymd/uix/navigationdrawer/navigationdrawer.kv
@@ -1,134 +1,83 @@
#:import Window kivy.core.window.Window
-#:import m_res kivymd.material_resources
-:
+
+ type: "filled"
size_hint_x: None
- width: Window.width - dp(56) if Window.width <= dp(376) else dp(320)
- md_bg_color: self.theme_cls.bg_light
- padding:
+ width: Window.width - dp(56) if Window.width <= dp(360) else dp(320)
+ theme_bg_color: "Custom"
+ shadow_radius: self.radius
+ md_bg_color:
+ self.theme_cls.surfaceContainerLowColor \
+ if not self.background_color else \
+ self.background_color
x:
(self.width * (self.open_progress - 1)) \
if self.anchor == "left" \
else (Window.width - self.width * self.open_progress)
- canvas:
- Clear
- Color:
- rgba: self.md_bg_color
- RoundedRectangle:
- size: self.size
- pos: self.pos
- source: root.background
- radius: root.radius
-
adaptive_height: True
+ padding: "20dp", "0dp", "16dp", "16dp"
+ text_color:
+ self.theme_cls.onSurfaceColor \
+ if self.theme_text_color == "Primary" else \
+ self.text_color
- MDLabel:
- text: root.text
- adaptive_size: True
- markup: True
+
+ radius: self.height / 2
+ ripple_color: self.theme_cls.onSecondaryContainerColor[:-1] + [0.12]
+ theme_bg_color: "Custom"
+ md_bg_color:
+ self.theme_cls.surfaceContainerLowColor \
+ if not self.inactive_indicator_color else \
+ self.inactive_indicator_color
+
+
+
+ text_color:
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_text_color == "Primary" else \
+ self.text_color
-
- adaptive_height: True
- MDSeparator:
- color: root.color if root.color else app.theme_cls.divider_color
+
+ font_style: "Label"
+ role: "large"
+ text_color:
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_text_color == "Primary" else \
+ self.text_color
+
+
+
+ icon_color:
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_icon_color == "Primary" else \
+ self.icon_color
- adaptive_height: True
+ size_hint_y: None
+ height: self.minimum_height
- FitImage:
- id: logo
- source: root.source
- size_hint: None, None
- size: label_box.height, label_box.height
-
- MDBoxLayout:
- id: label_box
- orientation: "vertical"
- adaptive_height: True
-
- MDLabel:
- id: title
- adaptive_height: True
- halign: root.title_halign
- text: root.title
- font_style: root.title_font_style
- font_size: root.title_font_size
- color:
- root.title_color \
- if root.title_color else \
- app.theme_cls.text_color
-
- MDLabel:
- id: text
- adaptive_height: True
- text: root.text
- halign: root.text_halign
- font_style: root.text_font_style
- font_size: root.text_font_size
- color:
- root.text_color \
- if root.text_color else \
- app.theme_cls.text_color
+
+ padding: 0, "4dp", 0, "4dp"
+ size_hint_y: None
+ height: self.minimum_height
-
- radius: self.height / 2 if self.radius == [0, 0, 0, 0] else self.radius
- divider: None
- theme_text_color: "Custom"
- text_color: self.text_color if not self.selected else self.selected_color
- _txt_left_pad: "56dp"
- on_size:
- self.ids._left_container.x = "4dp"
- self.ids._right_container.width = right_label.texture_size[0]
- on_release:
- if not self.selected: self._text_color = self.text_color
- self._text_right_color = root.text_right_color if root.text_right_color else app.theme_cls.text_color
- self._drawer_menu.reset_active_color(self)
-
- IconLeftWidgetWithoutTouch:
- icon: root.icon
- theme_icon_color: "Custom"
- icon_color:
- ( \
- app.theme_cls.text_color \
- if not root.icon_color else \
- root.icon_color \
- ) \
- if not root.selected else \
- root.selected_color
-
- MDLabel:
- id: right_label
- text: root.right_text
- pos_hint: {"center_y": .5}
- adaptive_size: True
- markup: True
- color:
- ( \
- root.text_right_color \
- if root.text_right_color else \
- app.theme_cls.text_color \
- ) \
- if not root.selected else \
- root.selected_color
- x:
- root.x \
- + root.width \
- - m_res.HORIZ_MARGINS \
- - root.ids._right_container.width - dp(24) \
- - self.texture_size[0] \
- + dp(24)
+ MDDivider:
- MDList:
+ GridLayout:
id: menu
+ cols: 1
+ size_hint_y: None
+ height: self.minimum_height
spacing: root.spacing
+
diff --git a/kivymd/uix/navigationdrawer/navigationdrawer.py b/kivymd/uix/navigationdrawer/navigationdrawer.py
index ac2eac309..2a726f83b 100755
--- a/kivymd/uix/navigationdrawer/navigationdrawer.py
+++ b/kivymd/uix/navigationdrawer/navigationdrawer.py
@@ -4,19 +4,22 @@
.. seealso::
- `Material Design 2 spec, Navigation drawer `_ and
- `Material Design 3 spec, Navigation drawer `_
+ `Material Design, Navigation drawer `_
.. rubric:: Navigation drawers provide access to destinations in your app.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.png
:align: center
+- Use navigation drawers in expanded layouts and modal navigation drawers in compact and medium layouts
+- Can be open or closed by default
+- Two types: standard and modal
+
When using the class :class:`~MDNavigationDrawer` skeleton of your `KV` markup
should look like this:
-Anatomy
--------
+Usage
+-----
.. code-block:: kv
@@ -32,8 +35,8 @@
MDNavigationDrawer:
- # This custom rule should implement what will be appear in your
- # MDNavigationDrawer.
+ # This custom rule should implement what will be displayed in
+ # your MDNavigationDrawer.
ContentNavigationDrawer:
A simple example
@@ -47,11 +50,11 @@
from kivy.lang import Builder
- from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
KV = '''
MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
MDNavigationLayout:
@@ -59,31 +62,39 @@
MDScreen:
- MDTopAppBar:
- title: "Navigation Drawer"
- elevation: 4
- pos_hint: {"top": 1}
- md_bg_color: "#e7e4c0"
- specific_text_color: "#4a4939"
- left_action_items:
- [['menu', lambda x: nav_drawer.set_state("open")]]
+ MDButton:
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: nav_drawer.set_state("toggle")
+ MDButtonText:
+ text: "Open Drawer"
MDNavigationDrawer:
id: nav_drawer
- radius: (0, 16, 16, 0)
+ radius: 0, dp(16), dp(16), 0
- ContentNavigationDrawer:
- '''
+ MDNavigationDrawerMenu:
+ MDNavigationDrawerLabel:
+ text: "Mail"
- class ContentNavigationDrawer(MDBoxLayout):
- pass
+ MDNavigationDrawerItem:
+
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "account"
+
+ MDNavigationDrawerItemText:
+ text: "Inbox"
+
+ MDNavigationDrawerItemTrailingText:
+ text: "24"
+
+ MDNavigationDrawerDivider:
+ '''
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
@@ -93,454 +104,416 @@ def build(self):
.. code-block:: python
- from kivymd.app import MDApp
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.navigationdrawer import MDNavigationLayout, MDNavigationDrawer
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.screenmanager import MDScreenManager
- from kivymd.uix.toolbar import MDTopAppBar
+ from kivy.metrics import dp
-
- class ContentNavigationDrawer(MDBoxLayout):
- pass
+ from kivymd.uix.button import MDButton, MDButtonText
+ from kivymd.uix.screenmanager import MDScreenManager
+ from kivymd.uix.navigationdrawer import (
+ MDNavigationLayout,
+ MDNavigationDrawer,
+ MDNavigationDrawerMenu,
+ MDNavigationDrawerLabel,
+ MDNavigationDrawerItem,
+ MDNavigationDrawerItemLeadingIcon,
+ MDNavigationDrawerItemText,
+ MDNavigationDrawerItemTrailingText,
+ MDNavigationDrawerDivider,
+ )
+ from kivymd.uix.screen import MDScreen
+ from kivymd.app import MDApp
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
- return(
- MDScreen(
- MDNavigationLayout(
- MDScreenManager(
- MDScreen(
- MDTopAppBar(
- title="Navigation Drawer",
- elevation=4,
- pos_hint={"top": 1},
- md_bg_color="#e7e4c0",
- specific_text_color="#4a4939",
- left_action_items=[
- ['menu', lambda x: self.nav_drawer_open()]
- ],
- )
-
- )
+ return MDScreen(
+ MDNavigationLayout(
+ MDScreenManager(
+ MDScreen(
+ MDButton(
+ MDButtonText(
+ text="Open Drawer",
+ ),
+ on_release=lambda x: self.root.get_ids().nav_drawer.set_state(
+ "toggle"
+ ),
+ pos_hint={"center_x": 0.5, "center_y": 0.5},
+ ),
),
- MDNavigationDrawer(
- ContentNavigationDrawer(),
- id="nav_drawer",
- radius=(0, 16, 16, 0),
+ ),
+ MDNavigationDrawer(
+ MDNavigationDrawerMenu(
+ MDNavigationDrawerLabel(
+ text="Mail",
+ ),
+ MDNavigationDrawerItem(
+ MDNavigationDrawerItemLeadingIcon(
+ icon="account",
+ ),
+ MDNavigationDrawerItemText(
+ text="Inbox",
+ ),
+ MDNavigationDrawerItemTrailingText(
+ text="24",
+ ),
+ ),
+ MDNavigationDrawerDivider(
+ ),
),
+ id="nav_drawer",
+ radius=(0, dp(16), dp(16), 0),
),
),
+ md_bg_color=self.theme_cls.backgroundColor,
)
- def nav_drawer_open(self, *args):
- nav_drawer = self.root.children[0].ids.nav_drawer
- nav_drawer.set_state("open")
-
Example().run()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.gif
+Anatomy
+-------
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-anatomy.png
:align: center
.. Note:: :class:`~MDNavigationDrawer` is an empty
:class:`~kivymd.uix.card.MDCard` panel.
-Standard content for the navigation bar
----------------------------------------
-
-.. tabs::
-
- .. tab:: Declarative KV styles
-
- .. code-block:: python
+Item anatomy
+------------
- from kivy.lang import Builder
+.. code-block:: kv
- from kivymd.app import MDApp
+ MDNavigationDrawerItem:
- KV = '''
-
- focus_color: "#e7e4c0"
- text_color: "#4a4939"
- icon_color: "#4a4939"
- ripple_color: "#c5bdd2"
- selected_color: "#0c6c4d"
+ MDNavigationDrawerItemLeadingIcon:
+ icon: "account"
+ MDNavigationDrawerItemText:
+ text: "Inbox"
-
- text_color: "#4a4939"
- icon_color: "#4a4939"
- focus_behavior: False
- selected_color: "#4a4939"
- _no_ripple_effect: True
+ MDNavigationDrawerItemTrailingText:
+ text: "24"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-item-anatomy.png
+ :align: center
- MDScreen:
+Type drawer
+===========
- MDNavigationLayout:
+Standard
+--------
- MDScreenManager:
+.. code-block:: kv
- MDScreen:
+ MDNavigationDrawer:
+ drawer_type: "standard"
- MDTopAppBar:
- title: "Navigation Drawer"
- elevation: 4
- pos_hint: {"top": 1}
- md_bg_color: "#e7e4c0"
- specific_text_color: "#4a4939"
- left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-type-standard.gif
+ :align: center
- MDNavigationDrawer:
- id: nav_drawer
- radius: (0, 16, 16, 0)
+Modal
+-----
- MDNavigationDrawerMenu:
+.. code-block:: kv
- MDNavigationDrawerHeader:
- title: "Header title"
- title_color: "#4a4939"
- text: "Header text"
- spacing: "4dp"
- padding: "12dp", 0, 0, "56dp"
+ MDNavigationDrawer:
+ drawer_type: "modal"
- MDNavigationDrawerLabel:
- text: "Mail"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-type-modal.gif
+ :align: center
- DrawerClickableItem:
- icon: "gmail"
- right_text: "+99"
- text_right_color: "#4a4939"
- text: "Inbox"
+Anchoring screen edge for drawer
+================================
- DrawerClickableItem:
- icon: "send"
- text: "Outbox"
+ Left
+ ----
- MDNavigationDrawerDivider:
+ .. code-block:: kv
- MDNavigationDrawerLabel:
- text: "Labels"
+ MDNavigationDrawer:
+ anchor: "left"
- DrawerLabelItem:
- icon: "information-outline"
- text: "Label"
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-ancjor-left.png
+ :align: center
- DrawerLabelItem:
- icon: "information-outline"
- text: "Label"
- '''
+ Right
+ -----
+ .. code-block:: kv
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
+ MDNavigationDrawer:
+ anchor: "right"
+ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-ancjor-right.png
+ :align: center
- Example().run()
+API break
+=========
- .. tab:: Declarative python styles
+1.2.0 version
+-------------
- .. code-block:: python
+.. code-block:: python
- from kivymd.app import MDApp
- from kivymd.uix.navigationdrawer import (
- MDNavigationLayout,
- MDNavigationDrawer,
- MDNavigationDrawerMenu,
- MDNavigationDrawerHeader,
- MDNavigationDrawerLabel,
- MDNavigationDrawerDivider,
- MDNavigationDrawerItem,
- )
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.screenmanager import MDScreenManager
- from kivymd.uix.toolbar import MDTopAppBar
+ from kivy.lang import Builder
+ from kivymd.app import MDApp
- class BaseNavigationDrawerItem(MDNavigationDrawerItem):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.radius = 24
- self.text_color = "#4a4939"
- self.icon_color = "#4a4939"
- self.focus_color = "#e7e4c0"
+ KV = '''
+
+ focus_color: "#e7e4c0"
+ text_color: "#4a4939"
+ icon_color: "#4a4939"
+ ripple_color: "#c5bdd2"
+ selected_color: "#0c6c4d"
- class DrawerLabelItem(BaseNavigationDrawerItem):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.focus_behavior = False
- self._no_ripple_effect = True
- self.selected_color = "#4a4939"
+
+ text_color: "#4a4939"
+ icon_color: "#4a4939"
+ focus_behavior: False
+ selected_color: "#4a4939"
+ _no_ripple_effect: True
- class DrawerClickableItem(BaseNavigationDrawerItem):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.ripple_color = "#c5bdd2"
- self.selected_color = "#0c6c4d"
+ MDScreen:
+ MDNavigationLayout:
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return(
- MDScreen(
- MDNavigationLayout(
- MDScreenManager(
- MDScreen(
- MDTopAppBar(
- title="Navigation Drawer",
- elevation=4,
- 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",
- ),
- DrawerClickableItem(
- icon="send",
- text="Outbox",
- ),
- MDNavigationDrawerDivider(),
- MDNavigationDrawerLabel(
- text="Labels",
- ),
- DrawerLabelItem(
- icon="information-outline",
- text="Label",
- ),
- DrawerLabelItem(
- icon="information-outline",
- text="Label",
- ),
- ),
- id="nav_drawer",
- radius=(0, 16, 16, 0),
- )
- )
- )
- )
+ MDScreenManager:
- def nav_drawer_open(self, *args):
- nav_drawer = self.root.children[0].ids.nav_drawer
- nav_drawer.set_state("open")
+ MDScreen:
+ MDRaisedButton:
+ text: "Open Drawer"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: nav_drawer.set_state("toggle")
- Example().run()
+ MDNavigationDrawer:
+ id: nav_drawer
+ radius: (0, dp(16), dp(16), 0)
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standatd-content.gif
- :align: center
+ MDNavigationDrawerMenu:
-Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar``
------------------------------------------------------------------------------
+ MDNavigationDrawerHeader:
+ title: "Header title"
+ title_color: "#4a4939"
+ text: "Header text"
+ spacing: "4dp"
+ padding: "12dp", 0, 0, "56dp"
-.. tabs::
+ MDNavigationDrawerLabel:
+ text: "Mail"
- .. tab:: Declarative KV styles
+ DrawerClickableItem:
+ icon: "gmail"
+ right_text: "+99"
+ text_right_color: "#4a4939"
+ text: "Inbox"
- .. code-block:: python
+ DrawerClickableItem:
+ icon: "send"
+ text: "Outbox"
- from kivy.lang import Builder
- from kivy.properties import ObjectProperty
+ MDNavigationDrawerDivider:
- from kivymd.app import MDApp
- from kivymd.uix.scrollview import MDScrollView
+ MDNavigationDrawerLabel:
+ text: "Labels"
- KV = '''
-
+ DrawerLabelItem:
+ icon: "information-outline"
+ text: "Label"
- MDList:
+ DrawerLabelItem:
+ icon: "information-outline"
+ text: "Label"
+ '''
- OneLineListItem:
- text: "Screen 1"
- on_press:
- root.nav_drawer.set_state("close")
- root.screen_manager.current = "scr 1"
- OneLineListItem:
- text: "Screen 2"
- on_press:
- root.nav_drawer.set_state("close")
- root.screen_manager.current = "scr 2"
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- MDScreen:
+ Example().run()
- MDTopAppBar:
- pos_hint: {"top": 1}
- elevation: 4
- title: "MDNavigationDrawer"
- left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
+2.2.0 version
+-------------
- MDNavigationLayout:
+.. code-block:: python
- MDScreenManager:
- id: screen_manager
+ from kivy.lang import Builder
+ from kivy.properties import StringProperty, ColorProperty
- MDScreen:
- name: "scr 1"
+ from kivymd.app import MDApp
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.navigationdrawer import (
+ MDNavigationDrawerItem, MDNavigationDrawerItemTrailingText
+ )
- MDLabel:
- text: "Screen 1"
- halign: "center"
+ KV = '''
+
+ active_indicator_color: "#e7e4c0"
- MDScreen:
- name: "scr 2"
+ MDNavigationDrawerItemLeadingIcon:
+ icon: root.icon
+ theme_icon_color: "Custom"
+ icon_color: "#4a4939"
- MDLabel:
- text: "Screen 2"
- halign: "center"
+ MDNavigationDrawerItemText:
+ text: root.text
+ theme_text_color: "Custom"
+ text_color: "#4a4939"
- MDNavigationDrawer:
- id: nav_drawer
- radius: (0, 16, 16, 0)
- ContentNavigationDrawer:
- screen_manager: screen_manager
- nav_drawer: nav_drawer
- '''
+
+ adaptive_height: True
+ padding: "18dp", 0, 0, "12dp"
+ MDNavigationDrawerItemLeadingIcon:
+ icon: root.icon
+ theme_icon_color: "Custom"
+ icon_color: "#4a4939"
+ pos_hint: {"center_y": .5}
- class ContentNavigationDrawer(MDScrollView):
- screen_manager = ObjectProperty()
- nav_drawer = ObjectProperty()
+ MDNavigationDrawerLabel:
+ text: root.text
+ theme_text_color: "Custom"
+ text_color: "#4a4939"
+ pos_hint: {"center_y": .5}
+ padding: "6dp", 0, "16dp", 0
+ theme_line_height: "Custom"
+ line_height: 0
- class Example(MDApp):
- def build(self):
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
+ MDNavigationLayout:
- Example().run()
+ MDScreenManager:
- .. tab:: Declarative python styles
+ MDScreen:
- .. code-block:: python
+ MDButton:
+ pos_hint: {"center_x": .5, "center_y": .5}
+ on_release: nav_drawer.set_state("toggle")
- from kivymd.app import MDApp
- from kivymd.uix.label import MDLabel
- from kivymd.uix.list import MDList, OneLineListItem
- from kivymd.uix.navigationdrawer import MDNavigationLayout, MDNavigationDrawer
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.screenmanager import MDScreenManager
- from kivymd.uix.scrollview import MDScrollView
- from kivymd.uix.toolbar import MDTopAppBar
+ MDButtonText:
+ text: "Open Drawer"
+ MDNavigationDrawer:
+ id: nav_drawer
+ radius: 0, dp(16), dp(16), 0
+
+ MDNavigationDrawerMenu:
+
+ MDNavigationDrawerHeader:
+ orientation: "vertical"
+ padding: 0, 0, 0, "12dp"
+ adaptive_height: True
+
+ MDLabel:
+ text: "Header title"
+ theme_text_color: "Custom"
+ theme_line_height: "Custom"
+ line_height: 0
+ text_color: "#4a4939"
+ adaptive_height: True
+ padding_x: "16dp"
+ font_style: "Display"
+ role: "small"
+
+ MDLabel:
+ text: "Header text"
+ padding_x: "18dp"
+ adaptive_height: True
+ font_style: "Title"
+ role: "large"
+
+ MDNavigationDrawerDivider:
+
+ DrawerItem:
+ icon: "gmail"
+ text: "Inbox"
+ trailing_text: "+99"
+ trailing_text_color: "#4a4939"
+
+ DrawerItem:
+ icon: "send"
+ text: "Outbox"
+
+ MDNavigationDrawerDivider:
+
+ MDNavigationDrawerLabel:
+ text: "Labels"
+ padding_y: "12dp"
+
+ DrawerLabel:
+ icon: "information-outline"
+ text: "Label"
+
+ DrawerLabel:
+ icon: "information-outline"
+ text: "Label"
+ '''
+
+
+ class DrawerLabel(MDBoxLayout):
+ icon = StringProperty()
+ text = StringProperty()
+
+
+ class DrawerItem(MDNavigationDrawerItem):
+ icon = StringProperty()
+ text = StringProperty()
+ trailing_text = StringProperty()
+ trailing_text_color = ColorProperty()
+
+ _trailing_text_obj = None
+
+ def on_trailing_text(self, instance, value):
+ self._trailing_text_obj = MDNavigationDrawerItemTrailingText(
+ text=value,
+ theme_text_color="Custom",
+ text_color=self.trailing_text_color,
+ )
+ self.add_widget(self._trailing_text_obj)
- class Example(MDApp):
- def build(self):
- self.theme_cls.primary_palette = "Orange"
- self.theme_cls.theme_style = "Dark"
- return (
- MDScreen(
- MDTopAppBar(
- pos_hint={"top": 1},
- elevation=4,
- title="MDNavigationDrawer",
- left_action_items=[["menu", lambda x: self.nav_drawer_open()]],
- ),
- MDNavigationLayout(
- MDScreenManager(
- MDScreen(
- MDLabel(
- text="Screen 1",
- halign="center",
- ),
- name="scr 1",
- ),
- MDScreen(
- MDLabel(
- text="Screen 2",
- halign="center",
- ),
- name="scr 2",
- ),
- id="screen_manager",
- ),
- MDNavigationDrawer(
- MDScrollView(
- MDList(
- OneLineListItem(
- text="Screen 1",
- on_press=self.switch_screen,
- ),
- OneLineListItem(
- text="Screen 2",
- on_press=self.switch_screen,
- ),
- ),
- ),
- id="nav_drawer",
- radius=(0, 16, 16, 0),
- ),
- id="navigation_layout",
- )
- )
- )
+ def on_trailing_text_color(self, instance, value):
+ self._trailing_text_obj.text_color = value
- def switch_screen(self, instance_list_item: OneLineListItem):
- self.root.ids.navigation_layout.ids.screen_manager.current = {
- "Screen 1": "scr 1", "Screen 2": "scr 2"
- }[instance_list_item.text]
- self.root.children[0].ids.nav_drawer.set_state("close")
- def nav_drawer_open(self):
- nav_drawer = self.root.children[0].ids.nav_drawer
- nav_drawer.set_state("open")
+ class Example(MDApp):
+ def build(self):
+ return Builder.load_string(KV)
- Example().run()
+ Example().run()
"""
__all__ = (
"MDNavigationLayout",
"MDNavigationDrawer",
"MDNavigationDrawerItem",
+ "MDNavigationDrawerItemLeadingIcon",
+ "MDNavigationDrawerItemTrailingText",
+ "MDNavigationDrawerItemText",
"MDNavigationDrawerMenu",
"MDNavigationDrawerHeader",
"MDNavigationDrawerLabel",
"MDNavigationDrawerDivider",
+ "BaseNavigationDrawerItem",
)
import os
-from typing import Union
from kivy.animation import Animation, AnimationTransition
-from kivy.clock import Clock
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
+from kivy.metrics import dp
from kivy.properties import (
AliasProperty,
BooleanProperty,
@@ -551,16 +524,24 @@ def nav_drawer_open(self):
StringProperty,
VariableListProperty,
)
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.floatlayout import FloatLayout
+from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager
+from kivymd.uix.appbar import MDTopAppBar
+from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.label import MDLabel
from kivymd import uix_path
from kivymd.uix.behaviors.focus_behavior import FocusBehavior
-from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.card import MDCard
-from kivymd.uix.floatlayout import MDFloatLayout
-from kivymd.uix.list import MDList, OneLineAvatarIconListItem
+from kivymd.uix.list import (
+ MDListItem,
+ MDListItemLeadingIcon,
+ MDListItemSupportingText,
+ MDListItemTrailingSupportingText,
+)
from kivymd.uix.scrollview import MDScrollView
-from kivymd.uix.toolbar import MDTopAppBar
with open(
os.path.join(uix_path, "navigationdrawer", "navigationdrawer.kv"),
@@ -573,10 +554,33 @@ class NavigationDrawerContentError(Exception):
pass
-class MDNavigationLayout(MDFloatLayout):
+class BaseNavigationDrawerItem:
+ """
+ Implement the base class for the menu list item.
+
+ .. versionadded:: 2.0.0
+ """
+
+ selected = BooleanProperty(False)
+ """
+ Is the item selected.
+
+ :attr:`selected` is a :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
+ """
+
+ # kivymd.uix.navigationdrawer.MDNavigationDrawerMenu object.
+ _drawer_menu = ObjectProperty()
+ # kivymd.uix.navigationdrawer.MDNavigationDrawerItem object.
+ _drawer_item = ObjectProperty()
+
+
+class MDNavigationLayout(DeclarativeBehavior, FloatLayout):
"""
For more information, see in the
- :class:`~kivymd.uix.floatlayout.MDFloatLayout` class documentation.
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.floatlayout.FloatLayout`
+ classes documentation.
"""
_scrim_color = ObjectProperty(None)
@@ -593,7 +597,7 @@ def update_pos(self, instance_navigation_drawer, pos_x: float) -> None:
manager = self._screen_manager
if not drawer or not manager:
return
- if drawer.type == "standard":
+ if drawer.drawer_type == "standard":
manager.size_hint_x = None
if drawer.anchor == "left":
manager.x = drawer.width + drawer.x
@@ -601,7 +605,7 @@ def update_pos(self, instance_navigation_drawer, pos_x: float) -> None:
else:
manager.x = 0
manager.width = drawer.x
- elif drawer.type == "modal":
+ elif drawer.drawer_type == "modal":
manager.size_hint_x = None
manager.x = 0
if drawer.anchor == "left":
@@ -634,7 +638,8 @@ def add_widget(self, widget, index=0, canvas=None):
"""
if not isinstance(
- widget, (MDNavigationDrawer, ScreenManager, MDTopAppBar)
+ widget,
+ (MDNavigationDrawer, ScreenManager, MDTopAppBar),
):
raise NavigationDrawerContentError(
"The MDNavigationLayout must contain "
@@ -656,324 +661,142 @@ def add_widget(self, widget, index=0, canvas=None):
return super().add_widget(widget)
-class MDNavigationDrawerLabel(MDBoxLayout):
+class MDNavigationDrawerLabel(MDLabel):
"""
- Implements a label for a menu for :class:`~MDNavigationDrawer` class.
+ Implements a label class.
- For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout`
+ For more information, see in the :class:`~kivymd.uix.label.label.MDLabel`
class documentation.
.. versionadded:: 1.0.0
-
- .. code-block:: kv
-
- MDNavigationDrawer:
-
- MDNavigationDrawerMenu:
-
- MDNavigationDrawerLabel:
- text: "Mail"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-label.png
- :align: center
- """
-
- text = StringProperty()
- """
- Text label.
-
- :attr:`text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- padding = VariableListProperty(["20dp", 0, 0, "8dp"])
- """
- Padding between layout box and children: [padding_left, padding_top,
- padding_right, padding_bottom].
-
- Padding also accepts a two argument form [padding_horizontal,
- padding_vertical] and a one argument form [padding].
-
- :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
- and defaults to `['20dp', 0, 0, '8dp']`.
"""
-class MDNavigationDrawerDivider(MDBoxLayout):
+class MDNavigationDrawerDivider(BoxLayout):
"""
- Implements a divider for a menu for :class:`~MDNavigationDrawer` class.
+ Implements a divider class.
- For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout`
- class documentation.
+ For more information, see in the
+ :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
.. versionadded:: 1.0.0
-
- .. code-block:: kv
-
- MDNavigationDrawer:
-
- MDNavigationDrawerMenu:
-
- MDNavigationDrawerLabel:
- text: "Mail"
-
- MDNavigationDrawerDivider:
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-divider.png
- :align: center
- """
-
- padding = VariableListProperty(["20dp", "12dp", 0, "12dp"])
- """
- Padding between layout box and children: [padding_left, padding_top,
- padding_right, padding_bottom].
-
- Padding also accepts a two argument form [padding_horizontal,
- padding_vertical] and a one argument form [padding].
-
- :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
- and defaults to `['20dp', '12dp', 0, '12dp']`.
"""
- color = ColorProperty(None)
- """
- Divider color in (r, g, b, a) or string format.
- :attr:`color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+class MDNavigationDrawerHeader(DeclarativeBehavior, BoxLayout):
"""
+ Implements a header class.
-
-class MDNavigationDrawerHeader(MDBoxLayout):
- """
- Implements a header for a menu for :class:`~MDNavigationDrawer` class.
-
- For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout`
- class documentation.
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout`
+ classes documentation.
.. versionadded:: 1.0.0
-
- .. code-block:: kv
-
- MDNavigationDrawer:
-
- MDNavigationDrawerMenu:
-
- MDNavigationDrawerHeader:
- title: "Header title"
- text: "Header text"
- spacing: "4dp"
- padding: "12dp", 0, 0, "56dp"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-header.png
- :align: center
- """
-
- source = StringProperty()
"""
- Image logo path.
-
- .. code-block:: kv
- MDNavigationDrawer:
-
- MDNavigationDrawerMenu:
-
- MDNavigationDrawerHeader:
- title: "Header title"
- text: "Header text"
- source: "logo.png"
- spacing: "4dp"
- padding: "12dp", 0, 0, "56dp"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-header-source.png
- :align: center
- :attr:`source` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+class MDNavigationDrawerItem(
+ MDListItem, FocusBehavior, BaseNavigationDrawerItem
+):
"""
+ Implements an item for the :class:`~MDNavigationDrawer` menu list.
- title = StringProperty()
- """
- Title shown in the first line.
-
- :attr:`title` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
+ For more information, see in the
+ :class:`~kivymd.uix.list.list.MDListItem` and
+ :class:`~kivymd.uix.behaviors.focus_behavior.FocusBehavior` and
+ :class:`~BaseNavigationDrawerItem`
+ classes documentation.
- title_halign = StringProperty("left")
+ .. versionadded:: 1.0.0
"""
- Title halign first line.
- :attr:`title_halign` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'left'`.
+ active_indicator_color = ColorProperty(None)
"""
+ The active indicator color in (r, g, b, a) or string format.
- title_color = ColorProperty(None)
- """
- Title text color in (r, g, b, a) or string format.
+ .. versionadded:: 2.0.0
- :attr:`title_color` is a :class:`~kivy.properties.ColorProperty`
+ :attr:`active_indicator_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- title_font_style = StringProperty("H4")
- """
- Title shown in the first line.
-
- :attr:`title_font_style` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'H4'`.
- """
-
- title_font_size = StringProperty("34sp")
+ inactive_indicator_color = ColorProperty(None)
"""
- Title shown in the first line.
+ The inactive indicator color in (r, g, b, a) or string format.
- :attr:`title_font_size` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'34sp'`.
- """
-
- text = StringProperty()
- """
- Text shown in the second line.
-
- :attr:`text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- text_halign = StringProperty("left")
- """
- Text halign first line.
-
- :attr:`text_halign` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'left'`.
- """
-
- text_color = ColorProperty(None)
- """
- Title text color in (r, g, b, a) or string format.
+ .. versionadded:: 2.0.0
- :attr:`text_color` is a :class:`~kivy.properties.ColorProperty`
+ :attr:`inactive_indicator_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- text_font_style = StringProperty("H6")
- """
- Title shown in the first line.
-
- :attr:`text_font_style` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'H6'`.
- """
-
- text_font_size = StringProperty("20sp")
- """
- Title shown in the first line.
-
- :attr:`text_font_size` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'20sp'`.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Clock.schedule_once(self.check_content)
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(
+ widget,
+ (
+ MDNavigationDrawerItemLeadingIcon,
+ MDNavigationDrawerItemText,
+ MDNavigationDrawerItemTrailingText,
+ ),
+ ):
+ widget._drawer_item = self
+ return super().add_widget(widget)
- def check_content(self, interval: Union[int, float]) -> None:
- """Removes widgets that the user has not added to the container."""
+ def on_release(self, *args) -> None:
+ """
+ Fired when the item is released
+ (i.e. the touch/click that pressed the item goes away).
+ """
- if not self.title:
- self.ids.label_box.remove_widget(self.ids.title)
- if not self.text:
- self.ids.label_box.remove_widget(self.ids.text)
- if not self.source:
- self.remove_widget(self.ids.logo)
+ self.selected = not self.selected
+ self._drawer_menu.update_items_color(self)
-class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior):
+class MDNavigationDrawerItemLeadingIcon(
+ MDListItemLeadingIcon, BaseNavigationDrawerItem
+):
"""
- Implements an item for the :class:`~MDNavigationDrawer` menu list.
+ Implements the leading icon for the menu list item.
For more information, see in the
- :class:`~kivymd.uix.list.OneLineAvatarIconListItem` and
- :class:`~kivymd.uix.behaviors.FocusBehavior`
- class documentation.
-
- .. versionadded:: 1.0.0
-
- .. code-block:: kv
-
- MDNavigationDrawer:
-
- MDNavigationDrawerMenu:
-
- MDNavigationDrawerHeader:
- title: "Header title"
- text: "Header text"
- spacing: "4dp"
- padding: "12dp", 0, 0, "56dp"
+ :class:`~kivymd.uix.list.list.MDListItemLeadingIcon` and
+ :class:`~BaseNavigationDrawerItem`
+ classes documentation.
- MDNavigationDrawerItem
- icon: "gmail"
- right_text: "+99"
- text: "Inbox"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-item.png
- :align: center
+ .. versionadded:: 2.0.0
"""
- selected = BooleanProperty(False)
- """
- Is the item selected.
-
- :attr:`selected` is a :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- icon = StringProperty()
- """
- Icon item.
-
- :attr:`icon` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
- icon_color = ColorProperty(None)
+class MDNavigationDrawerItemText(
+ MDListItemSupportingText, BaseNavigationDrawerItem
+):
"""
- Icon color in (r, g, b, a) or string format item.
+ Implements the text for the menu list item.
- :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- selected_color = ColorProperty([0, 0, 0, 1])
- """
- The color in (r, g, b, a) or string format of the icon and text of the
- selected item.
+ For more information, see in the
+ :class:`~kivymd.uix.list.list.MDListItemSupportingText` and
+ :class:`~BaseNavigationDrawerItem`
+ classes documentation.
- :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 1]`.
+ .. versionadded:: 2.0.0
"""
- right_text = StringProperty()
- """
- Right text item.
- :attr:`right_text` is a :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+class MDNavigationDrawerItemTrailingText(
+ MDListItemTrailingSupportingText, BaseNavigationDrawerItem
+):
"""
+ Implements the supporting text for the menu list item.
- text_right_color = ColorProperty(None)
- """
- Right text color item in (r, g, b, a) or string format.
+ For more information, see in the
+ :class:`~kivymd.uix.list.list.MDListItemTrailingSupportingText` and
+ :class:`~BaseNavigationDrawerItem`
+ classes documentation.
- :attr:`text_right_color` is a :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ .. versionadded:: 2.0.0
"""
- _text_color = None
- _text_right_color = None
- # kivymd.uix.navigationdrawer.navigationdrawer.MDNavigationDrawerMenu
- _drawer_menu = ObjectProperty()
-
class MDNavigationDrawerMenu(MDScrollView):
"""
@@ -1004,63 +827,59 @@ class MDNavigationDrawerMenu(MDScrollView):
"""
def add_widget(self, widget, *args, **kwargs):
- if isinstance(widget, MDList):
+ if isinstance(widget, GridLayout):
return super().add_widget(widget, *args, **kwargs)
else:
if isinstance(widget, MDNavigationDrawerItem):
widget._drawer_menu = self
self.ids.menu.add_widget(widget)
- def reset_active_color(self, item: MDNavigationDrawerItem) -> None:
+ def update_items_color(self, item: MDNavigationDrawerItem) -> None:
for widget in self.ids.menu.children:
if issubclass(widget.__class__, MDNavigationDrawerItem):
- if widget != item:
- widget.selected = False
+ if widget is not item:
+ widget.md_bg_color = (
+ widget.theme_cls.surfaceContainerLowColor
+ if not widget.inactive_indicator_color
+ else widget.inactive_indicator_color
+ )
else:
- widget.selected = True
-
- if (
- issubclass(widget.__class__, MDNavigationDrawerItem)
- and widget != item
- ):
- if widget._text_color:
- widget.text_color = widget._text_color
+ widget.md_bg_color = (
+ widget.theme_cls.secondaryContainerColor
+ if not widget.active_indicator_color
+ else widget.active_indicator_color
+ )
class MDNavigationDrawer(MDCard):
- type = OptionProperty("modal", options=("standard", "modal"))
"""
- Type of drawer. Modal type will be on top of screen. Standard type will be
- at left or right of screen. Also it automatically disables
- :attr:`close_on_click` and :attr:`enable_swiping` to prevent closing
- drawer for standard type.
+ Navigation drawer class.
- For more information, see in the :class:`~kivymd.uix.card.MDCard`
+ For more information, see in the :class:`~kivymd.uix.card.card.MDCard`
class documentation.
- Standard
- --------
-
- .. code-block:: kv
-
- MDNavigationDrawer:
- type: "standard"
+ :Events:
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standard.gif
- :align: center
+ .. versionadded:: 2.0.0
- Modal
- -----
+ `on_open`:
+ Fired when the navigation drawer is opened.
+ `on_close`:
+ Fired when the navigation drawer is closed.
+ """
- .. code-block:: kv
+ drawer_type = OptionProperty("modal", options=("standard", "modal"))
+ """
+ Type of drawer. Modal type will be on top of screen. Standard type will be
+ at left or right of screen. Also it automatically disables
+ :attr:`close_on_click` and :attr:`enable_swiping` to prevent closing
+ drawer for standard type.
- MDNavigationDrawer:
- type: "modal"
+ .. versionchanged:: 2.0.0
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-modal.gif
- :align: center
+ Rename from `type` to `drawer_type`.
- :attr:`type` is a :class:`~kivy.properties.OptionProperty`
+ :attr:`drawer_type` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'modal'`.
"""
@@ -1069,28 +888,6 @@ class documentation.
Anchoring screen edge for drawer. Set it to `'right'` for right-to-left
languages. Available options are: `'left'`, `'right'`.
- Left
- ----
-
- .. code-block:: kv
-
- MDNavigationDrawer:
- anchor: "left"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-type-left.png
- :align: center
-
- Right
- -----
-
- .. code-block:: kv
-
- MDNavigationDrawer:
- anchor: "right"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-type-right.png
- :align: center
-
:attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
and defaults to `'left'`.
"""
@@ -1102,20 +899,11 @@ class documentation.
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
-
- .. code-block:: kv
-
- MDNavigationDrawer:
- scrim_color: 0, 0, 0, .8
- # scrim_color: 0, 0, 0, .2
-
:attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `[0, 0, 0, 0.5]`.
"""
- padding = VariableListProperty([16, 16, 12, 16])
+ padding = VariableListProperty([dp(16), dp(16), dp(12), dp(16)])
"""
Padding between layout box and children: [padding_left, padding_top,
padding_right, padding_bottom].
@@ -1128,13 +916,13 @@ class documentation.
.. code-block:: kv
MDNavigationDrawer:
- padding: 56, 56, 12, 16
+ padding: "56dp"
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-padding.png
:align: center
:attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and
- defaults to '[16, 16, 12, 16]'.
+ defaults to '[dp(16), dp(16), dp(12), dp(16)]'.
"""
close_on_click = BooleanProperty(True)
@@ -1215,7 +1003,7 @@ class documentation.
def _get_scrim_alpha(self):
_scrim_alpha = 0
- if self.type == "modal":
+ if self.drawer_type == "modal":
_scrim_alpha = self._scrim_alpha_transition(self.open_progress)
if (
isinstance(self.parent, MDNavigationLayout)
@@ -1288,8 +1076,42 @@ def _get_scrim_alpha_transition(self):
and defaults to `0.2`.
"""
+ background_color = ColorProperty(None)
+ """
+ The drawer background color in (r, g, b, a) or string format.
+
+ .. versionadded:: 2.0.0
+
+ :attr:`background_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ theme_elevation_level = "Custom"
+ """
+ Drawer elevation level scheme name.
+
+ .. versionadded:: 2.0.0
+
+ Available options are: `'Primary'`, `'Custom'`.
+
+ :attr:`theme_elevation_level` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'Custom'`.
+ """
+
+ elevation_level = 1
+ """
+ Drawer elevation level (values from 0 to 5)
+
+ .. versionadded:: 2.2.0
+
+ :attr:`elevation_level` is an :class:`~kivy.properties.BoundedNumericProperty`
+ and defaults to `2`.
+ """
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ self.register_event_type("on_open")
+ self.register_event_type("on_close")
self.bind(
open_progress=self.update_status,
status=self.update_status,
@@ -1297,6 +1119,9 @@ def __init__(self, *args, **kwargs):
)
Window.bind(on_keyboard=self._handle_keyboard)
+ def set_properties_widget(self) -> None:
+ pass
+
def set_state(self, new_state="toggle", animation=True) -> None:
"""
Change state of the side panel.
@@ -1310,26 +1135,30 @@ def set_state(self, new_state="toggle", animation=True) -> None:
Animation.cancel_all(self, "open_progress")
self.status = "opening_with_animation"
if animation:
- Animation(
+ anim = Animation(
open_progress=1.0,
d=self.opening_time * (1 - self.open_progress),
t=self.opening_transition,
- ).start(self)
+ )
+ anim.bind(on_complete=self._check_state)
+ anim.start(self)
else:
self.open_progress = 1
else: # "close"
Animation.cancel_all(self, "open_progress")
self.status = "closing_with_animation"
if animation:
- Animation(
+ anim = Animation(
open_progress=0.0,
d=self.closing_time * self.open_progress,
t=self.closing_transition,
- ).start(self)
+ )
+ anim.bind(on_complete=self._check_state)
+ anim.start(self)
else:
self.open_progress = 0
- def update_status(self, *_) -> None:
+ def update_status(self, *args) -> None:
status = self.status
if status == "closed":
self.state = "close"
@@ -1365,7 +1194,7 @@ def on_touch_down(self, touch):
for child in self.children[:]:
if child.dispatch("on_touch_down", touch):
return True
- if self.type == "standard" and not self.collide_point(
+ if self.drawer_type == "standard" and not self.collide_point(
touch.ox, touch.oy
):
return False
@@ -1412,7 +1241,7 @@ def on_touch_up(self, touch):
touch.ox, touch.oy
):
self.set_state("close", animation=True)
- elif self.type == "standard" and not self.collide_point(
+ elif self.drawer_type == "standard" and not self.collide_point(
touch.ox, touch.oy
):
return False
@@ -1421,17 +1250,35 @@ def on_touch_up(self, touch):
return True
def on_radius(self, instance_navigation_drawer, radius_value: list) -> None:
+ """Fired when the :attr:`radius` value changes."""
+
self._radius = radius_value
- def on_type(self, instance_navigation_drawer, drawer_type: str) -> None:
- if self.type == "standard":
+ def on_drawer_type(
+ self, instance_navigation_drawer, drawer_type: str
+ ) -> None:
+ """Fired when the :attr:`drawer_type` value changes."""
+
+ if self.drawer_type == "standard":
self.enable_swiping = False
self.close_on_click = False
else:
self.enable_swiping = True
self.close_on_click = True
+ def on_open(self, *args) -> None:
+ """Fired when the navigation drawer is opened."""
+
+ def on_close(self, *args) -> None:
+ """Fired when the navigation drawer is closed."""
+
def _handle_keyboard(self, window, key, *largs):
if key == 27 and self.status == "opened" and self.close_on_click:
self.set_state("close")
return True
+
+ def _check_state(self, *args):
+ if self.state == "open":
+ self.dispatch("on_open")
+ elif self.state == "close":
+ self.dispatch("on_close")
diff --git a/kivymd/uix/navigationrail/__init__.py b/kivymd/uix/navigationrail/__init__.py
index 7d3ff7582..9076b4cea 100644
--- a/kivymd/uix/navigationrail/__init__.py
+++ b/kivymd/uix/navigationrail/__init__.py
@@ -2,6 +2,8 @@
from .navigationrail import (
MDNavigationRail,
MDNavigationRailFabButton,
+ MDNavigationRailItemIcon,
+ MDNavigationRailItemLabel,
MDNavigationRailItem,
MDNavigationRailMenuButton,
)
diff --git a/kivymd/uix/navigationrail/navigationrail.kv b/kivymd/uix/navigationrail/navigationrail.kv
index e2623bbc5..712afdb9b 100644
--- a/kivymd/uix/navigationrail/navigationrail.kv
+++ b/kivymd/uix/navigationrail/navigationrail.kv
@@ -1,157 +1,109 @@
- pos_hint: {"center_x": .5, "top": 1}
+ pos_hint: {"center_x": .5}
+ theme_icon_color: "Custom"
+ icon_color: self.theme_cls.onSurfaceVariantColor
type: "standard"
pos_hint: {"center_x": .5}
-
-
-
- size_hint: None, 1
- width: "80dp"
-
- PanelRoot:
- id: box_buttons
-
- PanelItems:
- id: box_items
- orientation: "vertical"
- spacing: "12dp"
- adaptive_size: True
- pos_hint: {"center_x": .5}
+ theme_bg_color: "Custom"
+ md_bg_color: self.theme_cls.primaryColor
+ theme_icon_color: "Custom"
+ icon_color: self.theme_cls.onPrimaryColor
orientation: "vertical"
size_hint: None, None
- size: self.navigation_rail.width if self.navigation_rail else 100, "56dp"
-
- RelativeLayout:
- id: container
- size_hint: None, None
- size: root.size
+ width: "80dp"
+ height: self.minimum_height
+ spacing:
+ ( \
+ { \
+ "selected": "8dp", \
+ "labeled": "16dp", \
+ "unselected": "16dp", \
+ }[self._navigation_rail.type] \
+ ) \
+ if self._navigation_rail else 0
- RippleWidget:
- id: ripple_widget
- size_hint: None, None
- size: (container.width, container.width)
- radius: container.width / 2
- scale_value_x: 0
- scale_value_y: 0
- scale_value_z: 0
- opacity: 0
- md_bg_color:
- root.navigation_rail.ripple_color_item \
- if root.navigation_rail and \
- root.navigation_rail.ripple_color_item else \
- app.theme_cls.primary_color
- MDIcon:
- id: icon
- icon: root.icon
- opposite_colors: root.opposite_colors
- font_size: "24sp"
- pos_hint: {"center_x": .5}
- badge_icon: root.badge_icon
- badge_font_size: root.badge_font_size
- badge_icon_color:
- root.badge_icon_color \
- if root.badge_icon_color else \
- (1, 1, 1, 1)
- badge_bg_color:
- root.badge_bg_color \
- if root.badge_bg_color else \
- app.theme_cls.error_color
- theme_text_color: "Custom"
- text_color:
+
+ canvas.before:
+ Color:
+ rgba:
+ ( \
( \
- root.navigation_rail.icon_color_item_normal \
- if root.navigation_rail \
- and root.navigation_rail.icon_color_item_normal else \
- app.theme_cls.text_color \
+ self.theme_cls.secondaryContainerColor \
+ if not self.active_indicator_color else \
+ self.active_indicator_color \
+ )[:-1] + [self._alpha] \
) \
- if not root.active else \
+ if self._layer_color == self.theme_cls.transparentColor else \
+ self._layer_color
+ RoundedRectangle:
+ group: "navigation-rail-rounded-rectangle"
+ radius:
( \
- root.navigation_rail.icon_color_item_active \
- if root.navigation_rail.icon_color_item_active else \
- app.theme_cls.text_color \
- )
- y:
- container.height - \
+ [ \
( \
- (self.height + dp(4)) \
- if root.navigation_rail and \
- root.navigation_rail.type == "unselected" else \
- (self.height - dp(8)) \
- )
-
- canvas.before:
- Color:
- rgba:
- ( \
- ( \
- ( \
- app.theme_cls.primary_color \
- if not root.navigation_rail.selected_color_background else \
- root.navigation_rail.selected_color_background \
- ) \
- if root._release else \
- (0, 0, 0, 0) \
- ) \
- ) \
- if root.active else \
- (0, 0, 0, 0)
- RoundedRectangle:
- radius:
- [root._selected_region_width / 2,] \
- if root.navigation_rail and \
- root.navigation_rail.type == "unselected" else \
- [root._selected_region_width / 4,]
- size:
- root._selected_region_width, \
- root._selected_region_width \
- if root.navigation_rail and \
- root.navigation_rail.type == "unselected" else \
- root._selected_region_width / 2
- pos:
- self.center_x - self.width - dp(4), \
- self.center_y - root._selected_region_width / 2 \
- if root.navigation_rail and \
- root.navigation_rail.type == "unselected" else \
- self.center_y - root._selected_region_width / 4
-
- MDLabel:
- id: label
- text: root.text
- size_hint_x: None
- text_size: None, root.height
- adaptive_height: True
- opposite_colors: root.opposite_colors
- pos_hint: {"center_x": .5}
- y: "16"
- font_style: "Body2"
- theme_text_color: "Custom"
- font_name:
- root.navigation_rail.font_name \
- if root.navigation_rail else \
- "Roboto"
- text_color:
+ dp(16) \
+ if self._navigation_rail.type != "unselected" else \
+ dp(28) \
+ ), \
+ ] \
+ ) \
+ if self._navigation_rail else [0, ]
+ pos:
+ self.center_x - self._selected_region_width / 2, \
( \
- root.navigation_rail.text_color_item_normal \
- if root.navigation_rail and \
- root.navigation_rail.text_color_item_normal else \
- app.theme_cls.text_color \
+ self.y \
+ - ( \
+ dp(4) \
+ if self._navigation_rail.type != "unselected" else \
+ dp(16) \
) \
- if not root.active else \
+ ) \
+ if self._navigation_rail else 0
+ size:
+ self._selected_region_width, \
( \
- root.navigation_rail.text_color_item_active \
- if root.navigation_rail.text_color_item_active else \
- app.theme_cls.text_color \
- )
- opacity:
- (0 if root.navigation_rail and \
- root.navigation_rail.type == "unselected" else 1) \
- if root.navigation_rail and \
- root.navigation_rail.type != "selected" else \
- (0 if not root.active else 1)
+ self.height \
+ + ( \
+ dp(8) \
+ if self._navigation_rail.type != "unselected" else \
+ self.width + dp(8)\
+ ) \
+ ) \
+ if self._navigation_rail else 0
+
+ pos_hint: {"center_x": .5}
+
+
+
+ size_hint_y: None
+ height: 0
+ halign: "center"
+ text_color: self.theme_cls.onSurfaceVariantColor
+ font_style: "Label"
+ role: "medium"
+
+
+
+ size_hint: None, 1
+ width: "80dp"
+ md_bg_color: self.theme_cls.surfaceColor
+
+ BoxLayout:
+ id: box_items
+ orientation: "vertical"
+ size_hint: None, None
+ size: self.minimum_size
+ pos_hint: {"center_x": .5}
+ spacing:
+ { \
+ "selected": "12dp", \
+ "labeled": "24dp", \
+ "unselected": "36dp", \
+ }[root.type]
diff --git a/kivymd/uix/navigationrail/navigationrail.py b/kivymd/uix/navigationrail/navigationrail.py
index 762c74d6c..239048104 100644
--- a/kivymd/uix/navigationrail/navigationrail.py
+++ b/kivymd/uix/navigationrail/navigationrail.py
@@ -6,16 +6,19 @@
.. seealso::
- `Material Design spec, Navigation rail `_
-
-.. rubric:: Navigation rails provide access to primary destinations in apps
- when using tablet and desktop screens.
+ `Material Design spec, Navigation rail `_
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail.png
:align: center
-Usage
------
+.. rubric:: Navigation rails let people switch between UI views on mid-sized
+ devices.
+
+- Can contain 3-7 destinations plus an optional FAB
+- Always put the rail in the same place, even on different screens of an app
+
+Example
+-------
.. tabs::
@@ -24,34 +27,54 @@
.. code-block:: python
from kivy.lang import Builder
+ from kivy.properties import StringProperty
from kivymd.app import MDApp
+ from kivymd.uix.navigationrail import MDNavigationRailItem
KV = '''
+
+
+ MDNavigationRailItemIcon:
+ icon: root.icon
+
+ MDNavigationRailItemLabel:
+ text: root.text
+
+
MDBoxLayout:
MDNavigationRail:
+ type: "selected"
+
+ MDNavigationRailMenuButton:
+ icon: "menu"
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
+ MDNavigationRailFabButton:
+ icon: "home"
- MDNavigationRailItem:
- text: "JavaScript"
- icon: "language-javascript"
+ CommonNavigationRailItem:
+ icon: "folder-outline"
+ text: "Files"
- MDNavigationRailItem:
- text: "CPP"
- icon: "language-cpp"
+ CommonNavigationRailItem:
+ icon: "bookmark-outline"
+ text: "Bookmark"
- MDNavigationRailItem:
- text: "Git"
- icon: "git"
+ CommonNavigationRailItem:
+ icon: "library-outline"
+ text: "Library"
MDScreen:
+ md_bg_color: self.theme_cls.secondaryContainerColor
'''
+ class CommonNavigationRailItem(MDNavigationRailItem):
+ text = StringProperty()
+ icon = StringProperty()
+
+
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
@@ -63,36 +86,64 @@ def build(self):
.. code-block:: python
+ from kivy.clock import Clock
+ from kivy.properties import StringProperty
+
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem
+ from kivymd.uix.navigationrail import (
+ MDNavigationRailItem,
+ MDNavigationRail,
+ MDNavigationRailMenuButton,
+ MDNavigationRailFabButton,
+ MDNavigationRailItemIcon,
+ MDNavigationRailItemLabel,
+ )
+ from kivymd.uix.screen import MDScreen
+
+
+ class CommonNavigationRailItem(MDNavigationRailItem):
+ text = StringProperty()
+ icon = StringProperty()
+
+ def on_icon(self, instance, value):
+ def on_icon(*ars):
+ self.add_widget(MDNavigationRailItemIcon(icon=value))
+ Clock.schedule_once(on_icon)
+
+ def on_text(self, instance, value):
+ def on_text(*ars):
+ self.add_widget(MDNavigationRailItemLabel(text=value))
+ Clock.schedule_once(on_text)
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return (
- MDBoxLayout(
- MDNavigationRail(
- MDNavigationRailItem(
- text="Python",
- icon="language-python",
- ),
- MDNavigationRailItem(
- text="JavaScript",
- icon="language-javascript",
- ),
- MDNavigationRailItem(
- text="CPP",
- icon="language-cpp",
- ),
- MDNavigationRailItem(
- text="Git",
- icon="git",
- ),
- )
- )
+ return MDBoxLayout(
+ MDNavigationRail(
+ MDNavigationRailMenuButton(
+ icon="menu",
+ ),
+ MDNavigationRailFabButton(
+ icon="home",
+ ),
+ CommonNavigationRailItem(
+ icon="bookmark-outline",
+ text="Files",
+ ),
+ CommonNavigationRailItem(
+ icon="folder-outline",
+ text="Bookmark",
+ ),
+ CommonNavigationRailItem(
+ icon="library-outline",
+ text="Library",
+ ),
+ type="selected",
+ ),
+ MDScreen(
+ md_bg_color=self.theme_cls.secondaryContainerColor,
+ ),
)
@@ -104,476 +155,220 @@ def build(self):
Anatomy
-------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anatomy.png
- :align: center
+.. code-block:: kv
-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)
+ MDNavigationRail:
-Example
-=======
+ # Optional.
+ MDNavigationRailMenuButton:
+ icon: "menu"
-.. tabs::
+ # Optional.
+ MDNavigationRailFabButton:
+ icon: "home"
- .. tab:: Declarative KV and imperative python styles
+ MDNavigationRailItem
- .. code-block:: python
+ MDNavigationRailItemIcon:
+ icon: icon
- from kivy.clock import Clock
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.behaviors import CommonElevationBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.button import MDFillRoundFlatIconButton
- from kivymd.uix.label import MDLabel
- from kivymd.uix.screen import MDScreen
-
- KV = '''
- #:import FadeTransition kivy.uix.screenmanager.FadeTransition
-
-
-
- elevation: 1
- shadow_radius: 12
- -height: "56dp"
-
-
-
- focus_color: "#e7e4c0"
- unfocus_color: "#fffcf4"
+ MDNavigationRailItemLabel:
+ text: text
+ [...]
- MDScreen:
-
- MDNavigationLayout:
-
- ScreenManager:
-
- MDScreen:
-
- MDBoxLayout:
- orientation: "vertical"
-
- MDBoxLayout:
- adaptive_height: True
- md_bg_color: "#fffcf4"
- padding: "12dp"
-
- MDLabel:
- text: "12:00"
- adaptive_height: True
- pos_hint: {"center_y": .5}
-
- MDBoxLayout:
-
- MDNavigationRail:
- id: navigation_rail
- md_bg_color: "#fffcf4"
- selected_color_background: "#e7e4c0"
- ripple_color_item: "#e7e4c0"
- on_item_release: app.switch_screen(*args)
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anatomy.png
+ :align: center
- MDNavigationRailMenuButton:
- on_release: nav_drawer.set_state("open")
+Anatomy item
+------------
- MDNavigationRailFabButton:
- md_bg_color: "#b0f0d6"
+.. code-block:: kv
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
+ MDNavigationRailItem
- MDNavigationRailItem:
- text: "JavaScript"
- icon: "language-javascript"
+ MDNavigationRailItemIcon:
+ icon: icon
- MDNavigationRailItem:
- text: "CPP"
- icon: "language-cpp"
+ MDNavigationRailItemLabel:
+ text: text
- MDNavigationRailItem:
- text: "Swift"
- icon: "language-swift"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anatomy-item.png
+ :align: center
- ScreenManager:
- id: screen_manager
- transition:
- FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark)
+Configurations
+==============
- MDNavigationDrawer:
- id: nav_drawer
- radius: 0, 16, 16, 0
- md_bg_color: "#fffcf4"
- elevation: 2
- width: "240dp"
+Rail types
+----------
- MDNavigationDrawerMenu:
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type.png
+ :align: center
- MDBoxLayout:
- orientation: "vertical"
- adaptive_height: True
- spacing: "12dp"
- padding: 0, 0, 0, "12dp"
+1. Selected
+2. Unselected
+3. Labeled
- MDIconButton:
- icon: "menu"
+Selected
+--------
- MDBoxLayout:
- adaptive_height: True
- padding: "12dp", 0, 0, 0
+.. code-block:: kv
- ExtendedButton:
- text: "Compose"
- icon: "pencil"
+ MDNavigationRail:
+ type: "selected" # default
- DrawerClickableItem:
- text: "Python"
- icon: "language-python"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-selected.gif
+ :align: center
- DrawerClickableItem:
- text: "JavaScript"
- icon: "language-javascript"
+Unselected
+----------
- DrawerClickableItem:
- text: "CPP"
- icon: "language-cpp"
+.. code-block:: kv
- DrawerClickableItem:
- text: "Swift"
- icon: "language-swift"
- '''
+ MDNavigationRail:
+ type: "unselected"
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-unselected.gif
+ :align: center
- class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior):
- '''
- Implements a button of type
- `Extended FAB `_.
+Labeled
+-------
- .. rubric::
- Extended FABs help people take primary actions.
- They're wider than FABs to accommodate a text label and larger target
- area.
+.. code-block:: kv
- This type of buttons is not yet implemented in the standard widget set
- of the KivyMD library, so we will implement it ourselves in this class.
- '''
+ MDNavigationRail:
+ type: "labeled"
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.padding = "16dp"
- Clock.schedule_once(self.set_spacing)
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-labeled.gif
+ :align: center
- def set_spacing(self, interval):
- self.ids.box.spacing = "12dp"
+Rail anchored
+-------------
- def set_radius(self, *args):
- if self.rounded_button:
- value = self.height / 4
- self.radius = [value, value, value, value]
- self._radius = value
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anchored.png
+ :align: center
+1. Top
+2. Center
+3. Bottom
- class Example(MDApp):
- def build(self):
- self.theme_cls.material_style = "M3"
- self.theme_cls.primary_palette = "Orange"
- return Builder.load_string(KV)
+Top
+---
- def switch_screen(
- self, instance_navigation_rail, instance_navigation_rail_item
- ):
- '''
- Called when tapping on rail menu items. Switches application screens.
- '''
+.. code-block:: kv
- self.root.ids.screen_manager.current = (
- instance_navigation_rail_item.icon.split("-")[1].lower()
- )
+ MDNavigationRail:
+ anchor: "top"
- def on_start(self):
- '''Creates application screens.'''
-
- navigation_rail_items = self.root.ids.navigation_rail.get_items()[:]
- navigation_rail_items.reverse()
-
- for widget in navigation_rail_items:
- name_screen = widget.icon.split("-")[1].lower()
- screen = MDScreen(
- name=name_screen,
- md_bg_color="#edd769",
- radius=[18, 0, 0, 0],
- )
- box = MDBoxLayout(padding="12dp")
- label = MDLabel(
- text=name_screen.capitalize(),
- font_style="H1",
- halign="right",
- adaptive_height=True,
- shorten=True,
- )
- box.add_widget(label)
- screen.add_widget(box)
- self.root.ids.screen_manager.add_widget(screen)
+Center
+------
+.. code-block:: kv
- Example().run()
+ MDNavigationRail:
+ anchor: "center" # default
- .. tab:: Declarative python style
+Bottom
+------
- .. code-block:: python
+.. code-block:: kv
- from kivy.clock import Clock
- from kivy.metrics import dp
+ MDNavigationRail:
+ anchor: "bottom"
- from kivymd.app import MDApp
- from kivymd.uix.behaviors import CommonElevationBehavior
- from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.button import MDFillRoundFlatIconButton, MDIconButton
- from kivymd.uix.label import MDLabel
- from kivymd.uix.navigationdrawer import (
- MDNavigationDrawerItem,
- MDNavigationLayout,
- MDNavigationDrawer,
- MDNavigationDrawerMenu,
- )
- from kivymd.uix.navigationrail import (
- MDNavigationRail,
- MDNavigationRailMenuButton,
- MDNavigationRailFabButton,
- MDNavigationRailItem,
- )
- from kivymd.uix.screen import MDScreen
- from kivymd.uix.screenmanager import MDScreenManager
+API break
+=========
+1.2.0 version
+-------------
- class DrawerClickableItem(MDNavigationDrawerItem):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.focus_color = "#e7e4c0"
- self.unfocus_color = self.theme_cls.bg_light
- self.radius = 24
+.. code-block:: kv
+ MDNavigationRail:
- class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.padding = "16dp"
- self.elevation = 1
- self.shadow_radius = 12
- self.height = dp(56)
- Clock.schedule_once(self.set_spacing)
+ MDNavigationRailMenuButton:
+ icon: "menu"
- def set_spacing(self, interval):
- self.ids.box.spacing = "12dp"
+ MDNavigationRailFabButton:
+ icon: "home"
- def set_radius(self, *args):
- if self.rounded_button:
- self._radius = self.radius = self.height / 4
+ MDNavigationRailItem:
+ icon: icon
+ text: text
+ [...]
- class Example(MDApp):
- def build(self):
- self.theme_cls.material_style = "M3"
- self.theme_cls.primary_palette = "Orange"
- return MDScreen(
- MDNavigationLayout(
- MDScreenManager(
- MDScreen(
- MDBoxLayout(
- MDBoxLayout(
- MDLabel(
- text="12:00",
- adaptive_height=True,
- pos_hint={"center_y": 0.5},
- ),
- adaptive_height=True,
- md_bg_color="#fffcf4",
- padding="12dp",
- ),
- MDBoxLayout(
- MDNavigationRail(
- MDNavigationRailMenuButton(
- on_release=self.open_nav_drawer,
- ),
- MDNavigationRailFabButton(
- md_bg_color="#b0f0d6",
- ),
- MDNavigationRailItem(
- text="Python",
- icon="language-python",
- ),
- MDNavigationRailItem(
- text="JavaScript",
- icon="language-javascript",
- ),
- MDNavigationRailItem(
- text="CPP",
- icon="language-cpp",
- ),
- MDNavigationRailItem(
- text="Swift",
- icon="language-swift",
- ),
- id="navigation_rail",
- md_bg_color="#fffcf4",
- selected_color_background="#e7e4c0",
- ripple_color_item="#e7e4c0",
- ),
- MDScreenManager(
- id="screen_manager_content",
- ),
- id="root_box",
- ),
- id="box_rail",
- orientation="vertical",
- ),
- id="box",
- ),
- id="screen",
- ),
- id="screen_manager",
- ),
- MDNavigationDrawer(
- MDNavigationDrawerMenu(
- MDBoxLayout(
- MDIconButton(
- icon="menu",
- ),
- MDBoxLayout(
- ExtendedButton(
- text="Compose",
- icon="pencil",
- ),
- adaptive_height=True,
- padding=["12dp", 0, 0, 0],
- ),
- orientation="vertical",
- adaptive_height=True,
- spacing="12dp",
- padding=("3dp", 0, 0, "12dp"),
- ),
- DrawerClickableItem(
- text="Python",
- icon="language-python",
- ),
- DrawerClickableItem(
- text="JavaScript",
- icon="language-javascript",
- ),
- DrawerClickableItem(
- text="CPP",
- icon="language-cpp",
- ),
- DrawerClickableItem(
- text="Swift",
- icon="language-swift",
- ),
- ),
- id="nav_drawer",
- radius=(0, 16, 16, 0),
- elevation=4,
- width="240dp",
- ),
- )
+2.2.0 version
+-------------
- def switch_screen(self, *args, screen_manager_content=None):
- '''
- Called when tapping on rail menu items. Switches application screens.
- '''
+.. code-block:: kv
- instance_navigation_rail, instance_navigation_rail_item = args
- screen_manager_content.current = (
- instance_navigation_rail_item.icon.split("-")[1].lower()
- )
+ MDNavigationRail:
- def open_nav_drawer(self, *args):
- self.root.ids.nav_drawer.set_state("open")
-
- def on_start(self):
- '''Creates application screens.'''
-
- screen_manager = self.root.ids.screen_manager
- root_box = screen_manager.ids.screen.ids.box.ids.box_rail.ids.root_box
- navigation_rail = root_box.ids.navigation_rail
- screen_manager_content = root_box.ids.screen_manager_content
- navigation_rail_items = navigation_rail.get_items()[:]
- navigation_rail_items.reverse()
- navigation_rail.bind(
- on_item_release=lambda *args: self.switch_screen(
- *args, screen_manager_content=screen_manager_content
- )
- )
+ MDNavigationRailMenuButton:
+ icon: "menu"
- for widget in navigation_rail_items:
- name_screen = widget.icon.split("-")[1].lower()
- screen_manager_content.add_widget(
- MDScreen(
- MDBoxLayout(
- MDLabel(
- text=name_screen.capitalize(),
- font_style="H1",
- halign="right",
- adaptive_height=True,
- shorten=True,
- ),
- padding="12dp",
- ),
- name=name_screen,
- md_bg_color="#edd769",
- radius=[18, 0, 0, 0],
- ),
- )
+ MDNavigationRailFabButton:
+ icon: "home"
+ MDNavigationRailItem
- Example().run()
+ MDNavigationRailItemIcon:
+ icon: icon
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-example.gif
- :align: center
+ MDNavigationRailItemLabel:
+ text: text
+ [...]
"""
__all__ = (
"MDNavigationRail",
"MDNavigationRailItem",
+ "MDNavigationRailItemIcon",
+ "MDNavigationRailItemLabel",
"MDNavigationRailFabButton",
"MDNavigationRailMenuButton",
)
import os
-from typing import Union
from kivy.animation import Animation
from kivy.clock import Clock
-from kivy.core.window import Window
+from kivy.graphics import (
+ StencilPush,
+ RoundedRectangle,
+ StencilUse,
+ Color,
+ Ellipse,
+ StencilUnUse,
+ StencilPop,
+)
from kivy.lang import Builder
-from kivy.logger import Logger
from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
ColorProperty,
- ListProperty,
NumericProperty,
- ObjectProperty,
OptionProperty,
- StringProperty,
VariableListProperty,
+ ObjectProperty,
)
from kivy.uix.behaviors import ButtonBehavior
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.relativelayout import RelativeLayout
from kivymd import uix_path
-from kivymd.uix.behaviors import ScaleBehavior
-from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.button import MDFloatingActionButton, MDIconButton
-from kivymd.uix.card import MDCard
-from kivymd.uix.floatlayout import MDFloatLayout
-from kivymd.uix.widget import MDWidget
+from kivymd.theming import ThemableBehavior
+from kivymd.uix.behaviors import (
+ ScaleBehavior,
+ DeclarativeBehavior,
+ BackgroundColorBehavior,
+ RectangularRippleBehavior,
+)
+from kivymd.uix.behaviors.focus_behavior import FocusBehavior
+from kivymd.uix.button import MDFabButton, MDIconButton
+from kivymd.uix.label import MDIcon, MDLabel
with open(
os.path.join(uix_path, "navigationrail", "navigationrail.kv"),
@@ -582,49 +377,22 @@ def on_start(self):
Builder.load_string(kv_file.read())
-class PanelRoot(MDFloatLayout):
- """
- Contains
- :class:`~MDNavigationRailFabButton`, :class:`~MDNavigationRailMenuButton`
- buttons and a :class:`~Paneltems` container with menu items.
- """
-
-
-class PanelItems(MDBoxLayout):
- """Box for menu items."""
-
-
-class RippleWidget(MDWidget, ScaleBehavior):
- """
- Implements a background color for a menu item -
- (:class:`~MDNavigationRailItem`).
- """
-
-
-class MDNavigationRailFabButton(MDFloatingActionButton):
+class MDNavigationRailFabButton(MDFabButton):
"""
- Implements an optional floating action button (FAB).
+ Implements a floating action button (FAB).
For more information, see in the
- :class:`~kivymd.uix.button.MDFloatingActionButton` class documentation.
+ :class:`~kivymd.uix.button.button.MDFabButton`
+ class documentation.
"""
- icon = StringProperty("pencil")
+ md_bg_color_disabled = ColorProperty(None)
"""
- Button icon name.
-
- .. code-block:: kv
-
- MDNavigationRail:
+ The background color in (r, g, b, a) or string format of the switch when
+ the widget is disabled.
- MDNavigationRailFabButton:
- icon: "home"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-fab-button-icon.png
- :align: center
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'pencil'`.
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
@@ -633,228 +401,228 @@ class MDNavigationRailMenuButton(MDIconButton):
Implements a menu button.
For more information, see in the
- :class:`~kivymd.uix.button.MDIconButton` classes documentation.
+ :class:`~kivymd.uix.button.button.MDIconButton` class documentation.
"""
- icon = StringProperty("menu")
+ md_bg_color_disabled = ColorProperty(None)
"""
- Button icon name.
-
- .. code-block:: kv
-
- MDNavigationRail:
+ The background color in (r, g, b, a) or string format of the switch when
+ the widget is disabled.
- MDNavigationRailMenuButton:
- icon: "home"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-menu-button-icon.png
- :align: center
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'menu'`.
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
-class MDNavigationRailItem(ButtonBehavior, MDBoxLayout):
+class MDNavigationRailItemIcon(RectangularRippleBehavior, MDIcon):
"""
- Implements a menu item with an icon and text.
+ Implements an icon for the :class:`~MDNavigationRailItem` class.
For more information, see in the
- :class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.boxlayout.MDBoxLayout`
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivymd.uix.label.label.MDIcon`
classes documentation.
- """
- navigation_rail = ObjectProperty()
+ .. versionchanged:: 2.0.0
"""
- :class:`~MDNavigationRail` object.
- :attr:`navigation_rail` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
+ active_indicator_color = ColorProperty(None)
"""
+ Background color of the active indicator in (r, g, b, a) or string format.
- icon = StringProperty("checkbox-blank-circle")
+ :attr:`active_indicator_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- Icon item.
-
- .. code-block:: kv
-
- MDNavigationRail:
- MDNavigationRailItem:
- icon: "language-python"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-icon.png
- :align: center
+ _alpha = NumericProperty(0)
+ _active = BooleanProperty(False)
+ _navigation_rail = ObjectProperty()
+ _navigation_item = ObjectProperty()
+ _layer_color = ColorProperty([0, 0, 0, 0])
+ _selected_region_width = NumericProperty(dp(0))
+
+ def anim_complete(self, *args):
+ super().anim_complete()
+ self._navigation_rail.set_active_item(self._navigation_item)
+
+ def lay_canvas_instructions(self) -> None:
+ if not self.ripple_effect:
+ return
+
+ canvas_rectangle = self.canvas.before.get_group(
+ "navigation-rail-rounded-rectangle"
+ )[0]
+
+ with self.canvas.after if self.ripple_canvas_after else self.canvas.before:
+ if hasattr(self, "radius"):
+ self.radius = [
+ canvas_rectangle.radius[0][0],
+ ]
+ self._round_rad = self.radius
+ StencilPush(group="rectangular_ripple_behavior")
+ RoundedRectangle(
+ pos=canvas_rectangle.pos,
+ size=canvas_rectangle.size,
+ radius=self._round_rad,
+ group="rectangular_ripple_behavior",
+ )
+ StencilUse(group="rectangular_ripple_behavior")
+ self.col_instruction = Color(
+ rgba=self.ripple_color, group="rectangular_ripple_behavior"
+ )
+ self.ellipse = Ellipse(
+ size=(self._ripple_rad, self._ripple_rad),
+ pos=(
+ self.ripple_pos[0] - self._ripple_rad / 2.0,
+ self.ripple_pos[1] - self._ripple_rad / 2.0,
+ ),
+ group="rectangular_ripple_behavior",
+ )
+ StencilUnUse(group="rectangular_ripple_behavior")
+ RoundedRectangle(
+ pos=self.pos,
+ size=self.size,
+ radius=self._round_rad,
+ group="rectangular_ripple_behavior",
+ )
+ StencilPop(group="rectangular_ripple_behavior")
+ self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse)
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'checkbox-blank'`.
- """
- text = StringProperty()
+class MDNavigationRailItemLabel(ScaleBehavior, MDLabel):
"""
- Text item.
-
- .. code-block:: kv
-
- MDNavigationRail:
-
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-text.png
- :align: center
+ Implements an label for the :class:`~MDNavigationRailItem` class.
- :attr:`text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
+ :class:`~kivymd.uix.label.label.MDLabel`
+ classes documentation.
- badge_icon = StringProperty()
+ .. versionchanged:: 2.0.0
"""
- Badge icon name.
-
- .. code-block:: kv
-
- MDNavigationRail:
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
- badge_icon: "plus"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-icon.png
- :align: center
-
- :attr:`badge_icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+ scale_value_y = NumericProperty(0)
"""
+ Y-axis value.
- badge_icon_color = ColorProperty(None)
+ :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0`.
"""
- Badge icon color in (r, g, b, a) format.
- .. code-block:: kv
+ _active = BooleanProperty(False)
- MDNavigationRail:
+ def on__active(self, instance, value) -> None:
+ """Fired when the :attr:`_active` value changes."""
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
- badge_icon: "plus"
- badge_icon_color: 0, 0, 1, 1
+ self.text_color = (
+ self.theme_cls.onSurfaceColor
+ if value
+ else self.theme_cls.onSurfaceVariantColor
+ )
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-icon-color.png
- :align: center
- :attr:`badge_icon_color` is an :class:`~kivy.properties.StringProperty`
- and defaults to `None`.
+class MDNavigationRailItem(
+ DeclarativeBehavior,
+ ButtonBehavior,
+ ThemableBehavior,
+ FocusBehavior,
+ BoxLayout,
+):
"""
+ Implements a menu item with an icon and text.
- badge_bg_color = ColorProperty(None)
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.behaviors.focus_behavior.FocusBehavior`
+ :class:`~kivy.uix.boxlayout.BoxLayout`
+ classes documentation.
"""
- Badge icon background color in (r, g, b, a) format.
-
- .. code-block:: kv
-
- MDNavigationRail:
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
- badge_icon: "plus"
- badge_bg_color: "#b0f0d6"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-bg-color.png
- :align: center
-
- :attr:`badge_bg_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ active = BooleanProperty(False)
"""
+ Is the element active.
- badge_font_size = NumericProperty(0)
+ :attr:`active` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
"""
- Badge icon font size.
-
- .. code-block:: kv
-
- MDNavigationRail:
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
- badge_icon: "plus"
- badge_font_size: "24sp"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-font-size.png
- :align: center
-
- :attr:`badge_font_size` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
+ radius = VariableListProperty(0, length=4)
"""
+ Item radius.
- active = BooleanProperty(False)
- """
- Is the element active.
+ .. versionchanged:: 2.0.0
- :attr:`active` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[0, 0, 0, 0]`.
"""
- _selected_region_width = NumericProperty("56dp")
- _ripple_size = ListProperty([0, 0])
- _release = BooleanProperty(False)
+ _navigation_rail = ObjectProperty()
+ _icon_item = ObjectProperty()
- def on_active(
- self, instance_navigation_rail_item, value_active: bool
- ) -> None:
- """Called when the value of `active` changes."""
+ def on_active(self, instance, value) -> None:
+ """Fired when the :attr:`active` value changes."""
- self.animation_size_ripple_area(1 if value_active else 0)
+ if value:
+ Clock.schedule_once(self.on_leave)
- def animation_size_ripple_area(self, value: int) -> None:
- """Animates the size/fade of the ripple area."""
+ def on_leave(self, *args) -> None:
+ """Fired when the mouse goes outside the widget border."""
Animation(
- scale_value_x=value,
- scale_value_y=value,
- scale_value_z=value,
- opacity=value,
- d=0.25,
- t=self.navigation_rail.ripple_transition,
- ).start(self.ids.ripple_widget)
-
- def on_press(self) -> None:
- """Called when pressed on a panel element."""
+ _selected_region_width=0,
+ d=0.2,
+ ).start(self._icon_item)
- self._release = False
- self.active = True
- self.navigation_rail.deselect_item(self)
- self.navigation_rail.dispatch("on_item_press", self)
+ def on_enter(self, *args) -> None:
+ """Fired when mouse enter the bbox of the widget."""
- def on_release(self) -> None:
- """Called when released on a panel element."""
+ if not self.active:
+ # FIXME: Move layer creation to
+ # kivymd/uix/behaviors/state_layer_behavior.py module.
+ self._icon_item._layer_color = self.theme_cls.onSurfaceColor[
+ :-1
+ ] + [0.12]
+ Animation(
+ _selected_region_width=self._icon_item.width + dp(32),
+ d=0.2,
+ ).start(self._icon_item)
- self._release = True
- self.animation_size_ripple_area(0)
- self.navigation_rail.dispatch("on_item_release", self)
+ def on_leave(self, *args) -> None:
+ """Fired when the mouse goes outside the widget border."""
+ self._icon_item._layer_color = self.theme_cls.transparentColor
-class MDNavigationRail(MDCard):
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDNavigationRailItemLabel):
+ Clock.schedule_once(lambda x: self._check_type_rail(widget))
+ elif isinstance(widget, MDNavigationRailItemIcon):
+ widget._navigation_rail = self._navigation_rail
+ widget._navigation_item = self
+ self._icon_item = widget
+ return super().add_widget(widget)
+
+ def _check_type_rail(self, instance: MDNavigationRailItemLabel):
+ if self._navigation_rail.type == "labeled":
+ instance.scale_value_y = 1
+
+
+class MDNavigationRail(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ RelativeLayout,
+):
"""
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 -
- :class:`~MDNavigationRailItem`.
-
- :attr:`on_item_release`
- Called on the `on_release` event of menu item -
- :class:`~MDNavigationRailItem`.
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.relativelayout.RelativeLayout`
+ classes documentation.
"""
radius = VariableListProperty(0, length=4)
@@ -865,417 +633,145 @@ class MDNavigationRail(MDCard):
and defaults to `[0, 0, 0, 0]`.
"""
- padding = VariableListProperty([0, "36dp", 0, "36dp"], length=4)
- """
- Padding between layout box and children:
- [padding_left, padding_top, padding_right, padding_bottom].
-
- :attr:`padding` is a :class:`~kivy.properties.VariableListProperty`
- and defaults to `[0, '36dp', 0, '36dp']`.
- """
-
- anchor = OptionProperty("top", options=["top", "bottom", "center"])
+ anchor = OptionProperty("center", options=["top", "bottom", "center"])
"""
The position of the panel with menu items.
Available options are: `'top'`, `'bottom'`, `'center'`.
- .. rubric:: Top
-
- .. code-block:: kv
-
- MDNavigationRail:
- anchor: "top"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anchor-top.png
- :align: center
-
- .. rubric:: Center
-
- .. code-block:: kv
-
- MDNavigationRail:
- anchor: "center"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-center.png
- :align: center
-
- .. rubric:: Bottom
-
- .. code-block:: kv
-
- MDNavigationRail:
- anchor: "bottom"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-bottom.png
- :align: center
-
:attr:`anchor` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'top'`.
"""
type = OptionProperty(
- "labeled", options=["labeled", "selected", "unselected"]
+ "selected", options=["labeled", "selected", "unselected"]
)
"""
Type of switching menu items.
Available options are: `'labeled'`, `'selected'`, `'unselected'`.
- .. rubric:: Labeled
-
- .. code-block:: kv
-
- MDNavigationRail:
- type: "labeled"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-labeled.png
- :align: center
-
- .. rubric:: Selected
-
- .. code-block:: kv
-
- MDNavigationRail:
- type: "selected"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-selected.gif
- :align: center
-
- .. rubric:: Unselected
-
- .. code-block:: kv
-
- MDNavigationRail:
- type: "unselected"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-unselected.gif
- :align: center
-
:attr:`type` is an :class:`~kivy.properties.OptionProperty`
and defaults to `'labeled'`.
"""
- text_color_item_normal = ColorProperty(None)
- """
- The text color in (r, g, b, a) or string format of the normal menu item
- (:class:`~MDNavigationRailItem`).
-
- .. code-block:: kv
-
- MDNavigationRail:
- text_color_item_normal: app.theme_cls.primary_color
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-text-color-item-normal.png
- :align: center
-
- :attr:`text_color_item_normal` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- text_color_item_active = ColorProperty(None)
- """
- The text color in (r, g, b, a) or string format of the active menu item
- (:class:`~MDNavigationRailItem`).
-
- .. code-block:: kv
-
- MDNavigationRail:
- text_color_item_active: app.theme_cls.primary_color
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-text-color-item-active.png
- :align: center
-
- :attr:`text_color_item_active` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- icon_color_item_normal = ColorProperty(None)
- """
- The icon color in (r, g, b, a) or string format of the normal menu item
- (:class:`~MDNavigationRailItem`).
-
- .. code-block:: kv
-
- MDNavigationRail:
- icon_color_item_normal: app.theme_cls.primary_color
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-icon-color-item-normal.png
- :align: center
-
- :attr:`icon_color_item_normal` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- icon_color_item_active = ColorProperty(None)
- """
- The icon color in (r, g, b, a) or string format of the active menu item
- (:class:`~MDNavigationRailItem`).
-
- .. code-block:: kv
-
- MDNavigationRail:
- icon_color_item_active: app.theme_cls.primary_color
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-icon-color-item-active.png
- :align: center
-
- :attr:`icon_color_item_active` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- selected_color_background = ColorProperty(None)
- """
- Background color which will highlight the icon of the active menu item -
- :class:`~MDNavigationRailItem` - in (r, g, b, a) format.
-
- .. code-block:: kv
-
- MDNavigationRail:
- selected_color_background: "#e7e4c0"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-selected-color-background.png
- :align: center
-
- :attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- ripple_color_item = ColorProperty(None)
- """
- Ripple effect color of menu items (:class:`~MDNavigationRailItem`)
- in (r, g, b, a) format.
-
- .. code-block:: kv
-
- MDNavigationRail:
- ripple_color_item: "#e7e4c0"
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-ripple-color-item.png
- :align: center
-
- :attr:`ripple_color_item` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- ripple_transition = StringProperty("out_cubic")
- """
- Type of animation of the ripple effect when a menu item is selected.
-
- :attr:`ripple_transition` is a :class:`~kivy.properties.StringProperty`
- and defaults to `'ripple_transition'`.
- """
-
- current_selected_item = NumericProperty(0)
- """
- Index of the menu list item (:class:`~MDNavigationRailItem`) that will be
- active by default
-
- .. code-block:: kv
-
- MDNavigationRail:
- current_selected_item: 1
-
- MDNavigationRailItem:
- ...
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-current-selected-item.png
- :align: center
-
- :attr:`current_selected_item` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
-
- font_name = StringProperty("Roboto")
- """
- Font path for menu item (:class:`~MDNavigationRailItem`) text.
-
- .. code-block:: kv
-
- MDNavigationRail:
-
- MDNavigationRailItem:
- text: "Python"
- icon: "language-python"
- font_name: "nasalization-rg.ttf"
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-font-name.png
- :align: center
-
- :attr:`font_name` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'Roboto'`.
- """
+ fab_button: MDNavigationRailFabButton = ObjectProperty()
+ menu_button: MDNavigationRailFabButton = ObjectProperty()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- Clock.schedule_once(self.set_pos_menu_fab_buttons)
- Clock.schedule_once(self.set_current_selected_item)
- 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 -
- :class:`~MDNavigationRailItem`.
- """
-
- def on_item_release(self, *args) -> None:
- """
- Called on the `on_release` event of menu item -
- :class:`~MDNavigationRailItem`.
- """
-
- def deselect_item(
- self, selected_navigation_rail_item: MDNavigationRailItem
- ) -> None:
- """
- Sets the `active` value to `False` for all menu items
- (:class:`~MDNavigationRailItem`) except the selected item.
- Called when a menu item is touched.
- """
-
- for navigation_rail_item in self.ids.box_items.children:
- if selected_navigation_rail_item is not navigation_rail_item:
- navigation_rail_item.active = False
+ Clock.schedule_once(self._check_anchor)
+
+ def on_size(self, *args) -> None:
+ """Fired when the application screen size changes."""
+
+ Clock.schedule_once(self._set_pos_menu_fab_buttons)
+ Clock.schedule_once(self._check_anchor)
def get_items(self) -> list:
- """Returns a list of :class:`~MDNavigationRailItem` objects"""
+ """Returns a list of :class:`~MDNavigationRailItem` objects."""
return self.ids.box_items.children
- def set_pos_panel_items(
- self,
- instance_fab_button: Union[None, MDNavigationRailFabButton],
- instance_menu_button: Union[None, MDNavigationRailFabButton],
- ) -> None:
- """Set :class:`~Paneltems` panel position with menu items."""
-
- if self.anchor == "top":
- if instance_fab_button:
- self.ids.box_items.y = instance_fab_button.y - (
- len(self.ids.box_items.children) * dp(56)
- + self.padding[1] * 2
- + dp(24)
- )
+ def set_active_item(self, item: MDNavigationRailItem) -> None:
+ """Sets the active menu list item."""
+
+ for widget in self.ids.box_items.children:
+ if item is widget:
+ widget.active = not widget.active
+
+ for widget_item in item.children:
+ if isinstance(widget_item, MDNavigationRailItemLabel):
+ widget_item._active = widget.active
+ if self.type == "selected":
+ Animation(
+ scale_value_y=1 if widget.active else 0,
+ height=widget_item.texture_size[1]
+ if widget.active
+ else 0,
+ d=0.2,
+ ).start(widget_item)
+ if isinstance(widget_item, MDNavigationRailItemIcon):
+ widget_item._active = widget.active
+ widget_item._alpha = 1 if widget.active else 0
+ widget_item._selected_region_width = 0
+ Animation(
+ _selected_region_width=widget_item.width + dp(32)
+ if widget.active
+ else 0,
+ d=0.2,
+ ).start(widget_item)
else:
- if not instance_menu_button:
- self.ids.box_items.pos_hint = {"top": 1}
- else:
- self.ids.box_items.y = instance_menu_button.y - (
- len(self.ids.box_items.children) * dp(56)
- + self.padding[1] * 2
- )
- elif self.anchor == "center":
- self.ids.box_items.pos_hint = {"center_y": 0.5}
- elif self.anchor == "bottom":
- self.ids.box_items.y = dp(12)
-
- def set_current_selected_item(self, interval: Union[int, float]) -> None:
- """Sets the active menu list item (:class:`~MDNavigationRailItem`)."""
+ widget.active = False
+ for widget_item in widget.children:
+ widget_item._active = widget.active
+ if isinstance(widget_item, MDNavigationRailItemLabel):
+ if self.type == "selected":
+ Animation(scale_value_y=0, height=0, d=0.2).start(
+ widget_item
+ )
+ if isinstance(widget_item, MDNavigationRailItemIcon):
+ Animation(
+ _selected_region_width=0,
+ _alpha=0,
+ d=0.2,
+ ).start(widget_item)
- if self.ids.box_items.children:
- items = self.ids.box_items.children[:]
- items.reverse()
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDNavigationRailFabButton):
+ self.fab_button = widget
+ super().add_widget(widget)
+ elif isinstance(widget, MDNavigationRailMenuButton):
+ self.menu_button = widget
+ super().add_widget(widget)
+ elif isinstance(widget, MDNavigationRailItem):
+ self.ids.box_items.add_widget(widget)
+ widget._navigation_rail = self
+ widget._navigation_item = widget
+ widget.bind(on_release=self.set_active_item)
+ else:
+ return super().add_widget(widget)
- if len(items) <= self.current_selected_item:
- Logger.error(
- f"MDNavigationRail:You have "
- f"{len(self.ids.box_items.children)} menu items, but you "
- f"set {self.current_selected_item} as the active item. "
- f"The very first menu item will be set active."
+ def _set_pos_menu_fab_buttons(self, *args):
+ def set_pos_menu_fab_buttons(*args):
+ if self.fab_button and not self.menu_button:
+ self.fab_button.y = self.height - (
+ self.fab_button.height + dp(48)
)
- index = 0
- else:
- index = self.current_selected_item
-
- items[index].dispatch("on_press")
- items[index].dispatch("on_release")
-
- def set_pos_menu_fab_buttons(self, *args) -> None:
- """
- Sets the position of the :class:`~MDNavigationRailFabButton` and
- :class:`~MDNavigationRailMenuButton` buttons on the panel.
- """
-
- fab_button = None # MDNavigationRailFabButton
- menu_button = None # MDNavigationRailMenuButton
-
- for widget in self.ids.box_buttons.children:
- if isinstance(widget, MDNavigationRailFabButton):
- fab_button = widget
- if isinstance(widget, MDNavigationRailMenuButton):
- menu_button = widget
-
- if fab_button and menu_button:
-
- def set_fab_button_y(interval):
- fab_button.y = self.parent.height - (
- menu_button.height
- + fab_button.height
- + self.padding[1]
- + dp(18)
+ elif self.menu_button and not self.fab_button:
+ self.menu_button.y = self.height - (
+ self.menu_button.height + dp(38)
)
- self.set_pos_panel_items(fab_button, menu_button)
-
- Clock.schedule_once(set_fab_button_y)
- elif fab_button and not menu_button:
-
- def set_fab_button_y(interval):
- fab_button.y = self.parent.height - (
- self.padding[1] + fab_button.height
+ elif self.fab_button and self.menu_button:
+ self.menu_button.y = self.height - (
+ self.menu_button.height + dp(38)
+ )
+ self.fab_button.y = self.height - (
+ self.fab_button.height + dp(48) + dp(48)
)
- self.set_pos_panel_items(fab_button, menu_button)
- Clock.schedule_once(set_fab_button_y)
- else:
- Clock.schedule_once(
- lambda x: self.set_pos_panel_items(fab_button, menu_button)
+ Clock.schedule_once(set_pos_menu_fab_buttons)
+
+ def _check_anchor(self, *args):
+ def set_top_pos(*args):
+ anchor_button = None
+ if (
+ self.fab_button
+ and not self.menu_button
+ or self.fab_button
+ and self.menu_button
+ ):
+ anchor_button = self.fab_button
+ elif self.menu_button and not self.fab_button:
+ anchor_button = self.menu_button
+
+ self.ids.box_items.y = (
+ anchor_button.y
+ - (len(self.ids.box_items.children) * dp(56))
+ - dp(56)
)
- def add_widget(self, widget, *args, **kwargs):
- if isinstance(widget, MDNavigationRailFabButton):
- self.ids.box_buttons.add_widget(widget)
- elif isinstance(widget, MDNavigationRailMenuButton):
- self.ids.box_buttons.add_widget(widget)
- elif isinstance(widget, MDNavigationRailItem):
- widget.navigation_rail = self
- self.ids.box_items.add_widget(widget)
- elif isinstance(widget, (PanelRoot, PanelItems)):
- return super().add_widget(widget)
+ if self.anchor == "center":
+ self.ids.box_items.pos_hint = {"center_y": 0.5}
+ elif self.anchor == "top":
+ Clock.schedule_once(set_top_pos)
+ elif self.anchor == "bottom":
+ self.ids.box_items.y = dp(56)
diff --git a/kivymd/uix/pickers/colorpicker/colorpicker.py b/kivymd/uix/pickers/colorpicker/colorpicker.py
index 4dfdfd1bc..43727023a 100644
--- a/kivymd/uix/pickers/colorpicker/colorpicker.py
+++ b/kivymd/uix/pickers/colorpicker/colorpicker.py
@@ -562,8 +562,6 @@ def on_open(self) -> None:
self._current_tab = self.gradient_tab
self.ids.bottom_navigation_gradient.add_widget(self.gradient_tab)
- super().on_open()
-
def on_select_color(self, color: list) -> None:
"""Called when a gradient image is clicked."""
diff --git a/kivymd/uix/recyclegridlayout.py b/kivymd/uix/recyclegridlayout.py
index e519af538..a6ca3db70 100644
--- a/kivymd/uix/recyclegridlayout.py
+++ b/kivymd/uix/recyclegridlayout.py
@@ -16,7 +16,7 @@
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -28,7 +28,7 @@
MDRecycleGridLayout:
adaptive_height: True
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
Available options are:
----------------------
@@ -87,13 +87,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDRecycleGridLayout(
- DeclarativeBehavior, ThemableBehavior, RecycleGridLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ RecycleGridLayout,
+ MDAdaptiveWidget,
):
"""
- Recycle grid layout layout class. For more information, see in the
- :class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` class documentation.
+ Recycle grid layout class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/recycleview.py b/kivymd/uix/recycleview.py
index ddcf16c1b..e8467e844 100644
--- a/kivymd/uix/recycleview.py
+++ b/kivymd/uix/recycleview.py
@@ -16,7 +16,7 @@
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -27,7 +27,7 @@
.. code-block:: kv
MDRecycleView:
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
"""
__all__ = ("MDRecycleView",)
@@ -36,13 +36,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDRecycleView(
- DeclarativeBehavior, ThemableBehavior, RecycleView, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ RecycleView,
+ MDAdaptiveWidget,
):
"""
- Recycle view class. For more information, see in the
- :class:`~kivy.uix.recycleview.RecycleView` class documentation.
+ Recycle view class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.recycleview.RecycleView` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/relativelayout.py b/kivymd/uix/relativelayout.py
index b0e58e1fa..0f243526e 100644
--- a/kivymd/uix/relativelayout.py
+++ b/kivymd/uix/relativelayout.py
@@ -13,7 +13,7 @@
RelativeLayout:
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
RoundedRectangle:
pos: (0, 0)
size: self.size
@@ -26,20 +26,31 @@
MDRelativeLayout:
radius: [25, ]
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
"""
from kivy.uix.relativelayout import RelativeLayout
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDRelativeLayout(
- DeclarativeBehavior, ThemableBehavior, RelativeLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ RelativeLayout,
+ MDAdaptiveWidget,
):
"""
- Relative layout class. For more information, see in the
- :class:`~kivy.uix.relativelayout.RelativeLayout` class documentation.
+ Relative layout class.
+
+ For more information see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.relativelayout.RelativeLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/screen.py b/kivymd/uix/screen.py
index 1f24676cc..212d3c71e 100644
--- a/kivymd/uix/screen.py
+++ b/kivymd/uix/screen.py
@@ -34,15 +34,28 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
from kivymd.uix.hero import MDHeroTo
-class MDScreen(DeclarativeBehavior, ThemableBehavior, Screen, MDAdaptiveWidget):
+class MDScreen(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ Screen,
+ MDAdaptiveWidget,
+):
"""
Screen is an element intended to be used with a
- :class:`~kivymd.uix.screenmanager.MDScreenManager`. For more information,
- see in the :class:`~kivy.uix.screenmanager.Screen` class documentation.
+ :class:`~kivymd.uix.screenmanager.MDScreenManager`.
+
+ For more information see in the
+ :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.BackgroundColorBehavior` and
+ :class:`~kivy.uix.screenmanager.Screen` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
hero_to = ObjectProperty(deprecated=True)
@@ -71,7 +84,7 @@ class MDScreen(DeclarativeBehavior, ThemableBehavior, Screen, MDAdaptiveWidget):
"""
def on_hero_to(self, screen, widget: MDHeroTo) -> None:
- """Called when the value of the :attr:`hero_to` attribute changes."""
+ """Fired when the value of the :attr:`hero_to` attribute changes."""
if not isinstance(widget, MDHeroTo) or not issubclass(
widget.__class__, MDHeroTo
diff --git a/kivymd/uix/scrollview.py b/kivymd/uix/scrollview.py
index 0b00061fa..cc76d5300 100644
--- a/kivymd/uix/scrollview.py
+++ b/kivymd/uix/scrollview.py
@@ -16,7 +16,7 @@
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -27,23 +27,41 @@
.. code-block:: kv
MDScrollView:
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
"""
+from __future__ import annotations
+
__all__ = ("MDScrollView",)
+from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.uix.scrollview import ScrollView
-from kivymd.uix.behaviors import (
- DeclarativeBehavior,
- SpecificBackgroundColorBehavior,
-)
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
+
+
+class MDScrollViewEffect(DampedScrollEffect):
+ """
+ This class is simply based on DampedScrollEffect.
+ If you need any documentation please look at
+ :class:`~kivy.effects.dampedscrolleffect`.
+ """
+
+ def on_overscroll(self, instance, overscroll: int | float) -> None:
+ ...
-class MDScrollView(
- DeclarativeBehavior, SpecificBackgroundColorBehavior, ScrollView
-):
+class MDScrollView(DeclarativeBehavior, BackgroundColorBehavior, ScrollView):
"""
- ScrollView class. For more information, see in the
- :class:`~kivy.uix.scrollview.ScrollView` class documentation.
+ ScrollView class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.scrollview.ScrollView`
+ classes documentation.
"""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.effect_cls = MDScrollViewEffect
diff --git a/kivymd/uix/segmentedbutton/__init__.py b/kivymd/uix/segmentedbutton/__init__.py
index f516e1c7a..91ad5befc 100644
--- a/kivymd/uix/segmentedbutton/__init__.py
+++ b/kivymd/uix/segmentedbutton/__init__.py
@@ -1,4 +1,6 @@
from .segmentedbutton import ( # NOQA F401
MDSegmentedButton,
MDSegmentedButtonItem,
+ MDSegmentButtonIcon,
+ MDSegmentButtonLabel,
)
diff --git a/kivymd/uix/segmentedbutton/segmentedbutton.kv b/kivymd/uix/segmentedbutton/segmentedbutton.kv
index c4b63ffc3..23ad70e59 100644
--- a/kivymd/uix/segmentedbutton/segmentedbutton.kv
+++ b/kivymd/uix/segmentedbutton/segmentedbutton.kv
@@ -1,32 +1,124 @@
- size_hint: None, None
- height: "40dp"
- opacity: 0
+ size_hint_y: None
+ height: self.minimum_height
+ radius: [self.height / 2, ]
+
+ # FIXME: The use of an additional container is due to the Kivy bug -
+ # https://github.com/kivy/kivy/issues/8470
+ MDSegmentedButtonContainer:
+ id: container
+ size_hint: 1, None
+ opacity: 1
+ size_hint_min_x: 0
+ height:
+ { \
+ "large": "40dp", \
+ "normal": "36dp", \
+ "medium": "32dp", \
+ "small": "28dp", \
+ }[root.type]
+
+
+
+ font_style: "Label"
+ role: "large"
+ theme_line_height: "Custom"
+ line_height: 1
+ markup: True
+ adaptive_width: True
+ pos_hint: {"center_y": .5}
+ padding: "12dp", 0, "12dp", 0
+ text_color:
+ ( \
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.ids.container.parent.active else \
+ self.theme_cls.onSurfaceColor \
+ ) \
+ if self.theme_text_color == "Primary" else \
+ ( \
+ self.text_color \
+ if self.text_color else app.theme_cls.transparentColor \
+ ) \
+ ) \
+ if self.parent else app.theme_cls.transparentColor
+
+
+
+ theme_font_size: "Custom"
+ font_size: "18sp"
+ pos_hint: {"center_y": .5}
+ padding:
+ "12dp", \
+ 0, \
+ 0 \
+ if self.parent.parent.ids.container.parent._label else \
+ "12dp", \
+ 0
+ icon_color:
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self.parent.parent.ids.container.parent.active else \
+ self.theme_cls.onSurfaceColor \
+ ) \
+ if self.theme_icon_color == "Primary" else self.icon_color
- size_hint: None, None
- height: self.parent.height
+ md_bg_color:
+ ( \
+ self.theme_cls.transparentColor \
+ if not self.active else \
+ ( \
+ self.theme_cls.secondaryContainerColor \
+ if not self.selected_color else self.selected_color \
+ ) \
+ ) \
+ if not self.disabled else \
+ ( \
+ ( \
+ self.theme_cls.onSurfaceColor[:-1] \
+ + [self.segmented_button_opacity_value_disabled_container] \
+ if not self.md_bg_color_disabled else self.md_bg_color_disabled \
+ ) \
+ if not self.active else \
+ ( \
+ self.md_bg_color[:-1] \
+ + [self.segmented_button_opacity_value_disabled_container_active] \
+ if not self.md_bg_color_disabled else self.md_bg_color_disabled \
+ ) \
+ )
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)
+ ( \
+ self.theme_cls.outlineColor \
+ if self.theme_line_color == "Primary" else \
+ ( \
+ self._line_color \
+ if self._line_color else \
+ self.line_color \
+ ) \
+ ) \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + \
+ [self.segmented_button_opacity_value_disabled_line]
+
+ MDSegmentedButtonItemContainer:
+ id: container
+ size_hint_x: None
+ width: self.minimum_width
+ pos_hint: {"center_x": .5}
+
+
+
+ icon: "check"
+ pos_hint: {"center_y": .5}
+ theme_font_size: "Custom"
+ font_size: 0
+ icon_color:
+ ( \
+ self.theme_cls.onSecondaryContainerColor \
+ if self._item.active else \
+ self.theme_cls.onSurfaceColor \
+ ) \
+ if not self._segmented_button.selected_icon_color else \
+ self._segmented_button.selected_icon_color
diff --git a/kivymd/uix/segmentedbutton/segmentedbutton.py b/kivymd/uix/segmentedbutton/segmentedbutton.py
index 0729dba1e..a06b174c7 100644
--- a/kivymd/uix/segmentedbutton/segmentedbutton.py
+++ b/kivymd/uix/segmentedbutton/segmentedbutton.py
@@ -16,131 +16,209 @@
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-preview.png
:align: center
-Usage
+- Segmented buttons can contain icons, label text, or both
+- Two types: single-select and multi-select
+- Use for simple choices between two to five items (for more items or complex
+ choices, use chips)
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-types.png
+ :align: center
+
+1. Single-select segmented button
+2. Multi-select segmented button
+
+Anatomy
+-------
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-anatomy.png
+ :align: center
+
+Icons
-----
+Icons may be used as labels by themselves or alongside text.
+If an icon is used without label text, it must clearly communicate the option
+it represents.
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-icons.png
+ :align: center
+
+Use with text and icon
+----------------------
+
.. code-block:: kv
- MDScreen:
+ MDSegmentedButton:
- MDSegmentedButton:
+ MDSegmentedButtonItem:
- MDSegmentedButtonItem:
- icon: ...
- text: ...
+ MDSegmentButtonIcon:
+ icon: "language-python"
- MDSegmentedButtonItem:
- icon: ...
- text: ...
+ MDSegmentButtonLabel:
+ text: "Python"
- MDSegmentedButtonItem:
- icon: ...
- text: ...
+ MDSegmentedButtonItem:
-Example
--------
+ MDSegmentButtonIcon:
+ icon: "language-javascript"
-.. code-block:: python
+ MDSegmentButtonLabel:
+ text: "Java-Script"
- from kivy.lang import Builder
+ MDSegmentedButtonItem:
- from kivymd.app import MDApp
+ MDSegmentButtonIcon:
+ icon: "language-swift"
- KV = '''
- MDScreen:
+ MDSegmentButtonLabel:
+ text: "Swift"
- MDSegmentedButton:
- pos_hint: {"center_x": .5, "center_y": .5}
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-use-with-text-and-icon.gif
+ :align: center
- MDSegmentedButtonItem:
- text: "Walking"
+Use without text with an icon
+----------------------------
- MDSegmentedButtonItem:
- text: "Transit"
+.. code-block:: kv
- MDSegmentedButtonItem:
- text: "Driving"
- '''
+ MDSegmentedButton:
+ MDSegmentedButtonItem:
- class Example(MDApp):
- def build(self):
- self.theme_cls.theme_style = "Dark"
- return Builder.load_string(KV)
+ MDSegmentButtonIcon:
+ icon: "language-python"
+ MDSegmentedButtonItem:
- Example().run()
+ MDSegmentButtonIcon:
+ icon: "language-javascript"
+
+ MDSegmentedButtonItem:
-By default, segmented buttons support single marking of elements:
+ MDSegmentButtonIcon:
+ icon: "language-swift"
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-false.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-use-without-text-with-an-icon.gif
:align: center
-For multiple marking of elements, use the
-:attr:`kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButton.multiselect`
-parameter:
+Use only text
+-------------
.. code-block:: kv
MDSegmentedButton:
- multiselect: True
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-true.gif
- :align: center
+ MDSegmentedButtonItem:
-Control width
--------------
+ MDSegmentButtonLabel:
+ text: "Python"
+
+ MDSegmentedButtonItem:
+
+ MDSegmentButtonLabel:
+ text: "Java-Script"
-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:
+ MDSegmentedButtonItem:
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-width-by-default.png
+ MDSegmentButtonLabel:
+ text: "Swift"
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-use-only-text.gif
:align: center
-But you can use the `size_hint_x` parameter to specify the relative width:
+Multiselect
+-----------
+
+For multiple marking of elements, use the
+:attr:`kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButton.multiselect`
+parameter:
.. code-block:: kv
MDSegmentedButton:
- size_hint_x: .9
+ multiselect: True
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-width-size-hint-x.png
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-true.gif
:align: center
-Customization
--------------
+Type
+----
-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.
+Density can be used in denser UIs where space is limited. Density is only
+applied to the height. Each step down in density removes 4dp from the height.
-Events
-------
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-type.png
+ :align: center
-- on_marked
- The method is called when a segment is marked.
+.. code-block:: python
-- on_unmarked
- The method is called when a segment is unmarked.
+ from kivy.lang import Builder
-.. code-block:: kv
+ from kivymd.uix.label import MDLabel
+ from kivymd.uix.boxlayout import MDBoxLayout
+ from kivymd.uix.segmentedbutton import (
+ MDSegmentedButton,
+ MDSegmentedButtonItem,
+ MDSegmentButtonLabel,
+ )
+ from kivymd.app import MDApp
- MDSegmentedButton:
- on_marked: app.on_marked(*args)
+ KV = '''
+ MDScreen:
+ md_bg_color: self.theme_cls.backgroundColor
-.. code-block:: python
+ MDBoxLayout:
+ id: box
+ orientation: "vertical"
+ size_hint_x: .7
+ adaptive_height: True
+ spacing: "24dp"
+ pos_hint: {"center_x": .5, "center_y": .5}
+ '''
- def on_marked(
- self,
- segment_button: MDSegmentedButton,
- segment_item: MDSegmentedButtonItem,
- marked: bool,
- ) -> None:
- print(segment_button)
- print(segment_item)
- print(marked)
+
+ class Example(MDApp):
+ def on_start(self):
+ for segment_type in ["large", "normal", "medium", "small"]:
+ self.root.ids.box.add_widget(
+ MDBoxLayout(
+ MDLabel(
+ text=f"Type '{segment_type}'",
+ adaptive_height=True,
+ bold=True,
+ pos_hint={"center_y": 0.5},
+ halign="center",
+ ),
+ MDSegmentedButton(
+ MDSegmentedButtonItem(
+ MDSegmentButtonLabel(
+ text="Songs",
+ ),
+ ),
+ MDSegmentedButtonItem(
+ MDSegmentButtonLabel(
+ text="Albums",
+ ),
+ ),
+ MDSegmentedButtonItem(
+ MDSegmentButtonLabel(
+ text="Podcasts",
+ ),
+ ),
+ type=segment_type,
+ ),
+ orientation="vertical",
+ spacing="12dp",
+ adaptive_height=True,
+ )
+ )
+
+ def build(self):
+ return Builder.load_string(KV)
+
+
+ Example().run()
A practical example
-------------------
@@ -157,37 +235,30 @@ def on_marked(
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
- from kivymd.uix.segmentedbutton import MDSegmentedButton, MDSegmentedButtonItem
from kivymd.utils import asynckivy
KV = '''
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:
+ MDListItem:
+ radius: 16
+ theme_bg_color: "Custom"
+ md_bg_color: self.theme_cls.secondaryContainerColor
+
+ MDListItemLeadingAvatar:
source: root.album
- radius: root.radius
+
+ MDListItemHeadlineText:
+ text: root.name
+
+ MDListItemSupportingText:
+ text: root.path_to_file
MDScreen:
- md_bg_color: "#151514"
+ md_bg_color: self.theme_cls.backgroundColor
MDBoxLayout:
orientation: "vertical"
@@ -197,25 +268,31 @@ def on_marked(
MDLabel:
adaptive_height: True
text: "Your downloads"
- font_style: "H5"
- theme_text_color: "Custom"
- text_color: "#8A8D79"
+ theme_font_style: "Custom"
+ font_style: "Display"
+ role: "small"
MDSegmentedButton:
size_hint_x: 1
- selected_color: "#303A29"
- line_color: "#343930"
- on_marked: app.on_marked(*args)
MDSegmentedButtonItem:
- text: "Songs"
- active: True
+ on_active: app.generate_card()
+
+ MDSegmentButtonLabel:
+ text: "Songs"
+ active: True
MDSegmentedButtonItem:
- text: "Albums"
+ on_active: app.generate_card()
+
+ MDSegmentButtonLabel:
+ text: "Albums"
MDSegmentedButtonItem:
- text: "Podcasts"
+ on_active: app.generate_card()
+
+ MDSegmentButtonLabel:
+ text: "Podcasts"
RecycleView:
id: card_list
@@ -242,16 +319,9 @@ class UserCard(MDBoxLayout):
class Example(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
+ self.theme_cls.primary_palette = "Olive"
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):
@@ -273,68 +343,126 @@ async def generate_card():
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-practical-example.gif
:align: center
+
+API break
+=========
+
+1.2.0 version
+-------------
+
+.. code-block:: kv
+
+ MDSegmentedButton:
+ on_marked: func(*args)
+
+ MDSegmentedButtonItem:
+ icon: ...
+ text: ...
+
+2.0.0 version
+-------------
+
+.. code-block:: kv
+
+ MDSegmentedButton:
+
+ MDSegmentedButtonItem:
+ on_active: func(*args)
+
+ MDSegmentButtonIcon:
+ icon: ...
+
+ MDSegmentButtonLabel:
+ text: ...
+
"""
from __future__ import annotations
-__all__ = ("MDSegmentedButton", "MDSegmentedButtonItem")
+__all__ = (
+ "MDSegmentedButton",
+ "MDSegmentedButtonItem",
+ "MDSegmentButtonLabel",
+ "MDSegmentButtonIcon",
+)
import os
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
-from kivy.metrics import dp
+from kivy.metrics import dp, sp
from kivy.properties import (
BooleanProperty,
ColorProperty,
ListProperty,
NumericProperty,
StringProperty,
- VariableListProperty,
+ OptionProperty,
+ ObjectProperty,
)
from kivy.uix.behaviors import ButtonBehavior
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.relativelayout import RelativeLayout
+from kivymd.theming import ThemableBehavior
from kivymd import uix_path
-from kivymd.uix.behaviors import RectangularRippleBehavior, ScaleBehavior
+from kivymd.uix.behaviors import (
+ RectangularRippleBehavior,
+ DeclarativeBehavior,
+ BackgroundColorBehavior,
+)
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.floatlayout import MDFloatLayout
-from kivymd.uix.label import MDIcon
+from kivymd.uix.label import MDIcon, MDLabel
with open(
os.path.join(uix_path, "segmentedbutton", "segmentedbutton.kv"),
encoding="utf-8",
) as kv_file:
- Builder.load_string(kv_file.read())
+ Builder.load_string(kv_file.read(), filename="MDSegmentedButton")
class MDSegmentedButtonItem(
- RectangularRippleBehavior, ButtonBehavior, MDFloatLayout
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ RectangularRippleBehavior,
+ ButtonBehavior,
+ StateLayerBehavior,
+ RelativeLayout,
):
"""
Segment button item.
- For more information, see in the
- :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and
+ For more information see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and
:class:`~kivy.uix.behaviors.ButtonBehavior` and
- :class:`~kivymd.uix.boxlayout.MDBoxLayout`
+ :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and
+ :class:`~kivy.uix.relativelayout.RelativeLayout` and
class documentation.
"""
- icon = StringProperty()
+ selected_color = ColorProperty(None)
"""
- Icon segment.
+ Color of the marked segment.
+
+ .. versionadded:: 2.0.0
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+ :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- text = StringProperty()
+ md_bg_color_disabled = ColorProperty(None)
"""
- Text segment.
+ The background color in (r, g, b, a) or string format of the list item when
+ the list item is disabled.
- :attr:`text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
active = BooleanProperty(False)
@@ -345,49 +473,100 @@ class documentation.
and defaults to `False`.
"""
- disabled_color = ColorProperty(None)
- """
- Is active segment.
+ _icon = ObjectProperty() # MDSegmentButtonIcon object
+ _label = ObjectProperty() # MDSegmentButtonLabel object
+ _segmented_button = ObjectProperty() # MDSegmentedButton object
+ _line_color = ColorProperty(None)
- :attr:`active` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
+ def add_widget(self, widget, *args, **kwargs):
+ def add_selected_icon(container: MDSegmentedButtonItemContainer):
+ selected_icon = MDSegmentButtonSelectedIcon(
+ _segmented_button=self._segmented_button, _item=self
+ )
+ container.add_widget(selected_icon, index=1)
+
+ if isinstance(
+ widget,
+ (MDSegmentButtonLabel, MDSegmentButtonIcon),
+ ):
+ if isinstance(widget, MDSegmentButtonLabel):
+ self._label = widget
+ elif isinstance(widget, MDSegmentButtonIcon):
+ self._icon = widget
+ Clock.schedule_once(
+ lambda x: self._segmented_button._set_size_hint_min_x(widget)
+ )
+ self.ids.container.add_widget(widget)
+ elif isinstance(widget, MDSegmentedButtonItemContainer):
+ Clock.schedule_once(lambda x: add_selected_icon(widget))
+ return super().add_widget(widget)
+
+ def on_line_color(self, instance, value) -> None:
+ """Fired when the values of :attr:`line_color` change."""
- _no_ripple_effect = BooleanProperty(True)
- _current_icon = ""
- _current_md_bg_color = None
+ if not self.disabled and self.theme_line_color == "Custom":
+ self._line_color = value
- 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
+ def on_active(self, instance, value) -> None:
+ """
+ Fired when the :attr:`active` value changes.
+ Animates the marker icon for the element.
+ """
+
+ def set_active(*args):
+ t = (
+ self._segmented_button.opening_icon_transition
+ if value
+ else self._segmented_button.hiding_icon_transition
+ )
+ d = (
+ self._segmented_button.opening_icon_duration
+ if value
+ else self._segmented_button.hiding_icon_duration
+ )
+
+ if self._icon and self._segmented_button:
+ if self._label:
+ Animation(font_size=0 if value else sp(18), t=t, d=d).start(
+ self._icon
+ )
+
+ selected_icon = self._get_selected_icon_from_container()
+ if selected_icon:
+ Animation(font_size=sp(18) if value else 0, t=t, d=d).start(
+ selected_icon
)
- 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)
+ Clock.schedule_once(set_active, 0.5)
+
+ def on_disabled(self, instance, value) -> None:
+ """Fired when the :attr:`disabled` value changes."""
+
+ selected_icon = None
- def on_icon(self, instance, icon_name: str):
- if icon_name != "check":
- self._current_icon = icon_name
+ if self._icon and self._segmented_button:
+ selected_icon = self._get_selected_icon_from_item()
+ elif not self._icon and self._segmented_button:
+ selected_icon = self._get_selected_icon_from_container()
+ if selected_icon:
+ selected_icon.state_layer_color = self.theme_cls.transparentColor
-# 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
+ def _get_selected_icon_from_container(self):
+ selected_icon = None
+ for item in self.ids.container.children:
+ if isinstance(item, MDSegmentButtonSelectedIcon):
+ selected_icon = item
+ break
+ return selected_icon
+
+ def _get_selected_icon_from_item(self):
+ selected_icon = None
+ for item in self.children:
+ if isinstance(item, MDSegmentButtonSelectedIcon):
+ selected_icon = item
+ break
+ return selected_icon
class MDSegmentedButton(MDBoxLayout):
@@ -396,20 +575,6 @@ class MDSegmentedButton(MDBoxLayout):
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)
@@ -428,12 +593,12 @@ class MDSegmentedButton(MDBoxLayout):
and defaults to `'linear'`.
"""
- hiding_icon_duration = NumericProperty(0.05)
+ hiding_icon_duration = NumericProperty(0.1)
"""
Duration of hiding the current icon.
:attr:`hiding_icon_duration` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0.05`.
+ and defaults to `1`.
"""
opening_icon_transition = StringProperty("linear")
@@ -444,210 +609,162 @@ class MDSegmentedButton(MDBoxLayout):
and defaults to `'linear'`.
"""
- opening_icon_duration = NumericProperty(0.05)
+ opening_icon_duration = NumericProperty(0.1)
"""
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`.
+ and defaults to `0.1`.
"""
- selected_items = ListProperty()
+ selected_segments = ListProperty()
"""
The list of :class:`~MDSegmentedButtonItem` objects that are currently
marked.
- :attr:`selected_items` is a :class:`~kivy.properties.ListProperty`
+ :attr:`selected_segments` is a :class:`~kivy.properties.ListProperty`
and defaults to `[]`.
"""
- selected_color = ColorProperty(None)
+ type = OptionProperty(
+ "large", options=["large", "normal", "medium", "small"]
+ )
"""
- Color of the marked segment.
+ Density can be used in denser UIs where space is limited.
+ Density is only applied to the height. Each step down in density removes
+ '4dp' from the height.
- :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty`
+ .. versionadded:: 2.0.0
+
+ Available options are: 'large', 'normal', 'medium', 'small'.
+
+ :attr:`type` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'large'`.
+ """
+
+ selected_icon_color = ColorProperty(None)
+ """
+ Color in (r, g, b, a) or string format of the icon of the marked segment.
+
+ .. versionadded:: 2.0.0
+
+ :attr:`selected_icon_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 get_marked_items(self) -> list:
+ """Returns a list of active item objects."""
- def mark_segment(self, *args) -> None:
- """Programmatically marks a segment."""
+ return [item for item in self.ids.container.children if item.active]
- for widget in self.children:
- if widget.active:
- widget.active = False
- widget.dispatch("on_release")
+ def get_items(self) -> list:
+ """Returns a list of item objects."""
- if not self.multiselect:
- break
+ return [item for item in self.ids.container.children]
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)
+ if self.ids.container.children[0].radius == [0, 0, 0, 0]:
+ self.ids.container.children[0].radius = (
+ 0,
+ self.height / 2,
+ self.height / 2,
+ 0,
+ )
+ if self.ids.container.children[-1].radius == [0, 0, 0, 0]:
+ self.ids.container.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.
- """
+ def mark_item(self, segment_item: MDSegmentedButtonItem) -> None:
+ """Fired when a segment element is clicked (`on_release` event)."""
- 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)
+ if not segment_item.disabled:
+ if not segment_item.active and not self.multiselect:
+ segment_item.active = True
+ elif self.multiselect:
+ segment_item.active = not segment_item.active
- for widget in self.children:
- widget.width = max_width
+ if not self.multiselect:
+ for widget in self.ids.container.children:
+ if segment_item is not widget:
+ widget.active = False
- self.opacity = 1
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDSegmentedButtonItem):
+ widget._segmented_button = self
+ widget.bind(on_release=self.mark_item)
+ self.ids.container.add_widget(widget)
+ Clock.schedule_once(self.adjust_segment_radius)
+ elif isinstance(widget, MDSegmentedButtonContainer):
+ return super().add_widget(widget)
- for widget in self.children:
- if widget.active:
- widget.dispatch("on_release")
+ def _set_size_hint_min_x(
+ self, widget: MDSegmentButtonLabel | MDSegmentButtonIcon
+ ):
+ self.ids.container.size_hint_min_x += widget.texture_size[0] + dp(36)
- 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)
+class MDSegmentedButtonContainer(BoxLayout):
+ """
+ Implements a container for placing :class:`~MDSegmentedButtonItem`
+ elements.
- 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.
- """
+ .. versionadded:: 2.0.0
- 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)
+ For more information, see in the
+ :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
+ """
- 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)
+class MDSegmentedButtonItemContainer(BoxLayout):
+ """
+ Implements a container for placing :class:`~MDSegmentButtonLabel`
+ and :class:`~MDSegmentButtonLabel` elements.
- 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
+ .. versionadded:: 2.0.0
- def add_widget(self, widget, *args, **kwargs):
- if isinstance(widget, MDSegmentedButtonItem):
- widget.bind(on_release=self.mark_item)
- return super().add_widget(widget)
+ For more information, see in the
+ :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
+ """
+
+
+class MDSegmentButtonSelectedIcon(MDIcon):
+ """
+ Implements the selected icon with scaling behavior
+ for :class:`~MDSegmentedButtonItem` class.
+
+ .. versionadded:: 2.0.0
- def on_size(self, instance_segment_button, size: list) -> None:
- """Called when the root screen is resized."""
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDIcon` class documentation.
+ """
+
+ _segmented_button = ObjectProperty() # MDSegmentedButton object
+ _item = ObjectProperty() # MDSegmentedButtonItem object
+
+
+class MDSegmentButtonIcon(MDIcon):
+ """
+ Implements a icon for :class:`~MDSegmentedButtonItem` class.
- if self.size_hint_x:
- max_width = size[0] / len(self.children)
- for widget in self.children:
- widget.width = max_width
+ .. versionadded:: 2.0.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDIcon` class documentation.
+ """
- 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 MDSegmentButtonLabel(MDLabel):
+ """
+ Implements a label for :class:`~MDSegmentedButtonItem` class.
+ .. versionadded:: 2.0.0
-class SegmentButtonIcon(MDIcon, ScaleBehavior):
- """Implements an icon with scaling behavior."""
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
diff --git a/kivymd/uix/selection/__init__.py b/kivymd/uix/selection/__init__.py
deleted file mode 100644
index 9be34e218..000000000
--- a/kivymd/uix/selection/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .selection import MDSelectionList # NOQA F401
diff --git a/kivymd/uix/selection/selection.kv b/kivymd/uix/selection/selection.kv
deleted file mode 100644
index 7e3dead60..000000000
--- a/kivymd/uix/selection/selection.kv
+++ /dev/null
@@ -1,17 +0,0 @@
-
- theme_text_color: "Custom"
- text_color: self.icon_check_color
-
- canvas.before:
- PushMatrix
- Scale:
- x: root.scale
- y: root.scale
- z: root.scale
- origin: self.center
- canvas.after:
- PopMatrix
-
-
-
- md_bg_color: root.overlay_color if root.selected else (0, 0, 0, 0)
diff --git a/kivymd/uix/selection/selection.py b/kivymd/uix/selection/selection.py
deleted file mode 100644
index 1ee867650..000000000
--- a/kivymd/uix/selection/selection.py
+++ /dev/null
@@ -1,672 +0,0 @@
-"""
-Components/Selection
-====================
-
-.. seealso::
-
- `Material Design spec, Banner `_
-
-.. rubric:: Selection refers to how users indicate specific items they intend to take action on.
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-previous.png
- :align: center
-
-Entering selection mode
------------------------
-
-To select an item and enter selection mode, long press the item:
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/enter-selection-mode.gif
- :align: center
-
-Exiting selection mode
-----------------------
-
-To exit selection mode, tap each selected item until they’re all deselected:
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/exit-selection-mode.gif
- :align: center
-
-Larger selections
------------------
-
-.. note:: This feature is missing yet.
-
-Events
-------
-
-.. code-block:: python
-
- def on_selected(self, instance_selection_list, instance_selection_item):
- '''Called when a list item is selected.'''
-
- def on_unselected(self, instance_selection_list, instance_selection_item):
- '''Called when a list item is unselected.'''
-
-Example with TwoLineAvatarListItem
-----------------------------------
-
-.. code-block:: python
-
- from kivy.animation import Animation
- from kivy.lang import Builder
- from kivy.utils import get_color_from_hex
-
- from kivymd.app import MDApp
- from kivymd.uix.list import TwoLineAvatarListItem
-
- KV = '''
-
- text: "Two-line item with avatar"
- secondary_text: "Secondary text here"
- _no_ripple_effect: True
-
- ImageLeftWidget:
- source: "data/logo/kivy-icon-256.png"
-
-
- MDBoxLayout:
- orientation: "vertical"
-
- MDTopAppBar:
- id: toolbar
- title: "Inbox"
- left_action_items: [["menu"]]
- right_action_items: [["magnify"], ["dots-vertical"]]
- md_bg_color: 0, 0, 0, 1
-
- MDBoxLayout:
- padding: "24dp", "8dp", 0, "8dp"
- adaptive_size: True
-
- MDLabel:
- text: "Today"
- adaptive_size: True
-
- ScrollView:
-
- MDSelectionList:
- id: selection_list
- spacing: "12dp"
- overlay_color: app.overlay_color[:-1] + [.2]
- icon_bg_color: app.overlay_color
- on_selected: app.on_selected(*args)
- on_unselected: app.on_unselected(*args)
- on_selected_mode: app.set_selection_mode(*args)
- '''
-
-
- class MyItem(TwoLineAvatarListItem):
- pass
-
-
- class Example(MDApp):
- overlay_color = get_color_from_hex("#6042e4")
-
- def build(self):
- return Builder.load_string(KV)
-
- def on_start(self):
- for i in range(10):
- self.root.ids.selection_list.add_widget(MyItem())
-
- def set_selection_mode(self, instance_selection_list, mode):
- if mode:
- md_bg_color = self.overlay_color
- left_action_items = [
- [
- "close",
- lambda x: self.root.ids.selection_list.unselected_all(),
- ]
- ]
- right_action_items = [["trash-can"], ["dots-vertical"]]
- else:
- md_bg_color = (0, 0, 0, 1)
- left_action_items = [["menu"]]
- right_action_items = [["magnify"], ["dots-vertical"]]
- self.root.ids.toolbar.title = "Inbox"
-
- Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar)
- self.root.ids.toolbar.left_action_items = left_action_items
- self.root.ids.toolbar.right_action_items = right_action_items
-
- def on_selected(self, instance_selection_list, instance_selection_item):
- self.root.ids.toolbar.title = str(
- len(instance_selection_list.get_selected_list_items())
- )
-
- def on_unselected(self, instance_selection_list, instance_selection_item):
- if instance_selection_list.get_selected_list_items():
- self.root.ids.toolbar.title = str(
- len(instance_selection_list.get_selected_list_items())
- )
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-example-with-listItem.gif
- :align: center
-
-Example with FitImage
----------------------
-
-.. code-block:: python
-
- from kivy.animation import Animation
- from kivy.lang import Builder
- from kivy.properties import ColorProperty
-
- from kivymd.app import MDApp
- from kivymd.uix.fitimage import FitImage
-
- KV = '''
- MDBoxLayout:
- orientation: "vertical"
- md_bg_color: app.theme_cls.bg_light
-
- MDTopAppBar:
- id: toolbar
- title: "Inbox"
- left_action_items: [["menu"]]
- right_action_items: [["magnify"], ["dots-vertical"]]
- md_bg_color: app.theme_cls.bg_light
- specific_text_color: 0, 0, 0, 1
-
- MDBoxLayout:
- padding: "24dp", "8dp", 0, "8dp"
- adaptive_size: True
-
- MDLabel:
- text: "Today"
- adaptive_size: True
-
- ScrollView:
-
- MDSelectionList:
- id: selection_list
- padding: "24dp", 0, "24dp", "24dp"
- cols: 3
- spacing: "12dp"
- overlay_color: app.overlay_color[:-1] + [.2]
- icon_bg_color: app.overlay_color
- progress_round_color: app.progress_round_color
- on_selected: app.on_selected(*args)
- on_unselected: app.on_unselected(*args)
- on_selected_mode: app.set_selection_mode(*args)
- '''
-
-
- class Example(MDApp):
- overlay_color = ColorProperty("#6042e4")
- progress_round_color = "#ef514b"
-
- def build(self):
- return Builder.load_string(KV)
-
- def on_start(self):
- for i in range(10):
- self.root.ids.selection_list.add_widget(
- FitImage(
- source="image.png",
- size_hint_y=None,
- height="240dp",
- )
- )
-
- def set_selection_mode(self, instance_selection_list, mode):
- if mode:
- md_bg_color = self.overlay_color
- left_action_items = [
- [
- "close",
- lambda x: self.root.ids.selection_list.unselected_all(),
- ]
- ]
- right_action_items = [["trash-can"], ["dots-vertical"]]
- else:
- md_bg_color = (1, 1, 1, 1)
- left_action_items = [["menu"]]
- right_action_items = [["magnify"], ["dots-vertical"]]
- self.root.ids.toolbar.title = "Inbox"
-
- Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar)
- self.root.ids.toolbar.left_action_items = left_action_items
- self.root.ids.toolbar.right_action_items = right_action_items
-
- def on_selected(self, instance_selection_list, instance_selection_item):
- self.root.ids.toolbar.title = str(
- len(instance_selection_list.get_selected_list_items())
- )
-
- def on_unselected(self, instance_selection_list, instance_selection_item):
- if instance_selection_list.get_selected_list_items():
- self.root.ids.toolbar.title = str(
- len(instance_selection_list.get_selected_list_items())
- )
-
-
- Example().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-example-with-fitimage.gif
- :align: center
-"""
-
-__all__ = ("MDSelectionList",)
-
-import os
-from typing import Union
-
-from kivy.animation import Animation
-from kivy.clock import Clock
-from kivy.graphics.context_instructions import Color
-from kivy.graphics.vertex_instructions import (
- Ellipse,
- RoundedRectangle,
- SmoothLine,
-)
-from kivy.lang import Builder
-from kivy.metrics import dp
-from kivy.properties import (
- BooleanProperty,
- ColorProperty,
- ListProperty,
- NumericProperty,
- ObjectProperty,
- StringProperty,
-)
-
-from kivymd import uix_path
-from kivymd.uix.behaviors import TouchBehavior
-from kivymd.uix.button import MDIconButton
-from kivymd.uix.list import MDList
-from kivymd.uix.relativelayout import MDRelativeLayout
-
-with open(
- os.path.join(uix_path, "selection", "selection.kv"), encoding="utf-8"
-) as kv_file:
- Builder.load_string(kv_file.read())
-
-
-class SelectionIconCheck(MDIconButton):
- """Implements the icon for the checked item."""
-
- scale = NumericProperty(0)
- icon_check_color = ColorProperty([0, 0, 0, 1])
-
-
-class SelectionItem(MDRelativeLayout, TouchBehavior):
- selected = BooleanProperty(False)
- """
- Whether or not an item is checked.
-
- :attr:`selected` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- owner = ObjectProperty()
- """
- Instance of :class:`~kivymd.uix.selection.MDSelectionList` class.
-
- :attr:`owner` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
- """
-
- instance_item = ObjectProperty()
- """
- User item. Must be a Kivy or KivyMD widget.
-
- :attr:`instance_item` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
- """
-
- instance_icon = ObjectProperty()
- """
- Instance of :class:`~kivymd.uix.selection.SelectionIconCheck` class.
-
- :attr:`instance_icon` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
- """
-
- overlay_color = ColorProperty([0, 0, 0, 0.2])
- """See :attr:`~MDSelectionList.overlay_color`."""
-
- progress_round_size = NumericProperty(dp(46))
- """See :attr:`~MDSelectionList.progress_round_size`."""
-
- progress_round_color = ColorProperty(None)
- """See :attr:`~MDSelectionList.progress_round_color`."""
-
- _progress_round = NumericProperty(0)
- _progress_line_end = NumericProperty(0)
- _progress_animation = BooleanProperty(False)
- _touch_long = BooleanProperty(False)
- _instance_progress_inner_circle_color = ObjectProperty()
- _instance_progress_inner_circle_ellipse = ObjectProperty()
- _instance_progress_inner_outer_color = ObjectProperty()
- _instance_progress_inner_outer_line = ObjectProperty()
- _instance_overlay_color = ObjectProperty()
- _instance_overlay_rounded_rec = ObjectProperty()
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Clock.schedule_once(self.set_progress_round)
-
- def set_progress_round(self, interval: Union[int, float]) -> None:
- with self.canvas.after:
- self._instance_progress_inner_circle_color = Color(
- rgba=(0, 0, 0, 0)
- )
- self._instance_progress_inner_circle_ellipse = Ellipse(
- size=self.get_progress_round_size(),
- pos=self.get_progress_round_pos(),
- )
- self.bind(
- pos=self.update_progress_inner_circle_ellipse,
- size=self.update_progress_inner_circle_ellipse,
- )
- # FIXME: Radius value is not displayed.
- self._instance_overlay_color = Color(rgba=(0, 0, 0, 0))
- self._instance_overlay_rounded_rec = RoundedRectangle(
- size=self.size,
- pos=self.pos,
- radius=self.instance_item.radius
- if hasattr(self.instance_item, "radius")
- else [
- 0,
- ],
- )
- self.bind(
- pos=self.update_overlay_rounded_rec,
- size=self.update_overlay_rounded_rec,
- )
- self._instance_progress_inner_outer_color = Color(rgba=(0, 0, 0, 0))
- self._instance_progress_inner_outer_line = SmoothLine(
- width=dp(4),
- circle=[
- self.center_x,
- self.center_y,
- self.progress_round_size * 0.58,
- 0,
- 0,
- ],
- )
-
- def do_selected_item(self, *args) -> None:
- Animation(scale=1, d=0.2).start(self.instance_icon)
- self.selected = True
- self._progress_animation = False
- self._instance_overlay_color.rgba = self.get_overlay_color()
- self.owner.dispatch("on_selected", self)
-
- def do_unselected_item(self) -> None:
- Animation(scale=0, d=0.2).start(self.instance_icon)
- self.selected = False
- self._instance_overlay_color.rgba = self.get_overlay_color()
- self.owner.dispatch("on_unselected", self)
-
- def do_animation_progress_line(
- self, animation: Animation, instance_selection_item, value: float
- ) -> None:
- self._instance_progress_inner_outer_line.circle = (
- self.center_x,
- self.center_y,
- self.progress_round_size * 0.58,
- 0,
- 360 * value,
- )
-
- def update_overlay_rounded_rec(self, *args) -> None:
- self._instance_overlay_rounded_rec.size = self.size
- self._instance_overlay_rounded_rec.pos = self.pos
-
- def update_progress_inner_circle_ellipse(self, *args) -> None:
- self._instance_progress_inner_circle_ellipse.size = (
- self.get_progress_round_size()
- )
- self._instance_progress_inner_circle_ellipse.pos = (
- self.get_progress_round_pos()
- )
-
- def reset_progress_animation(self) -> None:
- Animation.cancel_all(self)
- self._progress_animation = False
- self._instance_progress_inner_circle_color.rgba = (0, 0, 0, 0)
- self._instance_progress_inner_outer_color.rgba = (0, 0, 0, 0)
- self._instance_progress_inner_outer_line.circle = [
- self.center_x,
- self.center_y,
- self.progress_round_size * 0.58,
- 0,
- 0,
- ]
- self._progress_line_end = 0
-
- def get_overlay_color(self) -> list:
- return self.overlay_color if self.selected else (0, 0, 0, 0)
-
- def get_progress_round_pos(self) -> tuple:
- return (
- (self.pos[0] + self.width / 2) - self.progress_round_size / 2,
- self.center_y - self.progress_round_size / 2,
- )
-
- def get_progress_round_size(self) -> tuple:
- return self.progress_round_size, self.progress_round_size
-
- def get_progress_round_color(self) -> tuple:
- return (
- self.theme_cls.primary_color
- if not self.progress_round_color
- else self.progress_round_color
- )
-
- def get_progress_line_color(self) -> tuple:
- return (
- self.theme_cls.primary_color[:-1] + [0.5]
- if not self.progress_round_color
- else self.progress_round_color[:-1] + [0.5]
- )
-
- def on_long_touch(self, *args) -> None:
- if not self.owner.get_selected():
- self._touch_long = True
- self._progress_animation = True
-
- def on_touch_up(self, touch):
- if self.collide_point(*touch.pos):
- if self._touch_long:
- self._touch_long = False
- return super().on_touch_up(touch)
-
- def on_touch_down(self, touch):
- if self.collide_point(*touch.pos):
- if self.selected:
- self.do_unselected_item()
- else:
- if self.owner.selected_mode:
- self.do_selected_item()
- return super().on_touch_down(touch)
-
- def on__touch_long(self, instance_selection_tem, touch_value: bool) -> None:
- if not touch_value:
- self.reset_progress_animation()
-
- def on__progress_animation(
- self, instance_selection_tem, touch_value: bool
- ) -> None:
- if touch_value:
- anim = Animation(_progress_line_end=360, d=1, t="in_out_quad")
- anim.bind(
- on_progress=self.do_animation_progress_line,
- on_complete=self.do_selected_item,
- )
- anim.start(self)
- self._instance_progress_inner_outer_color.rgba = (
- self.get_progress_line_color()
- )
- self._instance_progress_inner_circle_color.rgba = (
- self.get_progress_round_color()
- )
- else:
- self.reset_progress_animation()
-
-
-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.
- `on_unselected`
- Called when a list item is unselected.
- """
-
- selected_mode = BooleanProperty(False)
- """
- List item selection mode. If `True` when clicking on a list item, it will
- be selected.
-
- :attr:`selected_mode` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- icon = StringProperty("check")
- """
- Name of the icon with which the selected list item will be marked.
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'check'`.
- """
-
- icon_pos = ListProperty()
- """
- The position of the icon that will mark the selected list item.
-
- :attr:`icon_pos` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[]`.
- """
-
- icon_bg_color = ColorProperty([1, 1, 1, 1])
- """
- 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]`.
- """
-
- icon_check_color = ColorProperty([0, 0, 0, 1])
- """
- 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]`.
- """
-
- overlay_color = ColorProperty([0, 0, 0, 0.2])
- """
- 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]]`.
- """
-
- progress_round_size = NumericProperty(dp(46))
- """
- Size of the spinner for switching of `selected_mode` mode.
-
- :attr:`progress_round_size` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `dp(46)`.
- """
-
- progress_round_color = ColorProperty(None)
- """
- 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`.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.register_event_type("on_selected")
- self.register_event_type("on_unselected")
-
- def add_widget(self, widget, index=0, canvas=None):
- selection_icon = SelectionIconCheck(
- icon=self.icon,
- md_bg_color=self.icon_bg_color,
- icon_check_color=self.icon_check_color,
- )
- container = SelectionItem(
- size_hint=(1, None),
- height=widget.height,
- instance_item=widget,
- instance_icon=selection_icon,
- overlay_color=self.overlay_color,
- progress_round_size=self.progress_round_size,
- progress_round_color=self.progress_round_color,
- owner=self,
- )
- container.add_widget(widget)
-
- if not self.icon_pos:
- pos = (
- dp(12),
- container.height / 2 - selection_icon.height / 2,
- )
- else:
- pos = self.icon_pos
- selection_icon.pos = pos
- container.add_widget(selection_icon)
- return super().add_widget(container, index, canvas)
-
- def get_selected(self) -> bool:
- """Returns ``True`` if at least one item in the list is checked."""
-
- selected = False
- for item in self.children:
- if item.selected:
- selected = True
- break
- return selected
-
- def get_selected_list_items(self) -> list:
- """
- Returns a list of marked objects:
-
- [, ...]
- """
-
- selected_list_items = []
- for item in self.children:
- if item.selected:
- selected_list_items.append(item)
- return selected_list_items
-
- def unselected_all(self) -> None:
- for item in self.children:
- item.do_unselected_item()
- self.selected_mode = False
-
- def selected_all(self) -> None:
- for item in self.children:
- item.do_selected_item()
- self.selected_mode = True
-
- def on_selected(self, *args):
- """Called when a list item is selected."""
-
- if not self.selected_mode:
- self.selected_mode = True
-
- def on_unselected(self, *args):
- """Called when a list item is unselected."""
-
- self.selected_mode = self.get_selected()
diff --git a/kivymd/uix/selectioncontrol/selectioncontrol.kv b/kivymd/uix/selectioncontrol/selectioncontrol.kv
index 3d3b015fe..e25b81d2b 100644
--- a/kivymd/uix/selectioncontrol/selectioncontrol.kv
+++ b/kivymd/uix/selectioncontrol/selectioncontrol.kv
@@ -1,12 +1,26 @@
-#:import get_color_from_hex kivy.utils.get_color_from_hex
-#:import colors kivymd.color_definitions.colors
-
-
+ _current_color:
+ ( \
+ ( \
+ self.theme_cls.primaryColor \
+ if not self.color_active else self.color_active \
+ ) \
+ if self.active else \
+ ( \
+ self.theme_cls.onSurfaceVariantColor \
+ if not self.color_inactive else self.color_inactive \
+ ) \
+ ) \
+ if not self.disabled else \
+ ( \
+ self.theme_cls.onSurfaceColor[:-1] \
+ + [self.checkbox_opacity_value_disabled_container] \
+ if not self.color_disabled else self.color_disabled \
+ )
+
canvas:
- Clear
Color:
- rgba: self.color
+ rgba: self._current_color
Rectangle:
texture: self.texture
size: self.texture_size
@@ -14,9 +28,22 @@
int(self.center_x - self.texture_size[0] / 2.), \
int(self.center_y - self.texture_size[1] / 2.)
- color: self._current_color
+ # FIXME: Move to `kivymd/uix/behaviors/state_layer_behavior.py`
+ canvas.after:
+ # Clear
+ Color
+ rgba: self.state_layer_color
+ RoundedRectangle:
+ size: self.width + dp(20), self.height + dp(20)
+ pos: self.x - self.width / 2 + dp(2), self.y - self.height / 2 + dp(2)
+ radius: [(self.width + dp(20)) / 2, ]
+
+ theme_text_color: "Custom"
+ text_color: self._current_color
halign: "center"
valign: "middle"
+ size_hint: None, None
+ size: "22dp", "22dp"
@@ -26,115 +53,103 @@
ThumbIcon:
id: icon
font_size: "16sp"
- theme_text_color: "Custom"
pos_hint: {"center_x": .5, "center_y": .5}
+ theme_text_color: "Custom"
+ text_color:
+ ( \
+ ( \
+ root.parent.icon_active_color \
+ if root.parent.icon_active_color \
+ else self.theme_cls.onPrimaryContainerColor \
+ ) \
+ if root.parent.icon_active and root.parent.active else \
+ ( \
+ root.parent.icon_inactive_color \
+ if root.parent.icon_inactive_color \
+ else self.theme_cls.surfaceContainerHighestColor \
+ ) \
+ ) \
+ if not root.parent.disabled else \
+ ( \
+ self.theme_cls.onSurfaceColor[:-1] \
+ + [root.parent.switch_opacity_value_disabled_icon] \
+ if root.parent.icon_active else \
+ self.theme_cls.surfaceContainerHighestColor[:-1] \
+ + [root.parent.switch_opacity_value_disabled_icon] \
+ )
- canvas.before:
- Color:
- rgba:
- ( \
- self.track_color_disabled \
- if self.track_color_disabled else \
- self.theme_cls.disabled_hint_text_color) \
- if self.disabled else \
- ( \
- ( \
- self.track_color_active \
- if self.track_color_active else \
- self.theme_cls.primary_color[:-1] + [.5]) \
- if self.active else \
- (self.track_color_inactive \
- if self.track_color_inactive else \
- self.theme_cls.disabled_hint_text_color) \
- )
- RoundedRectangle:
- size:
- (self.width + dp(14), dp(28)) \
- if root.widget_style == "ios" else \
- ( \
- (self.width - dp(8), dp(16)) \
- if app.theme_cls.material_style == "M2" else \
- (self.width + dp(16), dp(32)) \
- )
- pos:
- (self.x - dp(2), self.center_y - dp(14)) \
- if root.widget_style == "ios" else \
- (self.x + dp(8), self.center_y - dp(8))
- radius:
- [dp(14)] \
- if root.widget_style == "ios" else \
- [dp(7) if app.theme_cls.material_style == "M2" else dp(16)]
- Color:
- rgba:
- ( \
- self.theme_cls.disabled_hint_text_color \
- if not root.active else (0, 0, 0, 0) \
- ) \
- if root.widget_style == "ios" \
- or app.theme_cls.material_style == "M3" else \
- (0, 0, 0, 0)
- SmoothLine:
- width:
- 1 \
- if root.widget_style == "ios" \
- or app.theme_cls.material_style == "M2" else \
- 1.4
- rounded_rectangle:
- ( \
- self.x - dp(2), self.center_y - dp(14), self.width + dp(14), \
- dp(28), dp(14), dp(14), dp(14), dp(14), dp(28) \
- ) \
- if root.widget_style == "ios" else \
- ( \
- (1, 1, 1, 1, 1, 1, 1, 1, 1) \
- if app.theme_cls.material_style == "M2" else \
- ( \
- self.x + dp(8), self.center_y - dp(8), self.width + dp(16), \
- dp(32), dp(16), dp(16), dp(16), dp(16), dp(32) \
- )
- )
+ size_hint: None, None
+ size: dp(52), dp(32)
+ radius: [self.height / 2, ]
+ md_bg_color:
+ ( \
+ self.track_color_disabled \
+ if self.track_color_disabled else \
+ ( \
+ self.theme_cls.onSurfaceColor[:-1] \
+ + [self.switch_opacity_value_disabled_container] \
+ if self.active else self.theme_cls.surfaceContainerHighestColor[:-1] \
+ + [self.switch_opacity_value_disabled_container] \
+ ) \
+ ) \
+ if self.disabled else \
+ ( \
+ ( \
+ self.track_color_active \
+ if self.track_color_active else \
+ self.theme_cls.primaryColor \
+ ) \
+ if self.active else \
+ (self.track_color_inactive \
+ if self.track_color_inactive else \
+ self.theme_cls.surfaceContainerHighestColor \
+ ) \
+ )
+ line_color:
+ ( \
+ ( \
+ self.theme_cls.outlineColor if not self.active else self.md_bg_color
+ ) \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] \
+ + [self.switch_opacity_value_disabled_line] \
+ ) \
+ if self.theme_line_color == "Primary" else \
+ self._line_color if not self.disabled else \
+ ( \
+ self.line_color_disabled \
+ if self.line_color_disabled else \
+ self._line_color \
+ )
Thumb:
id: thumb
size_hint: None, None
size: dp(24), dp(24)
- elevation:
- (2.5 if root.active else 1) \
- if app.theme_cls.material_style != "M3" else \
- 0
- pos:
- (root.pos[0] + root._thumb_pos[0], root.pos[1] + root._thumb_pos[1]) \
- if root.widget_style == "ios" \
- or app.theme_cls.material_style == "M2" else \
- ( \
- root.pos[0] + self.width + root._thumb_pos[0], \
- root.pos[1] + (root.height / 2 - self.height / 2) + root._thumb_pos[1] \
- )
- _no_ripple_effect:
- True \
- if app.theme_cls.material_style == "M3" \
- and root.widget_style != "ios" else \
- False
+ _no_ripple_effect: not root.ripple_effect
md_bg_color:
( \
root.thumb_color_disabled \
if root.thumb_color_disabled else \
- get_color_from_hex(colors["Gray"]["800"]) \
+ ( \
+ root.theme_cls.surfaceColor \
+ if root.active else root.theme_cls.onSurfaceColor[:-1] \
+ + [root.switch_thumb_opacity_value_disabled_container] \
+ ) \
) \
if root.disabled else \
( \
(root.thumb_color_active \
if root.thumb_color_active else \
- root.theme_cls.primary_color \
+ root.theme_cls.onPrimaryColor \
) \
if root.active else \
( \
root.thumb_color_inactive \
if root.thumb_color_inactive else \
- get_color_from_hex(colors["Gray"]["50"] \
- ) \
+ self.theme_cls.outlineColor \
) \
)
on_touch_down:
@@ -143,3 +158,7 @@
on_touch_up:
if self.collide_point(*args[1].pos) and not root.disabled: \
setattr(root, "active", not root.active)
+ pos:
+ root.pos[0] + (self.width / 2) + root._thumb_pos[0] \
+ + dp(6 if root.icon_inactive else 0), \
+ root.pos[1] + (root.height / 2 - self.height / 2) + root._thumb_pos[1]
\ No newline at end of file
diff --git a/kivymd/uix/selectioncontrol/selectioncontrol.py b/kivymd/uix/selectioncontrol/selectioncontrol.py
index 6bcefe679..1effec528 100755
--- a/kivymd/uix/selectioncontrol/selectioncontrol.py
+++ b/kivymd/uix/selectioncontrol/selectioncontrol.py
@@ -152,34 +152,24 @@ def build(self):
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"
+ padding_x: "12dp"
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"
+ md_bg_color: self.theme_cls.backgroundColor
MDBoxLayout:
orientation: "vertical"
adaptive_height: True
padding: "12dp", "36dp", 0, 0
+ spacing: "12dp"
CheckItem:
text: "Recieve emails"
@@ -189,6 +179,7 @@ def build(self):
orientation: "vertical"
adaptive_height: True
padding: "24dp", 0, 0, 0
+ spacing: "12dp"
CheckItem:
text: "Daily"
@@ -213,7 +204,6 @@ class CheckItem(MDBoxLayout):
class Example(MDApp):
def build(self):
- self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Teal"
return Builder.load_string(KV)
@@ -270,7 +260,7 @@ def build(self):
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
-from kivy.metrics import dp, sp
+from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
ColorProperty,
@@ -278,18 +268,12 @@ def build(self):
StringProperty,
)
from kivy.uix.behaviors import ToggleButtonBehavior
-from kivy.uix.floatlayout import FloatLayout
from kivymd import uix_path
-from kivymd.theming import ThemableBehavior
-from kivymd.uix.behaviors import (
- CircularRippleBehavior,
- CommonElevationBehavior,
- ScaleBehavior,
-)
+from kivymd.uix.behaviors import CircularRippleBehavior, ScaleBehavior
+from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
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"),
@@ -299,13 +283,18 @@ def build(self):
class MDCheckbox(
- CircularRippleBehavior, ScaleBehavior, ToggleButtonBehavior, MDIcon
+ CircularRippleBehavior,
+ ScaleBehavior,
+ ToggleButtonBehavior,
+ MDIcon,
):
"""
Checkbox class.
For more information, see in the
+ :class:`~kivymd.uix.behaviors.StateLayerBehavior` and
:class:`~kivymd.uix.behaviors.CircularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.ScaleBehavior` and
:class:`~kivy.uix.behaviors.ToggleButtonBehavior` and
:class:`~kivymd.uix.label.MDIcon`
classes documentation.
@@ -394,26 +383,30 @@ class MDCheckbox(
and defaults to `None`.
"""
- disabled_color = ColorProperty(None)
+ color_disabled = ColorProperty(None)
"""
Color in (r, g, b, a) or string format when the checkbox is in the disabled state.
- .. code-block:: kv
+ .. versionadded:: 2.0.0
+ Use :attr:`color_disabled` instead.
- MDCheckbox:
- disabled_color: "lightgrey"
- disabled: True
- active: True
+ :attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-disabled-color.png
- :align: center
+ # Deprecated property.
+
+ disabled_color = ColorProperty(None)
+ """
+ Color in (r, g, b, a) or string format when the checkbox is in the disabled state.
+
+ .. deprecated:: 2.0.0
+ Use :attr:`color_disabled` instead.
:attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- # Deprecated property.
-
selected_color = ColorProperty(None, deprecated=True)
"""
Color in (r, g, b, a) or string format when the checkbox is in the active state.
@@ -446,10 +439,6 @@ def __init__(self, **kwargs):
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
- self.color_inactive = self.theme_cls.secondary_text_color
- self.disabled_color = self.theme_cls.divider_color
- self._current_color = self.color_inactive
self.check_anim_out.bind(
on_complete=lambda *x: self.check_anim_in.start(self)
)
@@ -459,38 +448,12 @@ def __init__(self, **kwargs):
radio_icon_normal=self.update_icon,
radio_icon_down=self.update_icon,
group=self.update_icon,
- color_active=self.update_color,
- color_inactive=self.update_color,
- disabled_color=self.update_color,
- disabled=self.update_color,
- state=self.update_color,
- )
- self.theme_cls.bind(
- theme_style=self.update_primary_color,
- primary_color=self.update_primary_color,
)
self.update_icon()
- 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
- else:
- self.color = self.disabled_color
- else:
- self.color_active = value
def update_icon(self, *args) -> None:
"""
- Called when the values of
+ Fired when the values of
:attr:`checkbox_icon_normal` and
:attr:`checkbox_icon_down` and
:attr:`radio_icon_normal` and
@@ -513,26 +476,22 @@ def update_icon(self, *args) -> None:
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.
- """
+ 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
- if self.disabled:
- self._current_color = self.disabled_color
- elif self.state == "down":
- self._current_color = self.color_active
- else:
- self._current_color = self.color_inactive
+ 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_state(self, *args) -> None:
- """Called when the values of :attr:`state` change."""
+ """Fired when the values of :attr:`state` change."""
if self.state == "down":
self.check_anim_in.cancel(self)
@@ -549,7 +508,7 @@ def on_state(self, *args) -> None:
self.active = False
def on_active(self, *args) -> None:
- """Called when the values of :attr:`active` change."""
+ """Fired when the values of :attr:`active` change."""
self.state = "down" if self.active else "normal"
@@ -563,20 +522,6 @@ def on_active(self, *args) -> None:
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":
@@ -597,7 +542,7 @@ class ThumbIcon(MDIcon):
"""
-class Thumb(CommonElevationBehavior, CircularRippleBehavior, MDFloatLayout):
+class Thumb(CircularRippleBehavior, MDFloatLayout):
"""Implements a thumb for the :class:`~MDSwitch` widget."""
def _set_ellipse(self, instance, value):
@@ -614,13 +559,34 @@ def _set_ellipse(self, instance, value):
)
-class MDSwitch(ThemableBehavior, FloatLayout):
+class MDSwitch(StateLayerBehavior, MDFloatLayout):
"""
Switch class.
For more information, see in the
- :class:`~kivymd.theming.ThemableBehavior` and
- :class:`~kivy.uix.floatlayout.FloatLayout` classes documentation.
+ :class:`~kivymd.uix.behaviors.StateLayerBehavior` and
+ :class:`~kivymd.uix.floatlayout.MDFloatLayout`
+ classes documentation.
+ """
+
+ md_bg_color_disabled = ColorProperty(None)
+ """
+ The background color in (r, g, b, a) or string format of the switch when
+ the switch is disabled.
+
+ :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
+ ripple_effect = BooleanProperty(True)
+ """
+ Allows or does not allow the ripple effect when activating/deactivating
+ the switch.
+
+ .. versionadded:: 2.0.0
+
+ :attr:`ripple_effect` is a :class:`~kivy.properties.BooleanProperty`
+ and defaults to `True`.
"""
active = BooleanProperty(False)
@@ -818,20 +784,27 @@ class MDSwitch(ThemableBehavior, FloatLayout):
and default to `None`.
"""
+ line_color_disabled = ColorProperty(None)
+ """
+ The color of the outline in the disabled state
+
+ .. versionadded:: 2.0.0
+
+ :attr:`line_color_disabled` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
+
_thumb_pos = ListProperty([0, 0])
+ _line_color = ColorProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(icon_active=self.set_icon, icon_inactive=self.set_icon)
- self.size_hint = (None, None)
- self.size = (dp(36), dp(48))
- Clock.schedule_once(self._check_style)
- Clock.schedule_once(lambda x: self._update_thumb_pos(animation=False))
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
+ Fired when the values of
:attr:`icon_active` and :attr:`icon_inactive` change.
"""
@@ -841,43 +814,29 @@ def set_icon(*args):
Clock.schedule_once(set_icon, 0.2)
+ def on_line_color(self, instance, value) -> None:
+ """Fired when the values of :attr:`line_color` change."""
+
+ if not self.disabled:
+ self._line_color = value
+
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 = (
- (
- (dp(16), dp(16))
- if not self.icon_inactive
- else (dp(24), dp(24))
- )
- if not active_value
- else (dp(24), dp(24))
- )
- icon = "blank"
- color = (0, 0, 0, 0)
-
- if self.icon_active and active_value:
- icon = self.icon_active
- color = (
- self.icon_active_color
- if self.icon_active_color
- else self.theme_cls.text_color
- )
- elif self.icon_inactive and not active_value:
- icon = self.icon_inactive
- color = (
- self.icon_inactive_color
- if self.icon_inactive_color
- else self.theme_cls.text_color
- )
-
- Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb)
- Animation(color=color, t="out_quad", d=0.2).start(
- self.ids.thumb.ids.icon
- )
- self.set_icon(self, icon)
+ """Fired when the values of :attr:`active` change."""
+
+ size = (
+ ((dp(16), dp(16)) if not self.icon_inactive else (dp(24), dp(24)))
+ if not active_value
+ else (dp(24), dp(24))
+ )
+ icon = "blank"
+
+ if self.icon_active and active_value:
+ icon = self.icon_active
+ elif self.icon_inactive and not active_value:
+ icon = self.icon_inactive
+ Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb)
+ self.set_icon(self, icon)
self._update_thumb_pos()
# FIXME: If you move the cursor from the switch during the
@@ -885,63 +844,28 @@ def on_active(self, instance_switch, active_value: bool) -> None:
# the previous size does not work. The following code fixes this.
def on_thumb_down(self) -> None:
"""
- Called at the on_touch_down event of the :class:`~Thumb` object.
+ Fired at the on_touch_down event of the :class:`~Thumb` object.
Indicates the state of the switch "on/off" by an animation of
increasing the size of the thumb.
"""
- if self.widget_style != "ios" and self.theme_cls.material_style == "M3":
- if self.active:
- size = (dp(28), dp(28))
- pos = (
- self.ids.thumb.pos[0] - dp(2),
- self.ids.thumb.pos[1] - dp(1.8),
- )
- else:
- size = (dp(26), dp(26))
- pos = (
- (
- self.ids.thumb.pos[0] - dp(5),
- self.ids.thumb.pos[1] - dp(5),
- )
- if not self.icon_inactive
- else (
- self.ids.thumb.pos[0] + dp(1),
- self.ids.thumb.pos[1] - dp(1),
- )
- )
- Animation(size=size, pos=pos, t="out_quad", d=0.2).start(
- self.ids.thumb
- )
+ if self.active:
+ size = (dp(28), dp(28))
+ else:
+ size = (dp(24), dp(24))
+
+ Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb)
def _update_thumb_pos(self, *args, animation=True):
if self.active:
_thumb_pos = (
- self.width
- - (
- dp(14)
- if self.widget_style == "ios"
- or self.theme_cls.material_style == "M2"
- else dp(28)
- ),
- self.height / 2
- - (
- dp(12)
- if self.widget_style == "ios"
- or self.theme_cls.material_style == "M2"
- else dp(16)
- ),
+ self.width - dp(46 if self.icon_inactive else 40),
+ self.height / 2 - dp(16),
)
else:
_thumb_pos = (
0 if not self.icon_inactive else dp(-14),
- self.height / 2
- - (
- dp(12)
- if self.widget_style == "ios"
- or self.theme_cls.material_style == "M2"
- else dp(16)
- ),
+ self.height / 2 - dp(16),
)
Animation.cancel_all(self, "_thumb_pos")
@@ -951,7 +875,3 @@ def _update_thumb_pos(self, *args, animation=True):
)
else:
self._thumb_pos = _thumb_pos
-
- def _check_style(self, *args):
- if self.widget_style == "ios" or self.theme_cls.material_style == "M2":
- self.set_icon(self, "")
diff --git a/kivymd/uix/slider/__init__.py b/kivymd/uix/slider/__init__.py
index a4edb11df..0d97a4770 100644
--- a/kivymd/uix/slider/__init__.py
+++ b/kivymd/uix/slider/__init__.py
@@ -1 +1 @@
-from .slider import MDSlider # NOQA F401
+from .slider import MDSlider, MDSliderHandle, MDSliderValueLabel # NOQA F401
diff --git a/kivymd/uix/slider/slider.kv b/kivymd/uix/slider/slider.kv
index 53323811b..3eb317954 100644
--- a/kivymd/uix/slider/slider.kv
+++ b/kivymd/uix/slider/slider.kv
@@ -1,169 +1,216 @@
-#:import Thumb kivymd.uix.selectioncontrol.Thumb
-#:import get_color_from_hex kivy.utils.get_color_from_hex
-#:import colors kivymd.color_definitions.colors
-
-
-
-
-
canvas:
Clear
+ # Inactive track.
Color:
rgba:
( \
- self.track_color_disabled \
- if self.track_color_disabled else \
- self.theme_cls.disabled_hint_text_color) \
- if self.disabled else \
+ self.theme_cls.surfaceVariantColor \
+ if not self.track_inactive_color else \
+ self.track_inactive_color \
+ ) \
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + [.12]
+ Rectangle:
+ size:
( \
+ self.width - self.padding * 2, \
+ self.track_inactive_width \
+ ) \
+ if self.orientation == "horizontal" else \
( \
- self.track_color_active \
- if self.track_color_active else \
+ self.track_inactive_width, \
+ self.height - self.padding * 2 \
+ )
+ pos:
( \
- get_color_from_hex(colors["Gray"]["400"]) \
- if app.theme_cls.theme_style == "Light" else \
- (1, 1, 1, .3) \
- ) \
+ self.x + self.padding, \
+ self.center_y - self.track_inactive_width \
) \
- if self.active else \
+ if self.orientation == "horizontal" else \
( \
- self.track_color_inactive \
- if self.track_color_inactive else \
+ self.center_x - self.track_inactive_width, \
+ self.y + self.padding \
+ )
+
+ # Active track.
+ Color:
+ rgba:
( \
- self.theme_cls.disabled_hint_text_color \
- if app.theme_cls.theme_style == "Light" else \
- get_color_from_hex(colors["Gray"]["800"]) \
+ self.theme_cls.primaryColor \
+ if not self.track_active_color else \
+ self.track_active_color \
) \
- ) \
- )
+ if not self.disabled else \
+ self.theme_cls.onSurfaceColor[:-1] + [.38]
Rectangle:
size:
- (self.width - self.padding * 2 - self._offset[0], dp(4)) if \
- self.orientation == "horizontal" \
- else (dp(4),self.height - self.padding*2 - self._offset[1])
+ ( \
+ (self.width - self.padding * 2) * self.value_normalized, \
+ self.track_active_width \
+ ) \
+ if self.orientation == "horizontal" else \
+ ( \
+ self.track_active_width, \
+ (self.height - self.padding * 2) * self.value_normalized \
+ )
pos:
- (self.x + self.padding + self._offset[0], self.center_y - dp(4)) \
+ (self.x + self.padding, self.center_y - self.track_active_width) \
if self.orientation == "horizontal" else \
- (self.center_x - dp(4), self.y + self.padding + self._offset[1])
+ (self.center_x - self.track_active_width, self.y + self.padding)
- # If 0 draw circle
+ # Points of the inactive track when a step is used.
Color:
rgba:
- (0, 0, 0, 0) if not self._is_off else \
- ( \
- self.track_color_disabled \
- if self.disabled and self.track_color_disabled else \
( \
- self.theme_cls.disabled_hint_text_color \
- if app.theme_cls.theme_style == "Light" else \
- get_color_from_hex(colors["Gray"]["800"]) \
+ self.theme_cls.onPrimaryColor \
+ if not self.track_active_step_point_color else \
+ self.track_active_step_point_color \
) \
- )
- Line:
- width: 2
- circle:
- (self.x + self.padding + dp(3), self.center_y - dp(2), 8 \
- if self.active else 6 ) if self.orientation == "horizontal" \
- else (self.center_x - dp(2), self.y + self.padding + dp(3), 8 \
- if self.active else 6)
+ if not self.disabled else \
+ self.theme_cls.onPrimaryColor[:-1] + [.38]
+ Point:
+ points: self._inactive_points
+ pointsize: self.step_point_size
+ # Points of the active track when a step is used.
Color:
rgba:
- (0, 0, 0, 0) if self._is_off \
- else \
- ( \
( \
- self.color if self.color else \
- app.theme_cls.primary_color \
+ self.theme_cls.primaryColor \
+ if not self.track_inactive_step_point_color else \
+ self.track_inactive_step_point_color \
) \
if not self.disabled else \
- ( \
- self.track_color_disabled \
- if self.track_color_disabled else \
- ( \
- (0, 0, 0, .26) \
- if app.theme_cls.theme_style == "Light" else (1, 1, 1, .3) \
- ) \
- ) \
- )
- Rectangle:
- size:
- ((self.width - self.padding * 2) * self.value_normalized, sp(4)) \
- if root.orientation == "horizontal" else \
- (sp(4), (self.height - self.padding * 2) * self.value_normalized)
- pos:
- (self.x + self.padding, self.center_y - dp(4)) \
- if self.orientation == "horizontal" else \
- (self.center_x - dp(4), self.y + self.padding)
+ self.theme_cls.onSurfaceColor[:-1] + [.38]
+ Point:
+ points: self._active_points
+ pointsize: self.step_point_size
+
+ size_hint_y: None
+ height:
+ handle_container.children[0].height \
+ if handle_container.children else 0
- Thumb:
- id: thumb
+ # Label container.
+ BoxLayout:
+ id: value_container
size_hint: None, None
- size:
- (dp(12), dp(12)) if root.disabled else ((dp(24), dp(24)) \
- if root.active else \
- (dp(16), dp(16)))
+ size: self.minimum_size
+ pos:
+ handle_container.center_x - self.width / 2, \
+ root._value_container_y
+
+ # Handle container.
+ BoxLayout:
+ id: handle_container
+ size_hint: None, None
+ size: self.minimum_size
pos:
- (root.value_pos[0] - dp(8), root.center_y - thumb.height / 2 - dp(2)) \
- if root.orientation == "horizontal" \
- else (root.center_x - thumb.width / 2 - dp(2), \
- root.value_pos[1] - dp(8))
- md_bg_color:
- (0, 0, 0, 0) if root._is_off else \
- ( \
- ( \
- root.thumb_color_disabled \
- if root.thumb_color_disabled else \
- get_color_from_hex(colors["Gray"]["800"]) \
- ) \
- if root.disabled else \
( \
- (root.thumb_color_active \
- if root.thumb_color_active else \
- root.theme_cls.primary_color \
+ root.value_pos[0] - dp(0), \
+ root.center_y - self.height / 2 - root.track_active_width / 2 \
) \
- if root.active else \
+ if root.orientation == "horizontal" else \
( \
- root.thumb_color_inactive \
- if root.thumb_color_inactive else \
- root.theme_cls.primary_color \
- ) \
- ) \
+ root.center_x - self.width / 2 - root.track_active_width / 2, \
+ root.value_pos[1] - root.track_active_width / 2 \
)
- elevation: 0 if root._is_off else (3 if root.active else 1)
- HintBoxContainer:
- id: hint_box
- size_hint: None, None
- md_bg_color: root.hint_bg_color if root.hint_bg_color else [0, 0, 0, 0]
- elevation: 1.5
- opacity: 1 if root.active else 0
- radius: root.hint_radius
- padding: "6dp", "6dp", "6dp", "8dp"
- shadow_color:
- ([0, 0, 0, 0.6] if root.hint_bg_color else [0, 0, 0, 0]) \
- if root.active else \
- [0, 0, 0, 0]
- size:
- lbl_value.width + self.padding[0] * 2, \
- lbl_value.height + self.padding[0]
- pos:
- (root.value_pos[0] - dp(9), root.center_y - hint_box.height / 2 + dp(30)) \
- if root.orientation == "horizontal" else \
- (root.center_x - hint_box.width / 2 + dp(30), root.value_pos[1] - dp(8))
-
- MDLabel:
- id: lbl_value
- font_style: "Caption"
- halign: "center"
- theme_text_color: "Custom"
- -text_size: None, None
- adaptive_size: True
- pos_hint: {"center_x": .5, "center_y": .5}
- text_color:
- app.theme_cls.primary_color \
- if not root.hint_text_color else root.hint_text_color
- text:
- str(root.value) \
- if isinstance(root.step, float) else str(int(root.value))
+
+
+ _state_layer: state_layer
+ size_hint: None, None
+ radius: self.radius
+
+ canvas:
+ Color:
+ rgba:
+ ( \
+ self.theme_cls.primaryColor \
+ if self.theme_bg_color == "Primary" else \
+ self.md_bg_color \
+ ) \
+ if not self.disabled else self.theme_cls.onSurfaceColor
+ RoundedRectangle:
+ radius: self.radius
+ size: self.size
+ pos: self.pos
+
+ MDSliderHandleStateLayer:
+ id: state_layer
+
+ canvas.before:
+ Color:
+ rgba:
+ ( \
+ app.theme_cls.primaryColor \
+ if not root.state_layer_color else \
+ root.state_layer_color \
+ )[:-1] + [0.38]
+ RoundedRectangle:
+ radius: [root.state_layer_size[0] / 2, ]
+ size: root.state_layer_size
+ pos:
+ root.x - root.state_layer_size[0] / 4, \
+ root.y - root.state_layer_size[1] / 4
+
+
+
+ canvas:
+ Color:
+ rgba: app.theme_cls.primaryColor
+ RoundedRectangle:
+ radius: [self.size[0] / 2, ]
+ pos: self.pos
+ size: self.size
+
+ Triangle:
+ points:
+ [ \
+ self.x + 18, self.y - 6, \
+ self.x + 32, self.y + 8, \
+ self.x + 2, self.y + 8 \
+ ]
+
+ # Label texture.
+ Color:
+ group: "md-slider-label-value-color"
+ Rectangle:
+ group: "md-slider-label-value-rect"
+ texture:
+ self._slider._value_label.texture \
+ if self._slider and self._slider._value_label else \
+ None
+ pos:
+ ( \
+ ( \
+ self._slider.value_pos[0] - (self._slider._value_label.texture_size[0] / 2) + dp(10), \
+ self._slider.center_y + dp(28) \
+ ) \
+ if self._slider.orientation == "horizontal" else \
+ ( \
+ self._slider.center_x - self.width / 2, \
+ self._slider.value_pos[1] - dp(0) \
+ ) \
+ ) \
+ if self._slider and self._slider._value_label else (0, 0)
+ size:
+ self._slider._value_label.texture_size \
+ if self._slider and self._slider._value_label else (0, 0)
+
+ size_hint: None, None
+ # opacity: 0
+ size:
+ self._slider._value_label.size \
+ if self._slider and self._slider._value_label else \
+ (0, 0)
+
+
+
+ font_style: "Label"
+ role: "medium"
+ size_hint: None, None
+ theme_text_color: "Custom"
+ text_color: self.theme_cls.onPrimaryColor
+ halign: "center"
diff --git a/kivymd/uix/slider/slider.py b/kivymd/uix/slider/slider.py
index f0824ae3e..602186a72 100644
--- a/kivymd/uix/slider/slider.py
+++ b/kivymd/uix/slider/slider.py
@@ -4,29 +4,81 @@
.. seealso::
- `Material Design spec, Sliders `_
+ `Material Design spec, Sliders `_
.. rubric:: Sliders allow users to make selections from a range of values.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider.png
:align: center
+
+- Sliders should present the full range of choices that are available
+- Two types: continuous and discrete
+- The slider should immediately reflect any input made by a user
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliders-types.png
+ :align: center
+
+1. Continuous slider
+2. Discrete slider
+
+Usage
+-----
+
+.. code-block:: python
+
+ MDSlider(
+ MDSliderHandle(
+ ),
+ MDSliderValueLabel(
+ ),
+ step=10,
+ value=50,
+ )
+
+.. code-block:: kv
+
+ MDSlider:
+ step: 10
+ value: 50
+
+ MDSliderHandle:
+
+ MDSliderValueLabel:
+
+
+Anatomy
+-------
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-anatomy.png
+ :align: center
"""
-__all__ = ("MDSlider",)
+__all__ = ("MDSlider", "MDSliderHandle", "MDSliderValueLabel")
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,
VariableListProperty,
+ StringProperty,
+ NumericProperty,
+ ObjectProperty,
+ ColorProperty,
)
from kivy.uix.slider import Slider
+from kivy.uix.widget import Widget
+from kivymd.uix.label import MDLabel
+from kivymd.uix.behaviors import (
+ ScaleBehavior,
+ DeclarativeBehavior,
+ BackgroundColorBehavior,
+)
+from kivymd.uix.behaviors.focus_behavior import FocusBehavior
from kivymd import uix_path
from kivymd.theming import ThemableBehavior
@@ -36,282 +88,488 @@
Builder.load_string(kv_file.read())
-class MDSlider(ThemableBehavior, Slider):
- """
- Class for creating a Slider widget. See in the
- :class:`~kivy.uix.slider.Slider` class documentation.
+class MDSlider(DeclarativeBehavior, ThemableBehavior, Slider):
"""
+ Slider class.
- active = BooleanProperty(False)
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivy.uix.slider.Slider`
+ classes documentation.
"""
- If the slider is clicked.
- :attr:`active` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
+ track_active_width = NumericProperty(dp(4))
"""
+ Width of the active track.
+
+ .. versionadded:: 2.0.0
- color = ColorProperty(None)
+ :attr:`track_active_width` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `dp(4)`.
"""
- Color slider in (r, g, b, a) or string format.
- .. code-block:: kv
+ track_inactive_width = NumericProperty(dp(4))
+ """
+ Width of the inactive track.
- MDSlider
- color: "red"
+ .. versionadded:: 2.0.0
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-color.png
- :align: center
+ :attr:`track_inactive_width` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `dp(4)`.
+ """
- :attr:`color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
+ step_point_size = NumericProperty(dp(1))
"""
+ Step point size.
- hint = BooleanProperty(True)
+ .. versionadded:: 2.0.0
+
+ :attr:`step_point_size` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `dp(1)`.
"""
- If True, then the current value is displayed above the slider.
- .. code-block:: kv
+ track_active_color = ColorProperty(None)
+ """
+ Color of the active track.
- MDSlider
- hint: True
+ .. versionadded:: 2.0.0
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint.png
- :align: center
+ .. versionchanged:: 2.0.0
+ Rename from `track_color_active` to `track_active_color`
- :attr:`hint` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
+ :attr:`track_active_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- hint_bg_color = ColorProperty(None)
+ track_active_step_point_color = ColorProperty(None)
"""
- Hint rectangle color in (r, g, b, a) or string format.
-
- .. code-block:: kv
+ Color of step points on active track.
- MDSlider
- hint: True
- hint_bg_color: "red"
+ .. versionadded:: 2.0.0
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-bg-color.png
- :align: center
+ :attr:`track_active_step_point_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
- :attr:`hint_bg_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0]`.
+ track_inactive_step_point_color = ColorProperty(None)
"""
+ Color of step points on inactive track.
+
+ .. versionadded:: 2.0.0
- hint_text_color = ColorProperty(None)
+ :attr:`track_inactive_step_point_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
"""
- Hint text color in in (r, g, b, a) or string format.
- .. code-block:: kv
+ track_inactive_color = ColorProperty(None)
+ """
+ Color of the inactive track.
- MDSlider
- hint: True
- hint_bg_color: "red"
- hint_text_color: "white"
+ .. versionadded:: 2.0.0
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-text-color.png
- :align: center
+ .. versionchanged:: 2.0.0
+ Rename from `track_color_inactive` to `track_inactive_color`
- :attr:`hint_text_color` is an :class:`~kivy.properties.ColorProperty`
+ :attr:`track_active_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- hint_radius = VariableListProperty([dp(4), dp(4), dp(4), dp(4)])
+ value_container_show_anim_duration = NumericProperty(0.2)
"""
- Hint radius.
+ Duration of the animation opening of the label value.
- .. code-block:: kv
+ .. versionadded:: 2.0.0
- MDSlider
- hint: True
- hint_bg_color: "red"
- hint_text_color: "white"
- hint_radius: [6, 0, 6, 0]
+ :attr:`value_container_show_anim_duration` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
+ """
+
+ value_container_hide_anim_duration = NumericProperty(0.2)
+ """
+ Duration of closing the animation of the label value.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-radius.png
- :align: center
+ .. versionadded:: 2.0.0
- :attr:`hint_radius` is an :class:`~kivy.properties.VariableListProperty`
- and defaults to `[dp(4), dp(4), dp(4), dp(4)]`.
+ :attr:`value_container_hide_anim_duration` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
"""
- thumb_color_active = ColorProperty(None)
+ value_container_show_anim_transition = StringProperty("out_circ")
"""
- The color in (r, g, b, a) or string format of the thumb when the slider is active.
+ The type of the opening animation of the label value.
- .. versionadded:: 1.0.0
+ .. versionadded:: 2.0.0
- .. code-block:: kv
+ :attr:`value_container_show_anim_transition` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_circ'`.
+ """
- MDSlider
- thumb_color_active: "red"
+ value_container_hide_anim_transition = StringProperty("out_circ")
+ """
+ The type of the closing animation of the label value.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-active.png
- :align: center
+ .. versionadded:: 2.0.0
- :attr:`thumb_color_active` is an :class:`~kivy.properties.ColorProperty`
- and default to `None`.
+ :attr:`value_container_hide_anim_transition` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_circ'`.
"""
- thumb_color_inactive = ColorProperty(None)
+ handle_anim_transition = StringProperty("out_circ")
"""
- The color in (r, g, b, a) or string format of the thumb when the slider is inactive.
+ Handle animation type.
- .. versionadded:: 1.0.0
+ .. versionadded:: 2.0.0
- .. code-block:: kv
+ :attr:`handle_anim_transition` is an :class:`~kivy.properties.StringProperty`
+ and defaults to `'out_circ'`.
+ """
- MDSlider
- thumb_color_inactive: "red"
+ handle_anim_duration = NumericProperty(0.2)
+ """
+ Handle animation duration.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-inactive.png
- :align: center
+ .. versionadded:: 2.0.0
- :attr:`thumb_color_inactive` is an :class:`~kivy.properties.ColorProperty`
- and default to `None`.
+ :attr:`handle_anim_duration` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `0.2`.
"""
- thumb_color_disabled = ColorProperty(None)
- """
- The color in (r, g, b, a) or string format of the thumb when the slider is
- in the disabled state.
+ _value_label_container_size = ListProperty([0, 0]) # value label texture
+ _value_label = ObjectProperty() # value label texture
+ _value_container = ObjectProperty() # MDSliderValueContainer object
+ _value_container_y = NumericProperty(0) # MDSliderValueContainer object
+ _handle = ObjectProperty() # MDSliderHandle object
+ # List of points displayed on the slider when using the `step` for th
+ # active/inactive tracks.
+ _active_points = ListProperty()
+ _inactive_points = ListProperty()
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ Clock.schedule_once(self._update_state_layer_pos, 0.5)
+ Clock.schedule_once(self.on_size)
+
+ def add_widget(self, widget, index=0, canvas=None):
+ def set_value_container_y(*args):
+ self._value_container_y = self.ids.handle_container.y
+
+ if isinstance(widget, MDSliderValueLabel):
+ self._value_label = widget
+ self._value_container = MDSliderValueContainer(_slider=self)
+ self.ids.value_container.add_widget(self._value_container)
+ Clock.schedule_once(set_value_container_y)
+ elif isinstance(widget, MDSliderHandle):
+ widget._slider = self
+ self._handle = widget
+ self.ids.handle_container.add_widget(widget)
+ else:
+ return super().add_widget(widget)
+
+ def update_points(self, instance, step) -> None:
+ """Draws the step points on the slider."""
+
+ def update_points(*args):
+ y = self.center_y - self.track_active_width / 2
+ slider_length = self.width - (self.padding * 2)
+ slider_max_value = int(self.max)
+ multiplier = slider_length / slider_max_value
+ active_track_width = (
+ self.width - self.padding * 2
+ ) * self.value_normalized
+
+ for i in range(0, slider_max_value + 1, step):
+ x = i * multiplier
+
+ if x < active_track_width:
+ points = self._inactive_points
+ else:
+ points = self._active_points
+
+ points.append(
+ self.x
+ + x
+ + self.padding
+ + (
+ (self.ids.handle_container.width / 2)
+ if i != self.max and i
+ else 0
+ )
+ )
+ points.append(y)
+
+ Clock.schedule_once(update_points)
+
+ def on_size(self, *args) -> None:
+ """Fired when the widget is resized."""
+
+ self._update_points()
+
+ def on_touch_down(self, touch):
+ if self.disabled or not self.collide_point(*touch.pos):
+ return
+ if touch.is_mouse_scrolling:
+ if "down" in touch.button or "left" in touch.button:
+ if self.step:
+ self.value = min(self.max, self.value + self.step)
+ else:
+ self.value = min(
+ self.max, self.value + (self.max - self.min) / 20
+ )
+ if "up" in touch.button or "right" in touch.button:
+ if self.step:
+ self.value = max(self.min, self.value - self.step)
+ else:
+ self.value = max(
+ self.min, self.value - (self.max - self.min) / 20
+ )
+ elif self.sensitivity == "handle":
+ if self.children[0].collide_point(*touch.pos):
+ touch.grab(self)
+ else:
+ touch.grab(self)
+ Clock.schedule_once(self._update_state_layer_pos)
+ Animation(value_pos=touch.pos, d=0.2).start(self)
+
+ return True
+
+ def on_value_pos(self, *args) -> None:
+ """
+ Fired when the `value_pos` value changes.
+ Sets a new value for the value label texture.
+ """
- .. versionadded:: 1.0.0
+ self._update_points()
- .. code-block:: kv
+ if self._value_label and self._value_container:
+ # FIXME: I do not know how else I can update the texture.
+ self._value_label.text = ""
+ self._value_label.text = f"{int(self.value)}"
+ self._value_label.texture_update()
+ label_value_rect = self._value_container.canvas.get_group(
+ "md-slider-label-value-rect"
+ )[0]
+ label_value_rect.texture = None
+ label_value_rect.texture = self._value_label.texture
+ label_value_rect.size = self._value_label.texture_size
- MDSlider
- value: 55
- disabled: True
- thumb_color_disabled: "red"
+ def on_touch_up(self, touch):
+ if touch.grab_current == self:
+ if self._handle:
+ self._handle.on_leave()
+ return True
+
+ def on_touch_move(self, touch):
+ if self.collide_point(touch.x, touch.y):
+ if self._handle:
+ self._update_state_layer_pos()
+ if self._handle and not self._handle._active:
+ self._handle.on_enter()
+ return super().on_touch_move(touch)
+
+ def on_handle_enter(self) -> None:
+ """Scales the container of the label value."""
+
+ if self._handle and self._value_label:
+ Animation(
+ scale_value_x=1,
+ scale_value_y=1,
+ t=self.value_container_show_anim_transition,
+ d=self.value_container_show_anim_duration,
+ ).start(self._value_container)
+ Animation(
+ _value_container_y=self.ids.handle_container.y + dp(32),
+ t=self.value_container_show_anim_transition,
+ d=self.value_container_show_anim_duration,
+ ).start(self)
+
+ def on_handle_leave(self) -> None:
+ """Scales the container of the label value."""
+
+ if self._handle and self._value_label:
+ Animation(
+ scale_value_x=0,
+ scale_value_y=0,
+ # opacity=1,
+ d=self.value_container_hide_anim_duration,
+ t=self.value_container_hide_anim_transition,
+ ).start(self._value_container)
+ Animation(
+ _value_container_y=self._value_container_y - dp(24),
+ t=self.value_container_hide_anim_transition,
+ d=self.value_container_hide_anim_duration,
+ ).start(self)
+
+ def _update_points(self, *args) -> None:
+ if self.step:
+ self._active_points = []
+ self._inactive_points = []
+ self.update_points(self, self.step)
+
+ def _update_state_layer_pos(self, *args):
+ if self._handle:
+ self._handle.ids.state_layer.scale_value_center = (
+ self.ids.handle_container.center
+ )
+
+
+class MDSliderHandle(
+ ThemableBehavior, BackgroundColorBehavior, FocusBehavior, Widget
+):
+ """
+ Handle class.
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-disabled.png
- :align: center
+ .. versionadded:: 2.0.0
- :attr:`thumb_color_disabled` is an :class:`~kivy.properties.ColorProperty`
- and default to `None`.
+ For more information, see in the
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivymd.uix.behaviors.focus_behavior.FocusBehavior` and
+ :class:`~kivy.uix.widget.Widget`
+ classes documentation.
"""
- track_color_active = ColorProperty(None)
+ radius = VariableListProperty([dp(10)], length=4)
"""
- The color in (r, g, b, a) or string format of the track when the slider is active.
+ Handle radius.
- .. versionadded:: 1.0.0
+ :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
+ and defaults to `[dp(10), dp(10), dp(10), dp(10)]`.
+ """
- .. code-block:: kv
+ size = ListProperty([dp(20), dp(20)])
+ """
+ Handle size.
- MDSlider
- track_color_active: "red"
+ :attr:`size` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[dp(20), dp(20)]`.
+ """
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-active.png
- :align: center
+ state_layer_size = ListProperty([dp(40), dp(40)])
+ """
+ Handle state layer size.
- :attr:`track_color_active` is an :class:`~kivy.properties.ColorProperty`
- and default to `None`.
+ :attr:`state_layer_size` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[dp(40), dp(40)]`.
"""
- track_color_inactive = ColorProperty(None)
+ state_layer_color = ColorProperty(None)
"""
- The color in (r, g, b, a) or string format of the track when the slider is inactive.
+ Handle state layer color.
- .. versionadded:: 1.0.0
+ :attr:`state_layer_color` is an :class:`~kivy.properties.ColorProperty`
+ and defaults to `None`.
+ """
- .. code-block:: kv
+ _slider = ObjectProperty() # MDSlider object
+ _active = False # is the layer currently displayed
+ _state_layer = ObjectProperty() # MDSliderStateLayer object
- MDSlider
- track_color_inactive: "red"
+ def on_enter(self) -> None:
+ """
+ Fired when mouse enter the bbox of the widget.
+ Animates the display of the slider handle layer.
+ """
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-inactive.png
- :align: center
+ if self._slider:
+ if self._state_layer and not self._slider.disabled:
+ self._active = True
+ anim = Animation(scale_value_x=1, scale_value_y=1, d=0.2)
+ anim.bind(on_complete=self._slider._update_state_layer_pos)
+ anim.start(self._state_layer)
+ if not self._slider.disabled:
+ self._slider.on_handle_enter()
- :attr:`track_color_inactive` is an :class:`~kivy.properties.ColorProperty`
- and default to `None`.
- """
+ def on_leave(self) -> None:
+ """
+ Fired when the mouse goes outside the widget border.
+ Animates the hiding of the slider handle layer.
+ """
- track_color_disabled = ColorProperty(None)
- """
- The color in (r, g, b, a) or string format of the track when the slider is
- in the disabled state.
+ if self._slider:
+ if self._state_layer and not self._slider.disabled:
+ self._active = False
+ anim = Animation(scale_value_x=0, scale_value_y=0, d=0.2)
+ anim.bind(on_complete=self._slider._update_state_layer_pos)
+ anim.start(self._state_layer)
+ if not self._slider.disabled:
+ self._slider.on_handle_leave()
- .. versionadded:: 1.0.0
- .. code-block:: kv
+class MDSliderHandleStateLayer(ScaleBehavior, Widget):
+ """
+ Slider state layer class.
- MDSlider
- disabled: True
- track_color_disabled: "red"
+ .. versionadded:: 2.0.0
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-disabled.png
- :align: center
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
+ :class:`~kivy.uix.widget.Widget`
+ classes documentation.
+ """
- :attr:`track_color_disabled` is an :class:`~kivy.properties.ColorProperty`
- and default to `None`.
+ scale_value_x = NumericProperty(0)
"""
+ X-axis value.
- show_off = BooleanProperty(True)
+ :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
"""
- Show the `'off'` ring when set to minimum value.
- :attr:`show_off` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
+ scale_value_y = NumericProperty(0)
"""
+ Y-axis value.
- _thumb_pos = ListProperty([0, 0])
- # Internal state of ring.
- _is_off = BooleanProperty(False)
- # Internal adjustment to reposition sliders for ring.
- _offset = ListProperty((0, 0))
+ :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
+ """
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Clock.schedule_once(self.set_thumb_icon)
- def set_thumb_icon(self, *args) -> None:
- self.ids.thumb.ids.icon.icon = "blank"
+class MDSliderValueLabel(MDLabel):
+ """
+ Implements the value label.
- def on_hint(self, instance, value) -> None:
- def on_hint(*args):
- if not value:
- self.remove_widget(self.ids.hint_box)
+ For more information, see in the :class:`~kivymd.uix.label.label.MDLabel`
+ class documentation.
- # Schedule using for declarative style.
- # Otherwise get AttributeError exception.
- Clock.schedule_once(on_hint)
+ .. versionadded:: 2.0.0
+ """
- def on_value_normalized(self, *args) -> None:
- """
- When the ``value == min`` set it to `'off'` state and make slider
- a ring.
- """
+ size = ListProperty([dp(36), dp(36)])
+ """
+ Container size for the label value.
- self._update_is_off()
+ :attr:`handle_anim_transition` is an :class:`~kivy.properties.ListProperty`
+ and defaults to `[dp(36), dp(36)]`.
+ """
- def on_show_off(self, *args) -> None:
- self._update_is_off()
- def on__is_off(self, *args) -> None:
- self._update_offset()
+class MDSliderValueContainer(ScaleBehavior, Widget):
+ """
+ Implements the container for value label.
- def on_active(self, *args) -> None:
- self._update_offset()
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and
+ :class:`~kivy.uix.widget.Widget`
+ classes documentation.
- def on_touch_down(self, touch):
- if super().on_touch_down(touch):
- self.active = True
+ .. versionadded:: 2.0.0
+ """
- def on_touch_up(self, touch):
- if super().on_touch_up(touch):
- self.active = False
+ scale_value_x = NumericProperty(0)
+ """
+ X-axis value.
- def _update_offset(self):
- """
- Offset is used to shift the sliders so the background color
- shows through the off circle.
- """
+ :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
+ """
+
+ scale_value_y = NumericProperty(0)
+ """
+ Y-axis value.
- d = 2 if self.active else 0
- self._offset = (dp(11 + d), dp(11 + d)) if self._is_off else (0, 0)
+ :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty`
+ and defaults to `1`.
+ """
- def _update_is_off(self):
- self._is_off = self.show_off and (self.value_normalized == 0)
+ _slider = ObjectProperty() # MDSlider object
diff --git a/kivymd/uix/snackbar/__init__.py b/kivymd/uix/snackbar/__init__.py
index a6fd8ec82..c83a2cf2d 100644
--- a/kivymd/uix/snackbar/__init__.py
+++ b/kivymd/uix/snackbar/__init__.py
@@ -1,6 +1,9 @@
from .snackbar import ( # NOQA F401
MDSnackbar,
+ MDSnackbarText,
+ MDSnackbarSupportingText,
MDSnackbarActionButton,
+ MDSnackbarActionButtonText,
+ MDSnackbarButtonContainer,
MDSnackbarCloseButton,
- Snackbar,
)
diff --git a/kivymd/uix/snackbar/snackbar.kv b/kivymd/uix/snackbar/snackbar.kv
index 4334bbf30..7ed7d7cf8 100644
--- a/kivymd/uix/snackbar/snackbar.kv
+++ b/kivymd/uix/snackbar/snackbar.kv
@@ -1,28 +1,78 @@
-#:import SNACK_BAR_ELEVATION kivymd.material_resources.SNACK_BAR_ELEVATION
-#:import SNACK_BAR_OFFSET kivymd.material_resources.SNACK_BAR_OFFSET
-
-
padding: 0, 0, "8dp", 0
+ theme_bg_color: "Custom"
+ theme_elevation_level: "Custom"
+ theme_elevation_level: "Custom"
+ style: "elevated"
+ shadow_radius: self.radius
+ elevation_level: 3
size_hint_y: None
height: self.minimum_height
- md_bg_color: "#323232"
- elevation: SNACK_BAR_ELEVATION
- shadow_offset: SNACK_BAR_OFFSET
+ orientation: "vertical"
+ md_bg_color:
+ self.theme_cls.inverseSurfaceColor \
+ if not self.background_color else \
+ self.background_color
- SnackbarLabelContainer:
+ BoxLayout:
id: label_container
padding: "16dp", "15dp", 0, "15dp"
orientation: "vertical"
- adaptive_height: True
- pos_hint: {"center_y": .5}
+ size_hint_y: None
+ height: self.minimum_height
+ pos_hint: {"top": 1}
spacing: "4dp"
- SnackbarActionButtonContainer:
- id: action_container
+ BoxLayout:
+ id: button_container
size_hint_x: None
+ width: self.minimum_width
- SnackbarCloseButtonContainer:
- id: close_container
- size_hint_x: None
- width: "38dp"
+
+
+ size_hint_y: None
+ height: self.minimum_height
+
+
+
+ adaptive_size: True
+ font_style: "Label"
+ role: "large"
+ markup: True
+ text_color:
+ self.theme_cls.inversePrimaryColor \
+ if self.theme_text_color == "Primary" else \
+ self.text_color
+
+
+
+ adaptive_height: True
+ font_style: "Body"
+ role: "medium"
+ markup: True
+ text_color:
+ self.theme_cls.inverseOnSurfaceColor \
+ if self.theme_text_color == "Primary" else \
+ self.text_color
+
+
+ style: "text"
+ pos_hint: {"right": 1}
+
+
+
+ text_color:
+ self.theme_cls.inverseOnSurfaceColor \
+ if self.theme_icon_color == "Primary" else \
+ self.icon_color
+
+
+
+ adaptive_size: True
+ font_style: "Label"
+ role: "large"
+ markup: True
+ color:
+ self.theme_cls.inversePrimaryColor \
+ if self.theme_text_color == "Primary" else \
+ self.text_color
diff --git a/kivymd/uix/snackbar/snackbar.py b/kivymd/uix/snackbar/snackbar.py
index fdb7ebe45..8b6902779 100755
--- a/kivymd/uix/snackbar/snackbar.py
+++ b/kivymd/uix/snackbar/snackbar.py
@@ -12,227 +12,178 @@
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar.png
:align: center
+- Snackbars shouldn’t interrupt the user’s experience
+- Usually appear at the bottom of the UI
+- Can disappear on their own or remain on screen until the user takes action
+
Usage
-----
.. code-block:: python
MDSnackbar(
- MDLabel(
- text="First string",
- theme_text_color="Custom",
- text_color="#393231",
+ MDSnackbarText(
+ text="Text",
),
+ y=dp(24),
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
).open()
-Example
--------
-
-.. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.label import MDLabel
- from kivymd.uix.snackbar import MDSnackbar
-
-
- KV = '''
- MDScreen:
-
- MDRaisedButton:
- text: "Create simple snackbar"
- on_release: app.open_snackbar()
- pos_hint: {"center_x": .5, "center_y": .5}
- '''
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-anatomy-detail.png
+ :align: center
+1. Container
+2. Supporting text
+3. Action (optional)
+4. Icon (optional close affordance)
- class Example(MDApp):
- def open_snackbar(self):
- MDSnackbar(
- MDLabel(
- text="First string",
- ),
- ).open()
+Anatomy
+-------
- def build(self):
- self.theme_cls.theme_style = "Dark"
- self.theme_cls.primary_palette = "Orange"
- return Builder.load_string(KV)
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-anatomy.png
+ :align: center
+Configurations
+==============
- Example().run()
+1. Single line
+--------------
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-simple.gif
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-single-line.png
:align: center
-Control width and pos
----------------------
-
.. code-block:: python
MDSnackbar(
- MDLabel(
- text="First string",
+ MDSnackbarText(
+ text="Single-line snackbar",
),
- pos=(dp(24), dp(56)),
+ y=dp(24),
+ pos_hint={"center_x": 0.5},
size_hint_x=0.5,
).open()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-widith-and-pos.gif
- :align: center
-On mobile, use up to two lines of text to communicate the snackbar message:
+2. Single-line snackbar with action
+-----------------------------------
+
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-single-line-with-action.png
+ :align: center
.. code-block:: python
MDSnackbar(
- MDLabel(
- text="First string",
- theme_text_color="Custom",
- text_color="#393231",
+ MDSnackbarSupportingText(
+ text="Single-line snackbar with action",
),
- MDLabel(
- text="Second string",
- theme_text_color="Custom",
- text_color="#393231",
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(
+ text="Action button"
+ ),
+ ),
+ pos_hint={"center_y": 0.5}
),
y=dp(24),
+ orientation="horizontal",
pos_hint={"center_x": 0.5},
size_hint_x=0.5,
- md_bg_color="#E8D8D7",
).open()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-two-line.gif
- :align: center
-
-Usage action button
--------------------
+3. Single-line snackbar with action and close buttons
+-----------------------------------------------------
-A snackbar can contain a single action. "Dismiss" or "cancel" actions are
-optional:
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-single-line-with-action-and-close-buttons.png
+ :align: center
.. code-block:: python
MDSnackbar(
- MDLabel(
- text="First string",
- theme_text_color="Custom",
- text_color="#393231",
+ MDSnackbarSupportingText(
+ text="Single-line snackbar with action and close buttons",
),
- MDSnackbarActionButton(
- text="Done",
- theme_text_color="Custom",
- text_color="#8E353C",
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(
+ text="Action button"
+ ),
+ ),
+ MDSnackbarCloseButton(
+ icon="close",
+ ),
+ pos_hint={"center_y": 0.5}
),
y=dp(24),
+ orientation="horizontal",
pos_hint={"center_x": 0.5},
size_hint_x=0.5,
- md_bg_color="#E8D8D7",
).open()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-action-button.gif
- :align: center
-
-Callback action button
-----------------------
+4. Two-line snackbar with action and close buttons
+--------------------------------------------------
-.. code-block:: python
-
- def snackbar_action_button_callback(self, *args):
- print("Snackbar callback action button")
-
- def open_snackbar(self):
- self.snackbar = MDSnackbar(
- MDLabel(
- text="First string",
- theme_text_color="Custom",
- text_color="#393231",
- ),
- MDSnackbarActionButton(
- text="Done",
- theme_text_color="Custom",
- text_color="#8E353C",
- _no_ripple_effect=True,
- on_release=self.snackbar_action_button_callback,
- ),
- y=dp(24),
- pos_hint={"center_x": 0.5},
- size_hint_x=0.5,
- md_bg_color="#E8D8D7",
- )
- self.snackbar.open()
-
-If an action is long, it can be displayed on a third line:
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-two-line-with-action-and-close-buttons.png
+ :align: center
.. code-block:: python
MDSnackbar(
- MDLabel(
- text="If an action is long, it can be displayed",
- theme_text_color="Custom",
- text_color="#393231",
+ MDSnackbarText(
+ text="Single-line snackbar",
),
- MDLabel(
- text="on a third line.",
- theme_text_color="Custom",
- text_color="#393231",
+ MDSnackbarSupportingText(
+ text="with action and close buttons",
),
- MDLabel(
- text=" ",
- ),
- MDSnackbarActionButton(
- text="Action button",
- theme_text_color="Custom",
- text_color="#8E353C",
- y=dp(8),
- _no_ripple_effect=True,
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(
+ text="Action button"
+ ),
+ ),
+ MDSnackbarCloseButton(
+ icon="close",
+ ),
+ pos_hint={"center_y": 0.5}
),
y=dp(24),
+ orientation="horizontal",
pos_hint={"center_x": 0.5},
size_hint_x=0.5,
- md_bg_color="#E8D8D7",
).open()
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-action-button-on-thrid-line.gif
- :align: center
+5. Two-line snackbar with action and close buttons at the bottom
+----------------------------------------------------------------
-Icon (optional close affordance):
+.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-two-line-with-action-and-close-buttons-bottom.png
+ :align: center
.. code-block:: python
- def snackbar_close(self, *args):
- self.snackbar.dismiss()
-
- def open_snackbar(self):
- self.snackbar = MDSnackbar(
- MDLabel(
- text="Icon (optional close affordance)",
- theme_text_color="Custom",
- text_color="#393231",
- ),
+ MDSnackbar(
+ MDSnackbarText(
+ text="Single-line snackbar with action",
+ ),
+ MDSnackbarSupportingText(
+ text="and close buttons at the bottom",
+ padding=[0, 0, 0, dp(56)],
+ ),
+ MDSnackbarButtonContainer(
+ Widget(),
MDSnackbarActionButton(
- text="Action button",
- theme_text_color="Custom",
- text_color="#8E353C",
- _no_ripple_effect=True,
+ MDSnackbarActionButtonText(
+ text="Action button"
+ ),
),
MDSnackbarCloseButton(
icon="close",
- theme_text_color="Custom",
- text_color="#8E353C",
- _no_ripple_effect=True,
- on_release=self.snackbar_close,
),
- y=dp(24),
- pos_hint={"center_x": 0.5},
- size_hint_x=0.5,
- md_bg_color="#E8D8D7",
- )
- self.snackbar.open()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-optional-close-affordance.gif
- :align: center
+ ),
+ y=dp(124),
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
+ padding=[0, 0, "8dp", "8dp"],
+ ).open()
API break
=========
@@ -279,37 +230,61 @@ def open_snackbar(self):
size_hint_x=0.5,
md_bg_color="#E8D8D7",
).open()
+
+2.2.0 version
+-------------
+
+.. code-block:: python
+
+ MDSnackbar(
+ MDSnackbarSupportingText(
+ text="Single-line snackbar with action",
+ ),
+ MDSnackbarButtonContainer(
+ MDSnackbarActionButton(
+ MDSnackbarActionButtonText(
+ text="Action button"
+ ),
+ ),
+ pos_hint={"center_y": 0.5}
+ ),
+ y=dp(24),
+ orientation="horizontal",
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5,
+ background_color=self.theme_cls.onPrimaryContainerColor,
+ ).open()
"""
__all__ = (
"MDSnackbar",
+ "MDSnackbarText",
+ "MDSnackbarSupportingText",
+ "MDSnackbarButtonContainer",
"MDSnackbarActionButton",
+ "MDSnackbarActionButtonText",
"MDSnackbarCloseButton",
)
import os
-from kivy import Logger
-from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
+from kivy.metrics import dp
from kivy.properties import (
BooleanProperty,
- ColorProperty,
ListProperty,
- NumericProperty,
- OptionProperty,
- StringProperty,
+ NumericProperty, ColorProperty,
)
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.widget import Widget
from kivymd import uix_path
-from kivymd.uix.behaviors import MotionShackBehavior
-from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.button import MDFlatButton, MDIconButton
+from kivymd.uix.behaviors import MotionShackBehavior, DeclarativeBehavior
+from kivymd.uix.button import MDButton, MDIconButton, MDButtonText
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
-from kivymd.uix.relativelayout import MDRelativeLayout
with open(
os.path.join(uix_path, "snackbar", "snackbar.kv"), encoding="utf-8"
@@ -317,16 +292,25 @@ def open_snackbar(self):
Builder.load_string(kv_file.read())
-class SnackbarLabelContainer(MDBoxLayout):
- """Container for placing snackbar text."""
+class MDSnackbarButtonContainer(DeclarativeBehavior, BoxLayout):
+ """
+ The class implements a container for placing snackbar buttons.
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation.
+ """
-class SnackbarActionButtonContainer(MDRelativeLayout):
- """Container for placing snackbar action button."""
+ def add_widget(self, widget, *args, **kwargs):
+ def set_container_width(w):
+ self.parent.width += w.width
+ if isinstance(
+ widget, (MDSnackbarActionButton, MDSnackbarCloseButton, Widget)
+ ):
+ Clock.schedule_once(lambda x: set_container_width(widget), 0.2)
-class SnackbarCloseButtonContainer(MDRelativeLayout):
- """Container for placing snackbar close button."""
+ return super().add_widget(widget)
class MDSnackbarCloseButton(MDIconButton):
@@ -334,28 +318,30 @@ class MDSnackbarCloseButton(MDIconButton):
Snackbar closed button class.
For more information, see in the
- :class:`~kivymd.uix.button.MDIconButton` class documentation.
+ :class:`~kivymd.uix.button.button.MDIconButton` class documentation.
"""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- if not self.y and not self.pos_hint:
- self.pos_hint = {"center_y": 0.5}
+
+class MDSnackbarActionButtonText(MDButtonText):
+ """
+ The class implements the text for the :class:`~MDSnackbarActionButton`
+ class.
+
+ .. versionchanged:: 2.2.0
+
+ For more information, see in the
+ :class:`~kivymd.uix.button.button.MDButtonText` class documentation.
+ """
-class MDSnackbarActionButton(MDFlatButton):
+class MDSnackbarActionButton(MDButton):
"""
Snackbar action button class.
For more information, see in the
- :class:`~kivymd.uix.button.MDFlatButton` class documentation.
+ :class:`~kivymd.uix.button.button.MDButton` class documentation.
"""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- if not self.y and not self.pos_hint:
- self.pos_hint = {"center_y": 0.5}
-
class MDSnackbar(MotionShackBehavior, MDCard):
"""
@@ -365,15 +351,15 @@ class MDSnackbar(MotionShackBehavior, MDCard):
Rename `BaseSnackbar` to `MDSnackbar` class.
For more information, see in the
- :class:`~kivymd.uix.card.MDCard` and
- :class:`~kivymd.uix.behaviors.StencilBehavior`
+ :class:`~kivymd.uix.behaviors.motion_behavior.MotionShackBehavior` and
+ :class:`~kivymd.uix.card.card.MDCard` and
class documentation.
:Events:
:attr:`on_open`
- Called when a snackbar opened.
+ Fired when a snackbar opened.
:attr:`on_dismiss`
- Called when a snackbar closes.
+ Fired when a snackbar closes.
"""
duration = NumericProperty(3)
@@ -392,70 +378,22 @@ class documentation.
and defaults to `True`.
"""
- radius = ListProperty([5, 5, 5, 5])
+ radius = ListProperty([dp(4), dp(4), dp(4), dp(4)])
"""
Snackbar radius.
:attr:`radius` is a :class:`~kivy.properties.ListProperty`
- and defaults to `[5, 5, 5, 5]`
+ and defaults to `[dp(4), dp(4), dp(4), dp(4)]`
"""
- bg_color = ColorProperty(None, deprecated=True)
+ background_color = ColorProperty(None)
"""
- Snackbar background color in (r, g, b, a) or string format.
+ The background color in (r, g, b, a) or string format of the snackbar.
- .. deprecated:: 1.2.0
- Use 'md_bg_color` instead.
-
- :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty`
+ :attr:`background_color` is a :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- buttons = ListProperty(deprecated=True)
- """
- Snackbar buttons.
-
- .. deprecated:: 1.2.0
-
- :attr:`buttons` is a :class:`~kivy.properties.ListProperty`
- and defaults to `[]`
- """
-
- snackbar_animation_dir = OptionProperty(
- "Bottom",
- options=["Top", "Bottom", "Left", "Right"],
- deprecated=True,
- )
- """
- Snackbar animation direction.
- Available options are: `'Top'`, `'Bottom'`, `'Left'`, `'Right'`.
-
- .. deprecated:: 1.2.0
-
- :attr:`snackbar_animation_dir` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'Bottom'`.
- """
-
- snackbar_x = NumericProperty(0, deprecated=True)
- """
- The snackbar x position in the screen
-
- .. deprecated:: 1.2.0
-
- :attr:`snackbar_x` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
-
- snackbar_y = NumericProperty(0, deprecated=True)
- """
- The snackbar x position in the screen
-
- .. deprecated:: 1.2.0
-
- :attr:`snackbar_y` is a :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
-
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.register_event_type("on_open")
@@ -470,80 +408,40 @@ def dismiss(self, *args) -> None:
def open(self) -> None:
"""Show the snackbar."""
- for widget in Window.parent.children:
- if widget.__class__ is MDSnackbar:
- return
-
- Window.parent.add_widget(self)
+ Window.add_widget(self)
super().on_open()
def add_widget(self, widget, *args, **kwargs):
- def check_color(color):
- if not widget.text_color:
- widget.theme_text_color = "Custom"
- widget.text_color = color
-
- if isinstance(widget, MDSnackbarCloseButton):
- widget.icon_size = "20sp"
- check_color("white")
- self.ids.close_container.add_widget(widget)
- if len(self.ids.close_container.children) >= 2:
- Logger.warning(
- "KivyMD: "
- "Do not use more than one button to close the snackbar. "
- "This is contrary to the material design rules "
- "of version 3"
- )
- if isinstance(widget, MDSnackbarActionButton):
- self.ids.action_container.add_widget(widget)
- check_color(self.theme_cls.primary_color)
- if len(self.ids.action_container.children) >= 2:
- Logger.warning(
- "KivyMD: "
- "Do not use more than one action button. "
- "This is contrary to the material design rules "
- "of version 3"
- )
- if isinstance(widget, MDLabel):
- widget.adaptive_height = True
- widget.pos_hint = {"center_y": 0.5}
- check_color("white")
+ if isinstance(widget, (MDSnackbarText, MDSnackbarSupportingText)):
self.ids.label_container.add_widget(widget)
- if len(self.ids.label_container.children) >= 4:
- Logger.warning(
- "KivyMD: "
- "Do not use more than three lines in the snackbar. "
- "This is contrary to the material design rules "
- "of version 3"
- )
- elif isinstance(
- widget,
- (
- SnackbarLabelContainer,
- SnackbarActionButtonContainer,
- SnackbarCloseButtonContainer,
- ),
- ):
+ elif isinstance(widget, MDSnackbarButtonContainer):
+ self.ids.button_container.size_hint_x = (
+ 1 if self.orientation == "vertical" else None
+ )
+ self.ids.button_container.add_widget(widget)
+ else:
return super().add_widget(widget)
def on_open(self, *args) -> None:
- """Called when a snackbar opened."""
+ """Fired when a snackbar opened."""
def on_dismiss(self, *args) -> None:
- """Called when a snackbar closed."""
+ """Fired when a snackbar closed."""
-class Snackbar(MDSnackbar):
+class MDSnackbarText(MDLabel):
"""
- .. deprecated:: 1.2.0
- Use :class:`~kivymd.uix.snackbar.MDSnackbar`
- class instead.
+ The class implements the text.
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
"""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- Logger.warning(
- "KivyMD: "
- "The `Snackbar` class has been deprecated. "
- "Use the `MDSnackbar` class instead."
- )
+
+class MDSnackbarSupportingText(MDLabel):
+ """
+ The class implements the supporting text.
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
+ """
diff --git a/kivymd/uix/stacklayout.py b/kivymd/uix/stacklayout.py
index c5028ca5b..224008702 100644
--- a/kivymd/uix/stacklayout.py
+++ b/kivymd/uix/stacklayout.py
@@ -16,7 +16,7 @@
canvas:
Color:
- rgba: app.theme_cls.primary_color
+ rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
@@ -28,7 +28,7 @@
MDStackLayout:
adaptive_height: True
- md_bg_color: app.theme_cls.primary_color
+ md_bg_color: app.theme_cls.primaryColor
Available options are:
----------------------
@@ -89,13 +89,24 @@
from kivymd.theming import ThemableBehavior
from kivymd.uix import MDAdaptiveWidget
-from kivymd.uix.behaviors import DeclarativeBehavior
+from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior
class MDStackLayout(
- DeclarativeBehavior, ThemableBehavior, StackLayout, MDAdaptiveWidget
+ DeclarativeBehavior,
+ ThemableBehavior,
+ BackgroundColorBehavior,
+ StackLayout,
+ MDAdaptiveWidget,
):
"""
- Stack layout class. For more information, see in the
- :class:`~kivy.uix.stacklayout.StackLayout` class documentation.
+ Stack layout class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
+ :class:`~kivy.uix.stacklayout.StackLayout` and
+ :class:`~kivymd.uix.MDAdaptiveWidget`
+ classes documentation.
"""
diff --git a/kivymd/uix/tab/__init__.py b/kivymd/uix/tab/__init__.py
index c5111982c..4454a781f 100644
--- a/kivymd/uix/tab/__init__.py
+++ b/kivymd/uix/tab/__init__.py
@@ -1 +1,6 @@
-from .tab import MDTabs, MDTabsBase, MDTabsLabel # NOQA F401
+from .tab import (
+ MDTabs,
+ MDTabsItem,
+ MDTabsItemIcon,
+ MDTabsItemLabel,
+) # NOQA F401
diff --git a/kivymd/uix/tab/tab.kv b/kivymd/uix/tab/tab.kv
index f219bc1e7..0b1abd589 100644
--- a/kivymd/uix/tab/tab.kv
+++ b/kivymd/uix/tab/tab.kv
@@ -1,128 +1,47 @@
-#:import DampedScrollEffect kivy.effects.dampedscroll.DampedScrollEffect
-
-
-
- _set_start_tab: False
- size_hint: None, 1
- halign: "center"
- valign: "center"
- group: "tabs"
- font: root.font_name
- allow_no_selection: False
- markup: True
- on_width:
- if not self._set_start_tab: \
- self.tab_bar.parent._update_indicator( \
- self.tab_bar.parent.carousel.current_slide.tab_label); \
- self._set_start_tab = True
- on_tab_bar:
- self.text_size = (None, None) \
- if self.tab_bar.parent.allow_stretch else (self.width, None)
- on_ref_press:
- self.tab_bar.parent.dispatch( \
- "on_ref_press",
- self, \
- self.tab, \
- self.tab_bar, \
- self.tab_bar.parent.carousel)
- color:
- ( \
- self.text_color_active \
- if self.text_color_active else self.specific_secondary_text_color \
- ) \
- if self.state == "down" else \
- ( \
- self.text_color_normal \
- if self.text_color_normal else self.theme_cls.text_color \
- )
-
+
+ size_hint_y: None
-
- size_hint: 1, 1
- do_scroll_y: False
- bar_color: 0, 0, 0, 0
- bar_inactive_color: 0, 0, 0, 0
- bar_width: 0
- effect_cls: DampedScrollEffect
+ canvas.before:
+ Color:
+ rgba:
+ self.md_bg_color \
+ if self.md_bg_color and self.theme_bg_color == "Custom" else \
+ self.theme_cls.surfaceColor
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ MDTabsScrollView:
+ id: tab_scroll
+ bar_width: 0
-
- carousel: carousel
- tab_bar: tab_bar
- anchor_y: "top"
- background_palette: "Primary"
+ GridLayout:
+ id: container
+ rows: 1
+ size_hint: None, None
+ width: self.minimum_width
+ height: root.height
- _line_x: 0
- _line_width: 0
- _line_height: 0
- _line_radius: 0
- on_size:
- root._update_padding(layout)
+
+ pos_hint: {"center_x": .5}
+ theme_icon_color: "Custom"
+ icon_color: self.theme_cls.onSurfaceVariantColor
- MDTabsMain:
- padding: 0, tab_bar.height, 0, 0
- MDTabsCarousel:
- id: carousel
- lock_swiping: root.lock_swiping
- ignore_perpendicular_swipes: True
- anim_move_duration: root.anim_duration
- on_index: root.on_carousel_index(*args)
- on__offset: tab_bar.android_animation(*args)
+
+ adaptive_size: True
+ pos_hint: {"center_x": .5}
+ padding_x: "36dp"
+ theme_text_color: "Custom"
+ text_color: self.theme_cls.onSurfaceVariantColor
- MDTabsBar:
- id: tab_bar
- padding: root.tab_padding
- carousel: carousel
- scrollview: scrollview
- layout: layout
- size_hint: 1, None
- elevation: root.elevation
- radius: root.radius
- shadow_offset: root.shadow_offset
- shadow_color: root.shadow_color
- shadow_softness: root.shadow_softness
- height: root.tab_bar_height
- md_bg_color:
- self.theme_cls.primary_color \
- if not root.background_color else \
- root.background_color
- MDTabsScrollView:
- id: scrollview
- do_scroll_x: False if layout.width <= self.width else True
+
+ orientation: "vertical"
+ size_hint: None, None
+ height: self.minimum_height
+ spacing: "4dp"
+ padding: 0, "8dp", 0, "8dp"
- MDGridLayout:
- id: layout
- rows: 1
- size_hint_y: 1
- adaptive_width: True
- on_size: root._update_padding(layout)
- canvas.before:
- Color:
- rgba: root.underline_color
- Line:
- width: dp(2)
- rectangle: [0, 0, layout.width, dp(2)]
- Color:
- rgba:
- root.theme_cls.accent_color \
- if not root.indicator_color else \
- root.indicator_color
- RoundedRectangle:
- group: "Indicator_line"
- pos: self.pos
- size: 0, root.tab_indicator_height
- radius: [0,]
- Line:
- width: dp(2)
- rounded_rectangle:
- [ \
- root._line_x, \
- self.pos[1], \
- root._line_width, \
- root._line_height, \
- root._line_radius \
- ]
diff --git a/kivymd/uix/tab/tab.py b/kivymd/uix/tab/tab.py
index 4e97befd1..7af769291 100755
--- a/kivymd/uix/tab/tab.py
+++ b/kivymd/uix/tab/tab.py
@@ -920,343 +920,44 @@ def switch_tab_by_name(self, *args):
:align: center
"""
-__all__ = ("MDTabs", "MDTabsBase")
+from __future__ import annotations
+
+__all__ = ("MDTabs", "MDTabsItem", "MDTabsItemIcon", "MDTabsItemLabel")
import os
-from typing import Union
+from kivy.utils import boundary
+from kivy.animation import Animation
from kivy.clock import Clock
-from kivy.graphics.texture import Texture
from kivy.lang import Builder
-from kivy.logger import Logger
from kivy.metrics import dp
from kivy.properties import (
- AliasProperty,
- BooleanProperty,
- BoundedNumericProperty,
- ColorProperty,
- ListProperty,
- NumericProperty,
ObjectProperty,
+ BooleanProperty,
OptionProperty,
- StringProperty,
+ ColorProperty,
+ AliasProperty,
)
-from kivy.uix.anchorlayout import AnchorLayout
-from kivy.uix.behaviors import ToggleButtonBehavior
+from kivy.uix.behaviors import ButtonBehavior
+from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
-from kivy.utils import boundary
from kivymd import uix_path
-from kivymd.font_definitions import fonts, theme_font_styles
-from kivymd.icon_definitions import md_icons
-from kivymd.theming import ThemableBehavior, ThemeManager
+from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
DeclarativeBehavior,
RectangularRippleBehavior,
- SpecificBackgroundColorBehavior,
+ CommonElevationBehavior,
)
-from kivymd.uix.boxlayout import MDBoxLayout
-from kivymd.uix.card import MDCard
-from kivymd.uix.carousel import MDCarousel
-from kivymd.uix.label import MDLabel
+from kivymd.uix.behaviors.focus_behavior import FocusBehavior
+from kivymd.uix.label import MDLabel, MDIcon
with open(os.path.join(uix_path, "tab", "tab.kv"), encoding="utf-8") as kv_file:
Builder.load_string(kv_file.read())
-class MDTabsException(Exception):
- pass
-
-
-class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel):
- """This class it represent the label of each tab."""
-
- text_color_normal = ColorProperty(None)
- text_color_active = ColorProperty(None)
- tab = ObjectProperty()
- tab_bar = ObjectProperty()
- font_name = StringProperty("Roboto")
-
- def __init__(self, **kwargs):
- self.split_str = " ,-"
- super().__init__(**kwargs)
- self.max_lines = 2
- self.size_hint_x = None
- self.size_hint_min_x = dp(90)
- self.min_space = dp(98)
- self.bind(
- text=self._update_text_size,
- )
-
- def on_release(self) -> None:
- try:
- self.tab_bar.parent.dispatch(
- "on_tab_switch", self.tab, self, self.text
- )
- # If the label is selected load the relative tab from carousel.
- if self.state == "down":
- self.tab_bar.parent.carousel.load_slide(self.tab)
- except KeyError:
- pass
-
- def on_texture(self, instance_tabs_label, texture: Texture) -> None:
- # Just save the minimum width of the label based of the content.
- if texture:
- max_width = dp(360)
- min_width = dp(90)
- if texture.width > max_width:
- self.width = max_width
- self.text_size = (max_width, None)
- elif texture.width < min_width:
- self.width = min_width
- else:
- self.width = texture.width
-
- def _update_text_size(self, *args):
- if not self.tab_bar:
- return
- if self.tab_bar.parent.allow_stretch is True:
- self.text_size = (None, None)
- else:
- self.width = self.tab_bar.parent.fixed_tab_label_width
- self.text_size = (self.width, None)
- Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0)
-
-
-class MDTabsBase:
- """
- This class allow you to create a tab.
- You must create a new class that inherits from MDTabsBase.
- In this way you have total control over the views of your tabbed panel.
- """
-
- icon = StringProperty()
- """
- This property will set the Tab's Label Icon.
-
- :attr:`icon` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"])
- """
- This property sets the mode in wich the tab's title and icon are shown.
-
- :attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'Lead'`.
- """
-
- title = StringProperty()
- """
- This property will set the Name of the tab.
-
- .. note::
- As a side note.
-
- All tabs have set `markup = True`.
- Thanks to this, you can use the kivy markup language to set a colorful
- and fully customizable tabs titles.
-
- .. warning::
- The material design requires that every title label is written in
- capital letters, because of this, the `string.upper()` will be applied
- to it's contents.
-
- :attr:`title` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- title_is_capital = BooleanProperty(False)
- """
- This value controls wether if the title property should be converted to
- capital letters.
-
- :attr:`title_is_capital` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- tab_label_text = StringProperty()
- """
- This property is the actual title's Label of the tab.
- use the property :attr:`icon` and :attr:`title` to set this property
- correctly.
-
- This property is kept public for specific and backward compatibility
- purposes.
-
- :attr:`tab_label_text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- tab_label = ObjectProperty()
- """
- It is the label object reference of the tab.
-
- :attr:`tab_label` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
- """
-
- def _get_label_font_style(self):
- if self.tab_label:
- return self.tab_label.font_style
-
- def _set_label_font_style(self, value):
- if self.tab_label:
- if value in theme_font_styles:
- self.tab_label.font_style = value
- else:
- raise ValueError(
- "tab_label_font_style:\n\t"
- "font_style not found in theme_font_styles\n\t"
- f"font_style = {value}"
- )
- else:
- Clock.schedule_once(lambda x: self._set_label_font_style(value))
- return True
-
- tab_label_font_style = AliasProperty(
- _get_label_font_style,
- _set_label_font_style,
- cache=True,
- )
- """
- :attr:`tab_label_font_style` is an :class:`~kivy.properties.AliasProperty`
- that behavies similar to an :class:`~kivy.properties.OptionProperty`.
-
- This property's behavior allows the developer to use any new label style
- registered to the app.
-
- This property will affect the Tab's Title Label widget.
- """
-
- def __init__(self, *args, **kwargs):
- self.tab_label = MDTabsLabel(tab=self)
- super().__init__(*args, **kwargs)
- self.bind(
- icon=self._update_text,
- title=self._update_text,
- title_icon_mode=self._update_text,
- tab_label_text=self.update_label_text,
- title_is_capital=self.update_label_text,
- )
- Clock.schedule_once(
- self._update_text
- ) # this will ensure the text is correct
-
- def _update_text(self, *args):
- # Ensures that the title is in capital letters.
- if self.title and self.title_is_capital is True:
- if self.title != self.title.upper():
- self.title = self.title.upper()
- # Avoids event recursion.
- return
- # Add the icon.
- if self.icon and self.icon in md_icons:
- self.tab_label_text = f"[size=24sp][font={fonts[-1]['fn_regular']}]{md_icons[self.icon]}[/size][/font]"
- if self.title:
- self.tab_label_text = (
- self.tab_label_text
- + (" " if self.title_icon_mode == "Lead" else "\n")
- + self.title
- )
- # Add the title.
- else:
- if self.icon:
- Logger.error(
- f"{self}: [UID] = [{self.uid}]:\n\t"
- f"Icon '{self.icon}' not found in md_icons"
- )
- if self.title:
- self.tab_label_text = self.title
- else:
- if not self.tab_label_text:
- raise ValueError(
- f"{self}: [UID] = [{self.uid}]:\n\t"
- "No valid Icon was found.\n\t"
- "No valid Title was found.\n\t"
- f"Icon\t= '{self.icon}'\n\t"
- f"Title\t= '{self.title}'\n\t"
- )
-
- self.tab_label.padding = dp(16), 0
- self.update_label_text(None, self.tab_label_text)
-
- def update_label_text(self, instance_user_tab, text_tab: str) -> None:
- self.tab_label.text = text_tab
-
-
-class MDTabsMain(MDBoxLayout):
- """
- This class is just a boxlayout that contain the carousel.
- It allows you to have control over the carousel.
- """
-
-
-class MDTabsCarousel(MDCarousel):
- lock_swiping = BooleanProperty(False)
- """
- If True - disable switching tabs by swipe.
-
- :attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- def on_touch_move(self, touch):
- if self.lock_swiping: # lock a swiping
- return
- if not self.touch_mode_change:
- if self.ignore_perpendicular_swipes and self.direction in (
- "top",
- "bottom",
- ):
- if abs(touch.oy - touch.y) < self.scroll_distance:
- if abs(touch.ox - touch.x) > self.scroll_distance:
- self._change_touch_mode()
- self.touch_mode_change = True
- elif self.ignore_perpendicular_swipes and self.direction in (
- "right",
- "left",
- ):
- if abs(touch.ox - touch.x) < self.scroll_distance:
- if abs(touch.oy - touch.y) > self.scroll_distance:
- self._change_touch_mode()
- self.touch_mode_change = True
-
- if self._get_uid("cavoid") in touch.ud:
- return
- if self._touch is not touch:
- super().on_touch_move(touch)
- return self._get_uid() in touch.ud
- if touch.grab_current is not self:
- return True
-
- ud = touch.ud[self._get_uid()]
- direction = self.direction[0]
-
- if ud["mode"] == "unknown":
- if direction in "rl":
- distance = abs(touch.ox - touch.x)
- else:
- distance = abs(touch.oy - touch.y)
- if distance > self.scroll_distance:
- ev = self._change_touch_mode_ev
- if ev is not None:
- ev.cancel()
- ud["mode"] = "scroll"
- else:
- if direction in "rl":
- self._offset += touch.dx
- if direction in "tb":
- self._offset += touch.dy
- return True
-
-
class MDTabsScrollView(ScrollView):
- """This class hacked version to fix scroll_x manual setting."""
-
- def goto(
- self, scroll_x: Union[float, None], scroll_y: Union[float, None]
- ) -> None:
+ def goto(self, scroll_x: float | None, scroll_y: float | None) -> None:
"""Update event value along with scroll_*."""
def _update(e, x):
@@ -1264,7 +965,8 @@ def _update(e, x):
e.value = (e.max + e.min) * x
if not (scroll_x is None):
- self.scroll_x = scroll_x
+ Animation(scroll_x=scroll_x, d=0.2).start(self)
+ # self.scroll_x = scroll_x
_update(self.effect_x, scroll_x)
if not (scroll_y is None):
@@ -1272,709 +974,224 @@ def _update(e, x):
_update(self.effect_y, scroll_y)
-class MDTabsBar(MDCard):
- """
- This class is just a boxlayout that contains the scroll view for tabs.
- It is also responsible for resizing the tab shortcut when necessary.
+class MDTabsItemLabel(MDLabel):
"""
+ Implements an label for the :class:`~MDTabsItem` class.
+
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDLabel` class documentation.
- target = ObjectProperty(None, allownone=True)
+ .. versionchanged:: 2.0.0
"""
- It is the carousel reference of the next tab / slide.
- When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the
- target tab / slide of the carousel.
- :attr:`target` is an :class:`~kivy.properties.ObjectProperty`
- and default to `None`.
+ _active = BooleanProperty(False)
+
+
+class MDTabsItemIcon(MDIcon):
"""
+ Implements an icon for the :class:`~MDTabsItem` class.
- def get_rect_instruction(self):
- canvas_instructions = self.layout.canvas.before.get_group(
- "Indicator_line"
- )
- return canvas_instructions[0]
+ For more information, see in the
+ :class:`~kivymd.uix.label.label.MDIcon` class documentation.
- indicator = AliasProperty(get_rect_instruction, cache=True)
+ .. versionchanged:: 2.0.0
"""
- It is the :class:`~kivy.graphics.vertex_instructions.RoundedRectangle`
- instruction reference of the tab indicator.
- :attr:`indicator` is an :class:`~kivy.properties.AliasProperty`.
+
+class MDTabsItem(
+ DeclarativeBehavior,
+ ThemableBehavior,
+ RectangularRippleBehavior,
+ FocusBehavior,
+ ButtonBehavior,
+ BoxLayout,
+):
"""
+ Implements a menu item with an icon and text.
+
+ .. versionchanged:: 2.0.0
- def get_last_scroll_x(self):
- return self.scrollview.scroll_x
+ For more information, see in the
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
+ :class:`~kivymd.theming.ThemableBehavior` and
+ :class:`~kivymd.uix.behaviors.behaviors.ripple_behavior.RectangularRippleBehavior` and
+ :class:`~kivymd.uix.behaviors.behaviors.focus_behavior.FocusBehavior` and
+ :class:`~kivy.uix.behaviors.ButtonBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout`
+ classes documentation.
+ """
- last_scroll_x = AliasProperty(
- get_last_scroll_x, bind=("target",), cache=True
- )
+ active = BooleanProperty(False)
"""
- Is the carousel reference of the next tab/slide.
- When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the
- target tab/slide of the carousel.
+ Is the tab active.
- :attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`.
+ :attr:`active` is an :class:`~kivy.properties.BooleanProperty`
+ and defaults to `False`.
"""
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
-
- def update_indicator(
- self, x: Union[float, int], w: Union[float, int], radius=None
- ) -> None:
- # Update position and size of the indicator.
- if self.parent.tab_indicator_type == "line-round":
- self.parent._line_x = x
- self.parent._line_width = w
- self.parent._line_height = self.parent.tab_indicator_height
- self.parent._line_radius = self.parent.tab_indicator_height / 2
- elif self.parent.tab_indicator_type == "line-rect":
- self.parent._line_x = x
- self.parent._line_width = w
- self.parent._line_height = self.parent.tab_indicator_height
- else:
- self.indicator.pos = (x, 0)
- self.indicator.size = (w, self.parent.tab_indicator_height)
- if radius:
- self.indicator.radius = radius
+ _tabs = ObjectProperty()
+
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, (MDTabsItemLabel, MDTabsItemIcon)):
+ if len(self.children) <= 1:
+ Clock.schedule_once(lambda x: self._set_width(widget))
+
+ def on_release(self):
+ print(self._tabs.ids.container.width)
+ # self._tabs.ids.tab_scroll.goto(x, None)
- def tab_bar_autoscroll(self, instance_tab_label: MDTabsLabel, step: float):
# Automatic scroll animation of the tab bar.
- bound_left = self.center_x - self.x
- bound_right = self.layout.width - bound_left
- dt = instance_tab_label.center_x - bound_left
- sx, sy = self.scrollview.convert_distance_to_scroll(dt, 0)
- lsx = self.last_scroll_x # ast scroll x of the tab bar
+ bound_left = self._tabs.center_x - self._tabs.x
+ bound_right = self._tabs.ids.container.width - bound_left
+ dt = self.center_x - bound_left
+ sx, sy = self._tabs.ids.tab_scroll.convert_distance_to_scroll(dt, 0)
+ lsx = self._tabs.ids.tab_scroll.scroll_x # ast scroll x of the tab bar
scroll_is_late = lsx < sx # determine scroll direction
- dst = abs(lsx - sx) * step # distance to run
+ dst = abs(lsx - sx) * 0.5 # distance to run
if not dst:
return
- if scroll_is_late and instance_tab_label.center_x > bound_left:
+ if scroll_is_late and self.center_x > bound_left:
x = lsx + dst
- elif not scroll_is_late and instance_tab_label.center_x < bound_right:
+ elif not scroll_is_late and self.center_x < bound_right:
x = lsx - dst
else:
return
x = boundary(x, 0.0, 1.0)
- self.scrollview.goto(x, None)
-
- def android_animation(
- self, instance_carousel: MDTabsCarousel, offset: Union[float, int]
- ):
- # Try to reproduce the android animation effect.
- if offset != 0 and abs(offset) < instance_carousel.width:
- forward = offset < 0
- offset = abs(offset)
- step = offset / float(instance_carousel.width)
- indicator_animation = self.parent.tab_indicator_anim
-
- skip_slide = (
- instance_carousel.slides[instance_carousel._skip_slide]
- if instance_carousel._skip_slide is not None
- else None
- )
- next_slide = (
- instance_carousel.next_slide
- if forward
- else instance_carousel.previous_slide
- )
- self.target = skip_slide if skip_slide else next_slide
-
- if not self.target:
- return
-
- a = instance_carousel.current_slide.tab_label
- b = self.target.tab_label
- self.tab_bar_autoscroll(b, step)
-
- # Avoids the animation if `indicator_animation` is True.
- if indicator_animation is False:
- return
- gap_x = abs((a.x) - (b.x))
- gap_w = (b.width) - (a.width)
- if forward:
- x_step = a.x + (gap_x * step)
- else:
- x_step = a.x - gap_x * step
- w_step = a.width + (gap_w * step)
- self.update_indicator(x_step, w_step)
+ self._tabs.ids.tab_scroll.goto(x, None)
+
+ def _set_width(self, widget):
+ def set_width(*args):
+ self.width = widget.texture_size[0] + widget.padding_x + 2
- def _label_request_indicator_update(self, *args):
- widget = self.carousel.current_slide.tab_label
- self.update_indicator(widget.x, widget.width)
+ if not self._tabs.allow_stretch and isinstance(widget, MDTabsItemLabel):
+ Clock.schedule_once(set_width)
+
+ super().add_widget(widget)
class MDTabs(
DeclarativeBehavior,
ThemableBehavior,
- SpecificBackgroundColorBehavior,
- AnchorLayout,
+ CommonElevationBehavior,
+ BoxLayout,
):
"""
Tabs class.
You can use this class to create your own tabbed panel.
For more information, see in the
- :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and
+ :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
:class:`~kivymd.theming.ThemableBehavior` and
- :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior` and
- :class:`~kivy.uix.anchorlayout.AnchorLayout`
+ :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and
+ :class:`~kivy.uix.boxlayout.BoxLayout`
classes documentation.
:Events:
`on_tab_switch`
Called when switching tabs.
- `on_slide_progress`
- Called while the slide is scrolling.
- `on_ref_press`
- The method will be called when the ``on_ref_press`` event
- occurs when you, for example, use markup text for tabs.
- """
-
- tab_bar_height = NumericProperty("48dp")
"""
- Height of the tab bar.
- :attr:`tab_bar_height` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `'48dp'`.
+ md_bg_color = ColorProperty(None)
"""
+ The background color of the widget.
- tab_padding = ListProperty([0, 0, 0, 0])
- """
- Padding of the tab bar.
-
- :attr:`tab_padding` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0, 0, 0]`.
- """
-
- tab_indicator_anim = BooleanProperty(False)
- """
- Tab indicator animation. If you want use animation set it to ``True``.
-
- :attr:`tab_indicator_anim` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- tab_indicator_height = NumericProperty("2dp")
- """
- Height of the tab indicator.
-
- :attr:`tab_indicator_height` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `'2dp'`.
- """
-
- tab_indicator_type = OptionProperty(
- "line", options=["line", "fill", "round", "line-round", "line-rect"]
- )
- """
- Type of tab indicator. Available options are: `'line'`, `'fill'`,
- `'round'`, `'line-rect'` and `'line-round'`.
-
- :attr:`tab_indicator_type` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'line'`.
- """
-
- tab_hint_x = BooleanProperty(False)
- """
- This option affects the size of each child. if it's `True`, the size of
- each tab will be ignored and will use the size available by the container.
-
- :attr:`tab_hint_x` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- anim_duration = NumericProperty(0.2)
- """
- Duration of the slide animation.
-
- :attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0.2`.
- """
-
- anim_threshold = BoundedNumericProperty(
- 0.8, min=0.0, max=1.0, errorhandler=lambda x: 0.0 if x < 0.0 else 1.0
- )
- """
- Animation threshold allow you to change the tab indicator animation effect.
-
- :attr:`anim_threshold` is an :class:`~kivy.properties.BoundedNumericProperty`
- and defaults to `0.8`.
- """
-
- allow_stretch = BooleanProperty(True)
- """
- If `True`, the tab will update dynamically (if :attr:`tab_hint_x` is `True`)
- to it's content width, and wrap any text if the widget is wider than `"360dp"`.
-
- If `False`, the tab won't update to it's maximum texture width.
- this means that the `fixed_tab_label_width` will be used as the label
- width. this will wrap any text inside to fit the fixed value.
-
- :attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- fixed_tab_label_width = NumericProperty("140dp")
- """
- If :attr:`allow_stretch` is `False`, the class will set this value as the
- width to all the tabs title label.
-
- :attr:`fixed_tab_label_width` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `140dp`.
- """
-
- background_color = ColorProperty(None)
- """
- Background color of tabs in (r, g, b, a) or string format.
-
- :attr:`background_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- underline_color = ColorProperty([0, 0, 0, 0])
- """
- Underline color of tabs in (r, g, b, a) or string format.
-
- :attr:`underline_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0]`.
- """
-
- text_color_normal = ColorProperty(None)
- """
- 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 `None`.
- """
-
- text_color_active = ColorProperty(None)
- """
- 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`
+ :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
and defaults to `None`.
"""
- shadow_softness = NumericProperty(12)
- """
- See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_softness`
- attribute.
-
- .. versionadded:: 1.1.0
-
- :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `12`.
- """
-
- shadow_color = ColorProperty([0, 0, 0, 0.6])
+ type = OptionProperty("primary", options=["primary", "secondary"])
"""
- See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_color`
- attribute.
+ Panel type.
+ Available options are: `'primary'`, `'secondary'`.
- .. versionadded:: 1.1.0
+ .. versionchanged:: 2.0.0
- :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `[0, 0, 0, 0.6]`.
+ :attr:`type` is an :class:`~kivy.properties.OptionProperty`
+ and defaults to `'primary'`.
"""
- shadow_offset = ListProperty((0, 0))
+ label_only = BooleanProperty(False)
"""
- See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_offset`
- attribute.
+ Tabs with a label only or with an icon and a label.
- .. versionadded:: 1.1.0
-
- :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0, 0]`.
- """
+ .. versionchanged:: 2.0.0
- elevation = NumericProperty(0)
- """
- See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation`
- attribute.
-
- :attr:`elevation` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0`.
- """
-
- indicator_color = ColorProperty(None)
- """
- Color indicator in (r, g, b, a) or string format.
-
- :attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty`
- and defaults to `None`.
- """
-
- lock_swiping = BooleanProperty(False)
- """
- If True - disable switching tabs by swipe.
-
- :attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty`
+ :attr:`label_only` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
"""
- font_name = StringProperty("Roboto")
- """
- Font name for tab text.
-
- :attr:`font_name` is an :class:`~kivy.properties.StringProperty`
- and defaults to `'Roboto'`.
+ allow_stretch = BooleanProperty(False)
"""
+ Whether to stretch tabs to the width of the panel.
- ripple_duration = NumericProperty(2)
- """
- Ripple duration when long touching to tab.
-
- :attr:`ripple_duration` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `2`.
- """
-
- no_ripple_effect = BooleanProperty(True)
- """
- Whether to use the ripple effect when tapping on a tab.
-
- :attr:`no_ripple_effect` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"])
- """
- This property sets the mode in wich the tab's title and icon are shown.
-
- :attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'Lead'`.
- """
-
- force_title_icon_mode = BooleanProperty(True)
- """
- If this property is se to `True`, it will force the class to update every
- tab inside the scroll view to the current `title_icon_mode`
-
- :attr:`force_title_icon_mode` is an :class:`~kivy.properties.BooleanProperty`
+ :attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty`
and defaults to `True`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.register_event_type("on_tab_switch")
- self.register_event_type("on_ref_press")
- self.register_event_type("on_slide_progress")
- Clock.schedule_once(self._carousel_bind, 1)
- self.theme_cls.bind(
- primary_palette=self.update_icon_color,
- theme_style=self.update_icon_color,
- )
- self.bind(
- force_title_icon_mode=self._parse_icon_mode,
- title_icon_mode=self._parse_icon_mode,
- )
- self.bind(tab_hint_x=self._update_tab_hint_x)
-
- def update_icon_color(
- self,
- instance_theme_manager: ThemeManager,
- name_theme_style_name_palette: str,
- ) -> None:
- """
- Called when the app's color scheme or style has changed
- (dark theme/light theme).
- """
-
- for tab_label in self.get_tab_list():
- if not self.text_color_normal:
- tab_label.text_color_normal = self.theme_cls.text_color
- if not self.text_color_active:
- tab_label.text_color_active = self.specific_secondary_text_color
-
- def switch_tab(self, name_tab: Union[MDTabsLabel, str], search_by="text"):
- """
- This method switch between tabs
- name_tab can be either a String or a :class:`~MDTabsBase`.
-
- `search_by` will look up through the properties of every tab.
-
- If the value doesnt match, it will raise a ValueError.
-
- Search_by options:
- text : will search by the raw text of the label (`tab_label_text`)
- icon : will search by the `icon` property
- title : will search by the `title` property
- """
-
- if isinstance(name_tab, str):
- if search_by == "title":
- for tab_instance in self.tab_bar.parent.carousel.slides:
- if tab_instance.title_is_capital is True:
- _name_tab = name_tab.upper()
- else:
- _name_tab = name_tab
- if tab_instance.title == _name_tab:
- self.carousel.load_slide(tab_instance)
- return
- # Search by icon.
- elif search_by == "icon":
- for tab_instance in self.tab_bar.parent.carousel.slides:
- if tab_instance.icon == name_tab:
- self.carousel.load_slide(tab_instance)
- return
- # Search by title.
- else:
- for tab_instance in self.tab_bar.parent.carousel.slides:
- if tab_instance.tab_label_text == name_tab:
- self.carousel.load_slide(tab_instance)
- return
- raise ValueError(
- "switch_tab:\n\t"
- "name_tab not found in the tab list\n\t"
- f"search_by = {repr(search_by)} \n\t"
- f"name_tab = {repr(name_tab)} \n\t"
- )
+ Clock.schedule_once(self._check_panel_height)
+
+ def add_widget(self, widget, *args, **kwargs):
+ if isinstance(widget, MDTabsItem):
+ widget._tabs = self
+ widget.bind(on_release=self.set_active_item)
+ self.ids.container.add_widget(widget)
else:
- self.carousel.load_slide(name_tab.tab)
-
- def get_tab_list(self) -> list:
- """Returns a list of :class:`~MDTabsLabel` objects."""
-
- return self.tab_bar.layout.children[::-1]
-
- def get_slides(self) -> list:
- """Returns a list of user tab objects."""
-
- return self.carousel.slides
-
- def get_current_tab(self):
- """
- Returns current tab object.
-
- .. versionadded:: 1.0.0
- """
-
- return self.carousel.current_slide
-
- def add_widget(self, widget, index=0, canvas=None):
- # You can add only subclass of MDTabsBase.
- if not isinstance(widget, (MDTabsBase, MDTabsMain, MDTabsBar)):
- raise ValueError(
- f"MDTabs[{self.uid}].add_widget:\n\t"
- "The widget provided is not a subclass of MDTabsBase."
- )
- if len(self.children) >= 2:
- try:
- # FIXME: Can't set the value of the `no_ripple_effect`
- # and `ripple_duration` properties for widget.tab_label.
- widget.tab_label._no_ripple_effect = self.no_ripple_effect
- widget.tab_label.ripple_duration_in_slow = self.ripple_duration
- widget.tab_label.group = str(self)
- widget.tab_label.tab_bar = self.tab_bar
- widget.tab_label.font_name = self.font_name
- widget.tab_label.text_color_normal = (
- self.text_color_normal
- if self.text_color_normal
- else self.specific_secondary_text_color
- )
- widget.tab_label.text_color_active = (
- self.text_color_active
- if self.text_color_active
- else self.specific_text_color
- )
- self.bind(
- allow_stretch=widget.tab_label._update_text_size,
- fixed_tab_label_width=widget.tab_label._update_text_size,
- font_name=widget.tab_label.setter("font_name"),
- text_color_active=widget.tab_label.setter(
- "text_color_active"
- ),
- text_color_normal=widget.tab_label.setter(
- "text_color_normal"
- ),
- )
- Clock.schedule_once(widget.tab_label._update_text_size, 0)
- self.tab_bar.layout.add_widget(widget.tab_label)
- self.carousel.add_widget(widget)
- if self.force_title_icon_mode is True:
- widget.title_icon_mode = self.title_icon_mode
- Clock.schedule_once(
- self.tab_bar._label_request_indicator_update, 0
- )
- return
- except AttributeError:
- pass
- if isinstance(widget, (MDTabsMain, MDTabsBar)):
return super().add_widget(widget)
- def remove_widget(self, widget):
- # You can remove only subclass of MDTabsLabel or MDTabsBase.
- if not issubclass(widget.__class__, (MDTabsLabel, MDTabsBase)):
- raise MDTabsException(
- "MDTabs can remove only subclass of MDTabsLabel or MDTabsBase"
- )
- # If the widget is an instance of MDTabsBase, then the widget is
- # set as the widget's tab_label object.
- if issubclass(widget.__class__, MDTabsBase):
- slide = widget
- title_label = widget.tab_label
- else:
- # We already got the label, so we set the slide reference.
- slide = widget.tab
- title_label = widget
- # Set memory.
- # Search object next tab.
- # Clean all bindings to allow the widget to be collected.
- self.unbind(
- allow_stretch=title_label._update_text_size,
- fixed_tab_label_width=title_label._update_text_size,
- font_name=title_label.setter("font_name"),
- text_color_active=title_label.setter("text_color_active"),
- text_color_normal=title_label.setter("text_color_normal"),
- )
- self.carousel.remove_widget(slide)
- self.tab_bar.layout.remove_widget(title_label)
- # Clean the references.
- slide = None
- title_label = None
- widget = None
-
- def on_slide_progress(self, *args) -> None:
- """
- This event is deployed every available frame while the tab is scrolling.
- """
-
- def on_carousel_index(self, instance_tabs_carousel, index: int) -> None:
- """
- Called when the Tab index have changed.
-
- This event is deployed by the built in carousel of the class.
- """
-
- # When the index of the carousel change, update tab indicator,
- # select the current tab and reset threshold data.
- if instance_tabs_carousel.current_slide:
- current_tab_label = instance_tabs_carousel.current_slide.tab_label
- if current_tab_label.state == "normal":
- # current_tab_label._do_press()
- current_tab_label.dispatch("on_release")
- current_tab_label._release_group(self)
- current_tab_label.state = "down"
-
- if self.tab_indicator_type == "round":
- self.tab_indicator_height = self.tab_bar_height
- if index == 0:
- radius = [
- 0,
- self.tab_bar_height / 2,
- self.tab_bar_height / 2,
- 0,
- ]
- self.tab_bar.update_indicator(
- current_tab_label.x, current_tab_label.width, radius
- )
- elif index == len(self.get_tab_list()) - 1:
- radius = [
- self.tab_bar_height / 2,
- 0,
- 0,
- self.tab_bar_height / 2,
- ]
- self.tab_bar.update_indicator(
- current_tab_label.x, current_tab_label.width, radius
- )
- else:
- radius = [
- self.tab_bar_height / 2,
- ]
- self.tab_bar.update_indicator(
- current_tab_label.x, current_tab_label.width, radius
- )
- elif (
- self.tab_indicator_type == "fill"
- or self.tab_indicator_type == "line-round"
- or self.tab_indicator_type == "line-rect"
- ):
- self.tab_indicator_height = self.tab_bar_height
- self.tab_bar.update_indicator(
- current_tab_label.x, current_tab_label.width
- )
+ def set_active_item(self, item: MDTabsItem) -> None:
+ """Sets the active tab item."""
+
+ for widget in self.ids.container.children:
+ if item is widget:
+ widget.active = not widget.active
+
+ for widget_item in item.children:
+ if isinstance(widget_item, MDTabsItemLabel):
+ widget_item._active = widget.active
+ Animation(
+ text_color=self.theme_cls.primaryColor
+ if widget.active
+ else self.theme_cls.onSurfaceVariantColor,
+ d=0.2,
+ ).start(widget_item)
+ if isinstance(widget_item, MDTabsItemIcon):
+ widget_item._active = widget.active
+ Animation(
+ icon_color=self.theme_cls.primaryColor
+ if widget.active
+ else self.theme_cls.onSurfaceVariantColor,
+ d=0.2,
+ ).start(widget_item)
else:
- self.tab_bar.update_indicator(
- current_tab_label.x, current_tab_label.width
- )
-
- def on_ref_press(self, *args) -> None:
- """
- This event will be launched every time the user press a markup enabled
- label with a link or reference inside.
- """
-
- def on_tab_switch(self, *args) -> None:
- """This event is launched every time the current tab is changed."""
-
- def on_size(self, instance_tab, size: list) -> None:
- """Called when the application screen is resized."""
-
- if self.carousel.current_slide:
- self._update_indicator(self.carousel.current_slide.tab_label)
-
- def _update_tab_hint_x(self, *args):
- if not self.ids.layout.children:
- return
- if self.tab_hint_x is True:
- self.fixed_tab_label_width = self.width // len(
- self.ids.layout.children
- )
- self.allow_stretch = False
+ widget.active = False
+ for widget_item in widget.children:
+ widget_item._active = widget.active
+ if isinstance(widget_item, MDTabsItemLabel):
+ Animation(
+ text_color=self.theme_cls.onSurfaceVariantColor,
+ d=0.2,
+ ).start(widget_item)
+ if isinstance(widget_item, MDTabsItemIcon):
+ Animation(
+ icon_color=self.theme_cls.onSurfaceVariantColor,
+ d=0.2,
+ ).start(widget_item)
+
+ def on_size(self, instance, size) -> None:
+ """Fired when the application screen size changes."""
+
+ width, height = size
+ number_tabs = len(self.ids.container.children)
+
+ if self.allow_stretch:
+ for tab in self.ids.container.children:
+ tab.width = width / number_tabs
+
+ def _check_panel_height(self, *args):
+ if self.label_only:
+ self.height = dp(48)
else:
- self.allow_stretch = True
-
- def _parse_icon_mode(self, *args):
- if self.force_title_icon_mode is True:
- for slide in self.carousel.slides:
- slide.title_icon_mode = self.title_icon_mode
- if self.title_icon_mode == "Top":
- self.tab_bar_height = dp(72)
- else:
- self.tab_bar_height = dp(48)
-
- def _carousel_bind(self, interval):
- self.carousel.bind(on_slide_progress=self._on_slide_progress)
-
- def _on_slide_progress(self, *args):
- self.dispatch("on_slide_progress", args)
-
- def _update_indicator(self, current_tab_label):
- def update_indicator(interval):
- self.tab_bar.update_indicator(
- current_tab_label.x, current_tab_label.width
- )
-
- if not current_tab_label:
- current_tab_label = self.tab_bar.layout.children[-1]
- Clock.schedule_once(update_indicator)
-
- def _update_padding(self, layout, *args):
- if self.tab_hint_x is True:
- layout.padding = [0, 0]
- Clock.schedule_once(self._update_tab_hint_x)
- return True
- padding = [0, 0]
- # FIXME: It's not entirely clear why the `padding = [dp (52), 0]`
- # instruction is needed? This creates an extra 52px left padding and
- # looks like a bug. This instruction was added by the contributors in
- # previous commits and I have not yet figured out why this was done.
- # This is more efficient than to use sum([layout.children]).
- # width = layout.width - (layout.padding[0] * 2)
- # Forces the padding of the tab_bar when the tab_bar is scrollable.
- # if width > self.width:
- # padding = [dp(52), 0]
- # Set the new padding.
- layout.padding = padding
- # Update the indicator.
- if self.carousel.current_slide:
- self._update_indicator(self.carousel.current_slide.tab_label)
- Clock.schedule_once(
- lambda x: setattr(
- self.carousel.current_slide.tab_label, "state", "down"
- ),
- -1,
- )
- return True
+ self.height = dp(64)
diff --git a/kivymd/uix/taptargetview.py b/kivymd/uix/taptargetview.py
deleted file mode 100644
index 735de0d08..000000000
--- a/kivymd/uix/taptargetview.py
+++ /dev/null
@@ -1,857 +0,0 @@
-"""
-Components/TapTargetView
-========================
-
-.. seealso::
-
- `TapTargetView, GitHub `_
-
- `TapTargetView, Material archive `_
-
-.. rubric:: Provide value and improve engagement by introducing users to new
- features and functionality at relevant moments.
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-previous.gif
- :align: center
-
-Usage
------
-
-.. code-block:: python
-
- from kivy.lang import Builder
-
- from kivymd.app import MDApp
- from kivymd.uix.taptargetview import MDTapTargetView
-
- KV = '''
- Screen:
-
- MDFloatingActionButton:
- id: button
- icon: "plus"
- pos: 10, 10
- on_release: app.tap_target_start()
- '''
-
-
- class TapTargetViewDemo(MDApp):
- def build(self):
- screen = Builder.load_string(KV)
- self.tap_target_view = MDTapTargetView(
- widget=screen.ids.button,
- title_text="This is an add button",
- description_text="This is a description of the button",
- widget_position="left_bottom",
- )
-
- return screen
-
- def tap_target_start(self):
- if self.tap_target_view.state == "close":
- self.tap_target_view.start()
- else:
- self.tap_target_view.stop()
-
-
- TapTargetViewDemo().run()
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-usage.gif
- :align: center
-
-Widget position
----------------
-
-Sets the position of the widget relative to the floating circle.
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="right",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="left",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="top",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-top.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="bottom",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-bottom.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="left_top",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_top.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="right_top",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_top.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="left_bottom",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_bottom.png
- :align: center
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="right_bottom",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_bottom.png
- :align: center
-
-If you use ``the widget_position = "center"`` parameter then you must
-definitely specify the :attr:`~MDTapTargetView.title_position`.
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- widget_position="center",
- title_position="left_top",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-center.png
- :align: center
-
-Text options
-------------
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- title_text="Title text",
- description_text="Description text",
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-text.png
- :align: center
-
-
-You can use the following options to control font size, color, and boldness:
-
-- :attr:`~MDTapTargetView.title_text_size`
-- :attr:`~MDTapTargetView.title_text_color`
-- :attr:`~MDTapTargetView.title_text_bold`
-- :attr:`~MDTapTargetView.description_text_size`
-- :attr:`~MDTapTargetView.description_text_color`
-- :attr:`~MDTapTargetView.description_text_bold`
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- title_text="Title text",
- title_text_size="36sp",
- description_text="Description text",
- description_text_color=[1, 0, 0, 1]
- )
-
-.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-text-option.png
- :align: center
-
-But you can also use markup to set these values.
-
-.. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- title_text="[size=36]Title text[/size]",
- description_text="[color=#ff0000ff]Description text[/color]",
- )
-
-Events control
---------------
-
-.. code-block:: python
-
- self.tap_target_view.bind(on_open=self.on_open, on_close=self.on_close)
-
-.. code-block:: python
-
- def on_open(self, instance_tap_target_view):
- '''Called at the time of the start of the widget opening animation.'''
-
- print("Open", instance_tap_target_view)
-
- def on_close(self, instance_tap_target_view):
- '''Called at the time of the start of the widget closed animation.'''
-
- print("Close", instance_tap_target_view)
-
-.. Note:: See other parameters in the :class:`~MDTapTargetView` class.
-"""
-
-from kivy.animation import Animation
-from kivy.event import EventDispatcher
-from kivy.graphics import Color, Ellipse, Rectangle
-from kivy.logger import Logger
-from kivy.metrics import dp
-from kivy.properties import (
- BooleanProperty,
- ListProperty,
- NumericProperty,
- ObjectProperty,
- OptionProperty,
- StringProperty,
-)
-from kivy.uix.label import Label
-
-from kivymd.theming import ThemableBehavior
-
-
-class MDTapTargetView(ThemableBehavior, EventDispatcher):
- """Rough try to mimic the working of Android's TapTargetView.
-
- :Events:
- :attr:`on_open`
- Called at the time of the start of the widget opening animation.
- :attr:`on_close`
- Called at the time of the start of the widget closed animation.
- """
-
- widget = ObjectProperty()
- """
- Widget to add ``TapTargetView`` upon.
-
- :attr:`widget` is an :class:`~kivy.properties.ObjectProperty`
- and defaults to `None`.
- """
-
- outer_radius = NumericProperty(dp(200))
- """
- Radius for outer circle.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-radius.png
- :align: center
-
- :attr:`outer_radius` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `dp(200)`.
- """
-
- outer_circle_color = ListProperty()
- """
- Color for the outer circle in ``rgb`` format.
-
- .. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- outer_circle_color=(1, 0, 0)
- )
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-circle-color.png
- :align: center
-
- :attr:`outer_circle_color` is an :class:`~kivy.properties.ListProperty`
- and defaults to ``theme_cls.primary_color``.
- """
-
- outer_circle_alpha = NumericProperty(0.96)
- """
- Alpha value for outer circle.
-
- :attr:`outer_circle_alpha` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `0.96`.
- """
-
- target_radius = NumericProperty(dp(45))
- """
- Radius for target circle.
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-radius.png
- :align: center
-
- :attr:`target_radius` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `dp(45)`.
- """
-
- target_circle_color = ListProperty([1, 1, 1])
- """
- Color for target circle in ``rgb`` format.
-
- .. code-block:: python
-
- self.tap_target_view = MDTapTargetView(
- ...
- target_circle_color=(1, 0, 0)
- )
-
- .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-circle-color.png
- :align: center
-
- :attr:`target_circle_color` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[1, 1, 1]`.
- """
-
- title_text = StringProperty()
- """
- Title to be shown on the view.
-
- :attr:`title_text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- title_text_size = NumericProperty(dp(25))
- """
- Text size for title.
-
- :attr:`title_text_size` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `dp(25)`.
- """
-
- title_text_color = ListProperty([1, 1, 1, 1])
- """
- Text color for title.
-
- :attr:`title_text_color` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[1, 1, 1, 1]`.
- """
-
- title_text_bold = BooleanProperty(True)
- """
- Whether title should be bold.
-
- :attr:`title_text_bold` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- description_text = StringProperty()
- """
- Description to be shown below the title (keep it short).
-
- :attr:`description_text` is an :class:`~kivy.properties.StringProperty`
- and defaults to `''`.
- """
-
- description_text_size = NumericProperty(dp(20))
- """
- Text size for description text.
-
- :attr:`description_text_size` is an :class:`~kivy.properties.NumericProperty`
- and defaults to `dp(20)`.
- """
-
- description_text_color = ListProperty([0.9, 0.9, 0.9, 1])
- """
- Text size for description text.
-
- :attr:`description_text_color` is an :class:`~kivy.properties.ListProperty`
- and defaults to `[0.9, 0.9, 0.9, 1]`.
- """
-
- description_text_bold = BooleanProperty(False)
- """
- Whether description should be bold.
-
- :attr:`description_text_bold` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- draw_shadow = BooleanProperty(False)
- """
- Whether to show shadow.
-
- :attr:`draw_shadow` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- cancelable = BooleanProperty(False)
- """
- Whether clicking outside the outer circle dismisses the view.
-
- :attr:`cancelable` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- widget_position = OptionProperty(
- "left",
- options=[
- "left",
- "right",
- "top",
- "bottom",
- "left_top",
- "right_top",
- "left_bottom",
- "right_bottom",
- "center",
- ],
- )
- """
- Sets the position of the widget on the :attr:`~outer_circle`. Available options are
- `'left`', `'right`', `'top`', `'bottom`', `'left_top`', `'right_top`',
- `'left_bottom`', `'right_bottom`', `'center`'.
-
- :attr:`widget_position` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'left'`.
- """
-
- title_position = OptionProperty(
- "auto",
- options=[
- "auto",
- "left",
- "right",
- "top",
- "bottom",
- "left_top",
- "right_top",
- "left_bottom",
- "right_bottom",
- ],
- )
- """
- Sets the position of :attr`~title_text` on the outer circle. Only works if
- :attr`~widget_position` is set to `'center'`. In all other cases, it
- calculates the :attr`~title_position` itself.
- Must be set to other than `'auto`' when :attr`~widget_position` is set
- to `'center`'.
-
- Available options are `'auto'`, `'left`', `'right`', `'top`', `'bottom`',
- `'left_top`', `'right_top`', `'left_bottom`', `'right_bottom`', `'center`'.
-
- :attr:`title_position` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'auto'`.
- """
-
- stop_on_outer_touch = BooleanProperty(False)
- """
- Whether clicking on outer circle stops the animation.
-
- :attr:`stop_on_outer_touch` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `False`.
- """
-
- stop_on_target_touch = BooleanProperty(True)
- """
- Whether clicking on target circle should stop the animation.
-
- :attr:`stop_on_target_touch` is an :class:`~kivy.properties.BooleanProperty`
- and defaults to `True`.
- """
-
- state = OptionProperty("close", options=["close", "open"])
- """
- State of :class:`~MDTapTargetView`.
-
- :attr:`state` is an :class:`~kivy.properties.OptionProperty`
- and defaults to `'close'`.
- """
-
- _outer_radius = NumericProperty(0)
- _target_radius = NumericProperty(0)
-
- __elevation = 0
-
- def __init__(self, **kwargs):
- self.ripple_max_dist = dp(90)
- self.on_outer_radius(self, self.outer_radius)
- self.on_target_radius(self, self.target_radius)
- self.anim_ripple = None
-
- self.core_title_text = Label(
- markup=True, size_hint=(None, None), bold=self.title_text_bold
- )
- self.core_title_text.bind(
- texture_size=self.core_title_text.setter("size")
- )
- self.core_description_text = Label(markup=True, size_hint=(None, None))
- self.core_description_text.bind(
- texture_size=self.core_description_text.setter("size")
- )
-
- super().__init__(**kwargs)
- self.register_event_type("on_outer_touch")
- self.register_event_type("on_target_touch")
- self.register_event_type("on_outside_click")
- self.register_event_type("on_open")
- self.register_event_type("on_close")
-
- if not self.outer_circle_color:
- self.outer_circle_color = self.theme_cls.primary_color[:-1]
-
- def start(self, *args):
- """Starts widget opening animation."""
-
- self._initialize()
- self._animate_outer()
- self.state = "open"
- self.core_title_text.opacity = 1
- self.core_description_text.opacity = 1
- self.dispatch("on_open")
-
- elevation = getattr(self.widget, "elevation", None)
- if elevation:
- self.__elevation = elevation
- self.widget.elevation = 0
-
- def stop(self, *args):
- """Starts widget close animation."""
-
- # It needs a better implementation.
- if self.anim_ripple is not None:
- self.anim_ripple.unbind(on_complete=self._repeat_ripple)
- self.core_title_text.opacity = 0
- self.core_description_text.opacity = 0
- anim = Animation(
- d=0.15,
- t="in_cubic",
- **dict(
- zip(
- ["_outer_radius", "_target_radius", "target_ripple_radius"],
- [0, 0, 0],
- )
- ),
- )
- anim.bind(on_complete=self._after_stop)
- anim.start(self.widget)
-
- def on_open(self, *args):
- """Called at the time of the start of the widget opening animation."""
-
- def on_close(self, *args):
- """Called at the time of the start of the widget closed animation."""
-
- def on_draw_shadow(self, instance, value):
- Logger.warning(
- "The shadow adding method will be implemented in future versions"
- )
-
- def on_description_text(self, instance, value):
- self.core_description_text.text = value
-
- def on_description_text_size(self, instance, value):
- self.core_description_text.font_size = value
-
- def on_description_text_bold(self, instance, value):
- self.core_description_text.bold = value
-
- def on_title_text(self, instance, value):
- self.core_title_text.text = value
-
- def on_title_text_size(self, instance, value):
- self.core_title_text.font_size = value
-
- def on_title_text_bold(self, instance, value):
- self.core_title_text.bold = value
-
- def on_outer_radius(self, instance, value):
- self._outer_radius = self.outer_radius * 2
-
- def on_target_radius(self, instance, value):
- self._target_radius = self.target_radius * 2
-
- def on_target_touch(self):
- if self.stop_on_target_touch:
- self.stop()
-
- def on_outer_touch(self):
- if self.stop_on_outer_touch:
- self.stop()
-
- def on_outside_click(self):
- if self.cancelable:
- self.stop()
-
- def _initialize(self):
- setattr(self.widget, "_outer_radius", 0)
- setattr(self.widget, "_target_radius", 0)
- setattr(self.widget, "target_ripple_radius", 0)
- setattr(self.widget, "target_ripple_alpha", 0)
-
- # Bind some function on widget event when this function is called
- # instead of when the class itself is initialized to prevent all
- # widgets of all instances to get bind at once and start messing up.
- self.widget.bind(on_touch_down=self._some_func)
-
- def _draw_canvas(self):
- _pos = self._ttv_pos()
- self.widget.canvas.before.remove_group("ttv_group")
-
- with self.widget.canvas.before:
- # Outer circle.
- Color(
- *self.outer_circle_color,
- self.outer_circle_alpha,
- group="ttv_group",
- )
- _rad1 = self.widget._outer_radius
- Ellipse(size=(_rad1, _rad1), pos=_pos[0], group="ttv_group")
-
- # Title text.
- Color(*self.title_text_color, group="ttv_group")
- Rectangle(
- size=self.core_title_text.texture.size,
- texture=self.core_title_text.texture,
- pos=_pos[1],
- group="ttv_group",
- )
-
- # Description text.
- Color(*self.description_text_color, group="ttv_group")
- Rectangle(
- size=self.core_description_text.texture.size,
- texture=self.core_description_text.texture,
- pos=(
- _pos[1][0],
- _pos[1][1] - self.core_description_text.size[1] - 5,
- ),
- group="ttv_group",
- )
-
- # Target circle.
- Color(*self.target_circle_color, group="ttv_group")
- _rad2 = self.widget._target_radius
- Ellipse(
- size=(_rad2, _rad2),
- pos=(
- self.widget.x - (_rad2 / 2 - self.widget.size[0] / 2),
- self.widget.y - (_rad2 / 2 - self.widget.size[0] / 2),
- ),
- group="ttv_group",
- )
-
- # Target ripple.
- Color(
- *self.target_circle_color,
- self.widget.target_ripple_alpha,
- group="ttv_group",
- )
- _rad3 = self.widget.target_ripple_radius
- Ellipse(
- size=(_rad3, _rad3),
- pos=(
- self.widget.x - (_rad3 / 2 - self.widget.size[0] / 2),
- self.widget.y - (_rad3 / 2 - self.widget.size[0] / 2),
- ),
- group="ttv_group",
- )
-
- def _after_stop(self, *args):
- self.widget.canvas.before.remove_group("ttv_group")
- args[0].stop_all(self.widget)
-
- elevation = getattr(self.widget, "elevation", None)
- if elevation:
- self.widget.elevation = self.__elevation
-
- self.dispatch("on_close")
-
- # Don't forget to unbind the function or it'll mess
- # up with other next bindings.
- self.widget.unbind(on_touch_down=self._some_func)
- self.state = "close"
-
- def _fix_elev(self):
- with self.widget.canvas.before:
- Color(a=self.widget._soft_shadow_a)
- Rectangle(
- texture=self.widget._soft_shadow_texture,
- size=self.widget._soft_shadow_size,
- pos=self.widget._soft_shadow_pos,
- )
- Color(a=self.widget._hard_shadow_a)
- Rectangle(
- texture=self.widget._hard_shadow_texture,
- size=self.widget._hard_shadow_size,
- pos=self.widget._hard_shadow_pos,
- )
- Color(a=1)
-
- def _animate_outer(self):
- anim = Animation(
- d=0.2,
- t="out_cubic",
- **dict(
- zip(
- ["_outer_radius", "_target_radius"],
- [self._outer_radius, self._target_radius],
- )
- ),
- )
- anim.cancel_all(self.widget)
- anim.bind(on_progress=lambda x, y, z: self._draw_canvas())
- anim.bind(on_complete=self._animate_ripple)
- anim.start(self.widget)
- setattr(self.widget, "target_ripple_radius", self._target_radius)
- setattr(self.widget, "target_ripple_alpha", 1)
-
- def _animate_ripple(self, *args):
- self.anim_ripple = Animation(
- d=1,
- t="in_cubic",
- target_ripple_radius=self._target_radius + self.ripple_max_dist,
- target_ripple_alpha=0,
- )
- self.anim_ripple.stop_all(self.widget)
- self.anim_ripple.bind(on_progress=lambda x, y, z: self._draw_canvas())
- self.anim_ripple.bind(on_complete=self._repeat_ripple)
- self.anim_ripple.start(self.widget)
-
- def _repeat_ripple(self, *args):
- setattr(self.widget, "target_ripple_radius", self._target_radius)
- setattr(self.widget, "target_ripple_alpha", 1)
- self._animate_ripple()
-
- def _some_func(self, wid, touch):
- """
- This function decides which one to dispatch based on the touch
- position.
- """
-
- if self._check_pos_target(touch.pos):
- self.dispatch("on_target_touch")
- elif self._check_pos_outer(touch.pos):
- self.dispatch("on_outer_touch")
- else:
- self.dispatch("on_outside_click")
-
- def _check_pos_outer(self, pos):
- """
- Checks if a given `pos` coordinate is within the :attr:`~outer_radius`.
- """
-
- cx = self.circ_pos[0] + self._outer_radius / 2
- cy = self.circ_pos[1] + self._outer_radius / 2
- r = self._outer_radius / 2
- h, k = pos
-
- lhs = (cx - h) ** 2 + (cy - k) ** 2
- rhs = r**2
- if lhs <= rhs:
- return True
- return False
-
- def _check_pos_target(self, pos):
- """
- Checks if a given `pos` coordinate is within the
- :attr:`~target_radius`.
- """
-
- cx = self.widget.pos[0] + self.widget.width / 2
- cy = self.widget.pos[1] + self.widget.height / 2
- r = self._target_radius / 2
- h, k = pos
-
- lhs = (cx - h) ** 2 + (cy - k) ** 2
- rhs = r**2
- if lhs <= rhs:
- return True
- return False
-
- def _ttv_pos(self):
- """
- Calculates the `pos` value for outer circle and text
- based on the position provided.
-
- :returns: A tuple containing pos for the circle and text.
- """
-
- _rad1 = self.widget._outer_radius
- _center_x = self.widget.x - (_rad1 / 2 - self.widget.size[0] / 2)
- _center_y = self.widget.y - (_rad1 / 2 - self.widget.size[0] / 2)
-
- if self.widget_position == "left":
- circ_pos = (_center_x + _rad1 / 3, _center_y)
- title_pos = (_center_x + _rad1 / 1.4, _center_y + _rad1 / 1.4)
- elif self.widget_position == "right":
- circ_pos = (_center_x - _rad1 / 3, _center_y)
- title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 1.4)
- elif self.widget_position == "top":
- circ_pos = (_center_x, _center_y - _rad1 / 3)
- title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4)
- elif self.widget_position == "bottom":
- circ_pos = (_center_x, _center_y + _rad1 / 3)
- title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 1.2)
- # Corner ones need to be at a little smaller distance
- # than edge ones that's why _rad1/4.
- elif self.widget_position == "left_top":
- circ_pos = (_center_x + _rad1 / 4, _center_y - _rad1 / 4)
- title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 4)
- elif self.widget_position == "right_top":
- circ_pos = (_center_x - _rad1 / 4, _center_y - _rad1 / 4)
- title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 4)
- elif self.widget_position == "left_bottom":
- circ_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4)
- title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.2)
- elif self.widget_position == "right_bottom":
- circ_pos = (_center_x - _rad1 / 4, _center_y + _rad1 / 4)
- title_pos = (_center_x, _center_y + _rad1 / 1.2)
- else:
- # Center.
- circ_pos = (_center_x, _center_y)
-
- if self.title_position == "auto":
- raise ValueError(
- "widget_position='center' requires title_position to be set."
- )
- elif self.title_position == "left":
- title_pos = (_center_x + _rad1 / 10, _center_y + _rad1 / 2)
- elif self.title_position == "right":
- title_pos = (_center_x + _rad1 / 1.6, _center_y + _rad1 / 2)
- elif self.title_position == "top":
- title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 1.3)
- elif self.title_position == "bottom":
- title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 4)
- elif self.title_position == "left_top":
- title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 1.4)
- elif self.title_position == "right_top":
- title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.3)
- elif self.title_position == "left_bottom":
- title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 4)
- elif self.title_position == "right_bottom":
- title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 3.5)
- else:
- raise ValueError(
- f"'{self.title_position}'"
- f"is not a valid value for title_position"
- )
-
- self.circ_pos = circ_pos
- return circ_pos, title_pos
diff --git a/kivymd/uix/templates/__init__.py b/kivymd/uix/templates/__init__.py
deleted file mode 100644
index 0e627d776..000000000
--- a/kivymd/uix/templates/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
-Templates
-=========
-
-Base classes for controlling the scale, rotation of the widget, etc.
-"""
-
-from .rotatewidget import RotateWidget
-from .scalewidget import ScaleWidget
-from .stencilwidget import StencilWidget
diff --git a/kivymd/uix/templates/rotatewidget/__init__.py b/kivymd/uix/templates/rotatewidget/__init__.py
deleted file mode 100644
index 1fb518fe7..000000000
--- a/kivymd/uix/templates/rotatewidget/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .rotatewidget import RotateWidget
diff --git a/kivymd/uix/templates/rotatewidget/rotatewidget.py b/kivymd/uix/templates/rotatewidget/rotatewidget.py
deleted file mode 100644
index 9416a5f33..000000000
--- a/kivymd/uix/templates/rotatewidget/rotatewidget.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
-Templates/RotateWidget
-======================
-
-.. deprecated:: 1.0.0
-
-.. note:: `RotateWidget` class has been deprecated. Please use
- `RotateBahavior `_
- class instead.
-"""
-
-__all__ = ("RotateWidget",)
-
-from kivy import Logger
-
-from kivymd.uix.behaviors import RotateBehavior
-
-
-class RotateWidget(RotateBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior`
- class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `RotateWidget` class has been deprecated. "
- "Use the `RotateBehavior` class instead."
- )
diff --git a/kivymd/uix/templates/scalewidget/__init__.py b/kivymd/uix/templates/scalewidget/__init__.py
deleted file mode 100644
index 45d304dde..000000000
--- a/kivymd/uix/templates/scalewidget/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .scalewidget import ScaleWidget
diff --git a/kivymd/uix/templates/scalewidget/scalewidget.py b/kivymd/uix/templates/scalewidget/scalewidget.py
deleted file mode 100644
index 3fe69850f..000000000
--- a/kivymd/uix/templates/scalewidget/scalewidget.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
-Templates/ScaleWidget
-=====================
-
-.. deprecated:: 1.1.0
-
-Base class for controlling the scale of the widget.
-
-.. note:: `ScaleWidget` class has been deprecated. Please use
- `ScaleBehavior `_
- class instead.
-"""
-
-__all__ = ("ScaleWidget",)
-
-from kivy import Logger
-
-from kivymd.uix.behaviors import ScaleBehavior
-
-
-class ScaleWidget(ScaleBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior`
- class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `ScaleWidget` class has been deprecated. "
- "Use the `ScaleBehavior` class instead."
- )
diff --git a/kivymd/uix/templates/stencilwidget/__init__.py b/kivymd/uix/templates/stencilwidget/__init__.py
deleted file mode 100644
index a249f510a..000000000
--- a/kivymd/uix/templates/stencilwidget/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .stencilwidget import StencilWidget
diff --git a/kivymd/uix/templates/stencilwidget/stencilwidget.py b/kivymd/uix/templates/stencilwidget/stencilwidget.py
deleted file mode 100644
index a23158881..000000000
--- a/kivymd/uix/templates/stencilwidget/stencilwidget.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
-Templates/StencilWidget
-=======================
-
-.. deprecated:: 1.1.0
-
-Base class for controlling the stencil instructions of the widget.
-
-.. note:: `StencilWidget` class has been deprecated. Please use
- `StencilBehavior `_
- class instead.
-"""
-
-__all__ = ("StencilWidget",)
-
-from kivy import Logger
-
-from kivymd.uix.behaviors import StencilBehavior
-
-
-class StencilWidget(StencilBehavior):
- """
- .. deprecated:: 1.1.0
- Use :class:`~kivymd.uix.behaviors.scale_behavior.StencilBehavior`
- class instead.
- """
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- Logger.warning(
- "KivyMD: "
- "The `StencilWidget` class has been deprecated. "
- "Use the `StencilBehavior` class instead."
- )
diff --git a/kivymd/uix/textfield/__init__.py b/kivymd/uix/textfield/__init__.py
index d3786ddf6..cb6b57a48 100644
--- a/kivymd/uix/textfield/__init__.py
+++ b/kivymd/uix/textfield/__init__.py
@@ -1,2 +1,9 @@
# NOQA F401
-from .textfield import MDTextField, MDTextFieldRect
+from .textfield import (
+ MDTextField,
+ MDTextFieldHelperText,
+ MDTextFieldMaxLengthText,
+ MDTextFieldHintText,
+ MDTextFieldLeadingIcon,
+ MDTextFieldTrailingIcon,
+)
diff --git a/kivymd/uix/textfield/textfield.kv b/kivymd/uix/textfield/textfield.kv
index 1ed7597e6..ef8d9bf95 100644
--- a/kivymd/uix/textfield/textfield.kv
+++ b/kivymd/uix/textfield/textfield.kv
@@ -1,154 +1,179 @@
+#:import theme_font_styles kivymd.font_definitions.theme_font_styles
+
+
input_filter: self.field_filter
do_backspace: self.do_backspace
canvas.before:
Clear
-
- # "round" mode.
- Color:
- group: "round-color"
- rgba: self._fill_color if self.mode == "round" else (0, 0, 0, 0)
- Ellipse:
- angle_start: 180
- angle_end: 360
- pos: self.x - self.height / 2 + dp(18), self.y
- size: self.height, self.height
- Ellipse:
- angle_start: 360
- angle_end: 540
- pos: (self.width - dp(18)) + self.x - self.height / 2.0, self.y
- size: self.height, self.height
- Rectangle:
- pos: self.x + dp(14), self.y
- size: self.width - dp(28), self.height
-
+ # Filled mode.
Color:
+ group: "fill-color"
rgba:
( \
- (self.line_color_focus if not self.error else self.error_color) \
- if self.focus else ( \
- self.theme_cls.disabled_hint_text_color \
- if not self.line_color_normal else \
- self.line_color_normal) \
+ ( \
+ self.theme_cls.surfaceVariantColor \
+ if self.theme_bg_color == "Primary" else
+ ( \
+ self.fill_color_normal \
+ if self.fill_color_normal else \
+ self.theme_cls.surfaceVariantColor \
) \
- if self.mode == "round" else \
- (0, 0, 0, 0)
- SmoothLine:
- width: dp(1)
- rounded_rectangle:
- self.x, \
- self.y, \
- self.width, \
- self.height, \
- self.height / 2
-
- # "fill" mode.
- Color:
- group: "fill-color"
- rgba: self._fill_color if self.mode == "fill" else (0, 0, 0, 0)
+ ) \
+ if not self.focus else \
+ ( \
+ self.theme_cls.surfaceVariantColor \
+ if self.theme_bg_color == "Primary" else
+ ( \
+ self.fill_color_focus \
+ if self.fill_color_focus else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ ) \
+ ) \
+ if self.mode == "filled" else self.theme_cls.transparentColor
RoundedRectangle:
pos: self.x, self.y
size: self.width, self.height
- radius: self.radius
+ radius: self.radius[0], self.radius[1], 0, 0
- # Static underline texture.
+ # Active indicator.
Color:
- group: "static-underline-color"
+ group: "active-indicator-color"
rgba:
- (self._line_color_normal \
- if self.line_color_normal else self.theme_cls.divider_color) \
- if self.mode == "line" else (0, 0, 0, 0)
+ ( \
+ ( \
+ ( \
+ ( \
+ self.theme_cls.onSurfaceVariantColor \
+ if self.theme_line_color == "Primary" else \
+ ( \
+ self.line_color_normal \
+ if self.line_color_normal else \
+ self.theme_cls.onSurfaceVariantColor \
+ ) \
+ ) \
+ if not self.focus else \
+ ( \
+ self.theme_cls.primaryColor \
+ if self.theme_line_color == "Primary" else \
+ ( \
+ self.line_color_focus \
+ if self.line_color_focus else \
+ self.theme_cls.primaryColor \
+ ) \
+ ) \
+ ) \
+ if not self.error else self._get_error_color()
+ ) \
+ if not self.disabled else self.theme_cls.disabledTextColor \
+ ) \
+ if self.mode == "filled" else self.theme_cls.transparentColor
Line:
- points: self.x, self.y + dp(16), self.x + self.width, self.y + dp(16)
- width: 1
- dash_length: dp(3)
- dash_offset: 2 if self.disabled else 0
+ width: self._indicator_height
+ points:
+ self.x + dp(1 if self.focus else 0), \
+ self.y, \
+ self.x - dp(1 if self.focus else 0) + self.width, \
+ self.y
- # Active underline (on focus) texture.
+ # Helper text texture.
Color:
- group: "active-underline-color"
- rgba:
- self._line_color_focus \
- if self.mode in ("line", "fill") and self.active_line \
- else (0, 0, 0, 0)
+ group: "helper-text-color"
Rectangle:
- size: self._underline_width, dp(1)
+ texture:
+ self._helper_text_label.texture \
+ if self._helper_text_label else \
+ None
+ size:
+ self._helper_text_label.texture_size \
+ if self._helper_text_label else \
+ (0, 0)
pos:
- self.center_x - (self._underline_width / 2), \
- self.y + (dp(16) if self.mode != "fill" else 0)
+ self.x + (dp(16) if self.mode == "filled" else \
+ (0 if self.mode == "filled" else dp(12))), \
+ self.y + dp(-18)
- # Helper text texture.
+ # Leading icon texture.
Color:
- group: "helper-text-color"
- rgba:
- self.theme_cls.disabled_hint_text_color \
- if self.disabled else \
- self._helper_text_color
+ group: "leading-icons-color"
Rectangle:
- texture: self._helper_text_label.texture
- size: self._helper_text_label.texture_size
+ texture:
+ self._leading_icon.texture if self._leading_icon else None
+ size:
+ self._leading_icon.texture_size \
+ if self._leading_icon else \
+ (0, 0)
pos:
- self.x + (dp(16) if self.mode == "fill" else \
- (0 if self.mode not in ("round", "rectangle") else dp(12))), \
- self.y + (dp(-18) if self.mode in ("fill", "rectangle", "round") else dp(-2))
+ ( \
+ ( \
+ self.x + \
+ ( \
+ ( \
+ 0 if self.mode != "outlined" else dp(12) \
+ ) \
+ if self.mode != "filled" else \
+ ( \
+ dp(4) if not self._leading_icon else dp(16) \
+ ) \
+ ), \
- # Right/left icon texture.
+ self.center_y - self._leading_icon.texture_size[1] / 2 \
+ ) \
+ ) \
+ if self._leading_icon else (0, 0)
+
+ # Trailing icon texture.
Color:
- group: "right-left-icons-color"
- rgba:
- self.theme_cls.disabled_hint_text_color \
- if self.disabled else \
- (self._icon_right_color if self.icon_right else self._icon_left_color)
+ group: "trailing-icons-color"
Rectangle:
texture:
- self._icon_right_label.texture if self.icon_right else self._icon_left_label.texture
+ self._trailing_icon.texture if self._trailing_icon else None
size:
- (0, 0) if (not self.icon_right and not self.icon_left) else \
- (self._icon_right_label.texture_size if self.icon_right else self._icon_left_label.texture_size)
+ self._trailing_icon.texture_size \
+ if self._trailing_icon else \
+ (0, 0)
pos:
( \
- (self.width + self.x - (0 if self.mode != "round" else dp(4))) - \
- (self._icon_right_label.texture_size[1]) - dp(8), \
- self.center[1] - self._icon_right_label.texture_size[1] / 2 + ((dp(8) \
- if self.mode != "round" else 0) if self.mode != "fill" else 0) \
- if self.mode != "rectangle" else \
- self.center[1] - self._icon_right_label.texture_size[1] / 2 - dp(4) \
+ (self.width + self.x) - \
+ (self._trailing_icon.texture_size[1]) - dp(14), \
+ self.center_y - self._trailing_icon.texture_size[1] / 2 \
) \
- if self.icon_right else \
- ( \
- self.x + ((dp((0 if self.mode != "round" else 12)) \
- if self.mode != "rectangle" else dp(12)) \
- if self.mode != "fill" else (dp(4) if not self.icon_left else dp(16))), \
-
- self.center[1] - self._icon_left_label.texture_size[1] / 2 + (((dp(4) \
- if self.mode != "round" else 0) if self.mode not in ("rectangle", "fill") \
- else dp(8)) if self.mode != "fill" else 0) \
- if self.mode != "rectangle" else \
- self.center[1] - self._icon_left_label.texture_size[1] / 2 - dp(4) \
- )
+ if self._trailing_icon else (0, 0)
# Max length texture.
Color:
group: "max-length-color"
- rgba:
- self.theme_cls.disabled_hint_text_color \
- if self.disabled else \
- self._max_length_text_color
Rectangle:
- texture: self._max_length_label.texture
- size: self._max_length_label.texture_size
+ group: "max-length-rect"
+ texture:
+ self._max_length_label.texture \
+ if self._max_length_label else \
+ None
+ size:
+ self._max_length_label.texture_size \
+ if self._max_length_label else \
+ (0, 0)
pos:
- self.x + self.width - self._max_length_label.texture_size[0] - dp(12), \
- self.y - (dp(2) if self.mode == "line" else dp(18))
+ ( \
+ (self.x + self.width) \
+ - (self._max_length_label.texture_size[0] + dp(16)), \
+ self.y - dp(18) \
+ ) \
+ if self._max_length_label else (0, 0)
# Cursor blink.
Color:
rgba:
( \
- (self.text_color_focus if not self.error else self.error_color) \
+ ( \
+ self.theme_cls.primaryColor \
+ if not self.error else \
+ self._get_error_color() \
+ ) \
if self.focus \
- else self._text_color_normal \
+ else self.theme_cls.primaryColor \
) \
if self.focus and not self._cursor_blink \
else \
@@ -157,161 +182,225 @@
pos: (int(x) for x in self.cursor_pos)
size: 1, -self.line_height
- # "rectangle" mode
+ # Outlined mode.
Color:
group: "rectangle-color"
rgba:
( \
- (self.line_color_focus if not self.error else self.error_color) \
- if self.focus else \
- self.line_color_normal \
- ) \
- if self.mode == "rectangle" else \
- (0, 0, 0, 0)
- SmoothLine:
- width: dp(1)
- rounded_rectangle:
- self.x, \
- self.y, \
- self.width, \
- self.height - self._hint_text_label.texture_size[1] // 2, \
- root.radius[0]
-
- # The background color line of the widget on which the text field
- # is placed (for background hint text texture).
- Color:
- rgba:
( \
( \
- self.parent.md_bg_color \
- if hasattr(self.parent, "md_bg_color") \
- and self.parent.md_bg_color != [1, 1, 1, 0] else \
- self.theme_cls.bg_normal \
+ ( \
+ self.theme_cls.primaryColor \
+ if self.theme_line_color == "Primary" else \
+ self.line_color_focus \
+ if self.line_color_focus else \
+ self.theme_cls.primaryColor \
) \
if self.focus else \
( \
- (0, 0, 0, 0) if not self.text else \
- ( \
- self.parent.md_bg_color \
- if hasattr(self.parent, "md_bg_color") \
- and self.parent.md_bg_color != [1, 1, 1, 0] else \
- self.theme_cls.bg_normal \
+ self.theme_cls.outlineColor \
+ if self.theme_line_color == "Primary" else \
+ self.line_color_normal \
+ if self.line_color_normal else \
+ self.theme_cls.outlineColor \
) \
) \
+ if not self.error else self._get_error_color() \
) \
- if self.mode == "rectangle" else \
- (0, 0, 0, 0)
+ if not self.disabled else \
+ app.theme_cls.onSurfaceColor[:-1] + \
+ [self.text_field_opacity_value_disabled_line]
+ ) \
+ if self.mode != "filled" else self.theme_cls.transparentColor
+
+ # Top right corner.
+ # ------------------------------------------------------─╮
+ SmoothLine:
+ width: self._outline_height
+ circle:
+ self.x + self.width - self.radius[1], \
+ self.y + self.height - self.radius[1], \
+ self.radius[1], \
+ 0, \
+ 90
+
+ # Bottom corner.
+ # -----------------------------------------------------─╯
+ SmoothLine:
+ width: self._outline_height
+ circle:
+ self.x + self.width - self.radius[2], \
+ self.y + self.radius[2], \
+ -self.radius[2], \
+ 0, \
+ -90
+
+ # Top left corner.
+ # ╭─------------------------------------------------------
+ SmoothLine:
+ width: self._outline_height
+ circle:
+ self.x + self.radius[0], \
+ self.y + self.height - self.radius[0], \
+ -self.radius[0], \
+ 180, \
+ 90
+
+ # Bottom left corner.
+ # ╰─-----------------------------------------------------
+ SmoothLine:
+ width: self._outline_height
+ circle:
+ self.x + self.radius[3], \
+ self.y + self.radius[3], \
+ -self.radius[3], \
+ 0, \
+ 90
+
+ # Left vertical line.
+ # │
+ # │
+ # ╰─------------------------------------------------------
+ SmoothLine:
+ width: self._outline_height
+ points:
+ self.x, \
+ self.y + self.radius[3], \
+ self.x, \
+ self.y + (self.height - self.radius[0])
+
+ # Right vertical line.
+ # │
+ # │
+ # -----------------------------------------------------─╯
+ SmoothLine:
+ width: self._outline_height
+ points:
+ self.x + self.width, \
+ self.y + self.radius[2], \
+ self.x + self.width, \
+ self.y + (self.height - self.radius[1])
+
+ # Bottom horizontal line.
+ # ——————————————————————————————————————————————————————─╯
SmoothLine:
- width: dp(2)
+ width: self._outline_height
points:
- self.x + dp(10), \
- self.top - self._hint_text_label.texture_size[1] // 2, \
- self.x + dp(16) + self._hint_text_label.texture_size[0], \
- self.top - self._hint_text_label.texture_size[1] // 2
+ self.x + self.radius[3], \
+ self.y, \
+ self.x + self.width - self.radius[2], \
+ self.y
+
+ # Top (left) part of the line.
+ # ╭─-----------------------------------------------------
+ SmoothLine:
+ width: self._outline_height
+ points:
+ self.x + self.radius[0], \
+ self.y + self.height, \
+ self.x + self._left_x_axis_pos, \
+ self.y + self.height
+
+ # Top (right) part of the line.
+ # ╭─-----------—————————————————————————————————————————─╮
+ SmoothLine:
+ width: self._outline_height
+ points:
+ self.x + self._right_x_axis_pos, \
+ self.y + self.height, \
+ self.x + self.width - self.radius[1], \
+ self.y + self.height
# Text color.
Color:
group: "text-color"
rgba:
- self.theme_cls.disabled_hint_text_color if self.disabled else \
+ self.theme_cls.disabled_hint_text_color \
+ if self.disabled else \
( \
- self.text_color_focus if self.focus else self._text_color_normal
+ self.text_color_focus \
+ if self.text_color_focus and self.theme_text_color == "Custom" \
+ else self.theme_cls.onSurfaceColor \
) \
- if not self.error else self.error_color
+ if self.focus else \
+ ( \
+ self.text_color_normal \
+ if self.text_color_normal and self.theme_text_color == "Custom" \
+ else self.theme_cls.onSurfaceVariantColor \
+ )
+ # Hint texture.
canvas.after:
- # Hint text texture.
Color:
group: "hint-text-color"
- rgba:
- self.theme_cls.disabled_hint_text_color \
- if self.disabled else \
- self._hint_text_color
Rectangle:
- texture: self._hint_text_label.texture
- size: self._hint_text_label.texture_size
+ group: "hint-text-rectangle"
+ texture:
+ self._hint_text_label.texture \
+ if self._hint_text_label else \
+ None
+ size:
+ self._hint_text_label.texture_size \
+ if self._hint_text_label else \
+ (0, 0)
pos:
( \
- self.x + ((dp(16) if not self.icon_left else dp(52)) \
- if self.mode == "fill" else ( \
- ((0 if self.mode != "round" else dp(12)) if self.mode != "rectangle" else dp(12)) \
- if not self.icon_left else \
- (dp(36 if self.mode != "round" else 42) if self.mode != "rectangle" else dp(42)))) \
- if not self.focus and not self.text else \
- self.x + ((dp(16) if self.mode != "round" else dp(36 if not self.icon_left else 42)) \
- if self.mode in ("fill", "rectangle", "round") and \
- self.icon_left else (0 if self.mode != "round" else dp(12))) + self._hint_x
+ self.x + \
+ ( \
+ dp(16) \
+ if not self._leading_icon else \
+ self._leading_icon.texture_size[0] + dp(28) + self._hint_x \
), \
- self.y + self.height + (((dp(4) if self.mode != "round" else dp(10)) \
- if self.mode != "line" else \
- dp(-6)) if self.mode != "rectangle" else dp(-4)) - self._hint_y
-
+ self.y + self.height \
+ + (self._hint_text_label.texture_size[1] / 2) \
+ - (self.height / 2) \
+ - self._hint_y \
+ ) \
+ if self._hint_text_label else (0, 0)
- font_name: "Roboto" if not self.font_name else self.font_name
- foreground_color: self.theme_cls.text_color
bold: False
+ font_name: theme_font_styles[self.font_style][self.role]["font-name"]
+ font_size: theme_font_styles[self.font_style][self.role]["font-size"]
padding:
( \
- ((0 if self.mode != "round" else "12dp") \
- if self.mode != "rectangle" else "12dp") \
- if not self.icon_left else \
- (("36dp" if self.mode != "round" else "42dp") \
- if self.mode != "rectangle" else "42dp") \
- ) \
- if self.mode != "fill" else \
- ("16dp" if not self.icon_left else "52dp"), \
-
- "24dp" if self.mode != "round" else "8dp", \
-
- ((0 if self.mode != "round" and not self.icon_left else dp(12)) \
- if self.mode != "rectangle" else "12dp") \
- if self.mode != "fill" and not self.icon_right else \
- ( \
- "14dp" \
- if not self.icon_right else \
- self._icon_right_label.texture_size[1] + (dp(20) \
- if self.mode != "round" else dp(24))), \
-
- "8dp" if self.mode == "fill" else \
- (("22dp" if self.mode != "round" else "8dp") \
- if self.icon_left and self.mode != "rectangle" else \
- ("16dp" if self.mode in ("fill", "rectangle") else \
- "20dp" if self.mode != "round" else "8dp"))
+ dp(16) if not self._leading_icon else dp(42) \
+ if self.mode != "filled" else \
+ (dp(16) if not self._leading_icon else dp(52)), \
+
+ (self.height / 2.0 - (self.line_height / 2.0) * len(self._lines)) \
+ + dp(8 if self.mode == "filled" else 0), \
+
+ dp(16) \
+ if not self._trailing_icon else \
+ self._trailing_icon.texture_size[0] + dp(28), \
+
+ 0 \
+ )
multiline: False
size_hint_y: None
- height: self.minimum_height
+ height: dp(56)
-
+
+ size_hint: None, None
+ size: "20dp", "20dp"
size_hint_x: None
width: self.texture_size[0]
- shorten: True
- shorten_from: "right"
+ theme_text_color: "Custom"
-
- on_focus:
- self.anim_rect((self.x, self.y, self.right, self.y, self.right, \
- self.top, self.x, self.top, self.x, self.y), 1) if self.focus \
- else self.anim_rect((self.x - dp(60), self.y - dp(60), \
- self.right + dp(60), self.y - dp(60),
- self.right + dp(60), self.top + dp(60), \
- self.x - dp(60), self.top + dp(60), \
- self.x - dp(60), self.y - dp(60)), 0)
+
+ role: "large"
+ theme_font_size: "Custom"
- canvas.after:
- Color:
- group: "color"
- rgba: self._primary_color
- Line:
- group: "rectangle"
- width: dp(1.5)
- points:
- (
- self.x - dp(60), self.y - dp(60),
- self.right + dp(60), self.y - dp(60),
- self.right + dp(60), self.top + dp(60),
- self.x - dp(60), self.top + dp(60),
- self.x - dp(60), self.y - dp(60)
- )
+
+
+ size_hint_x: None
+ width: self.texture_size[0]
+ adaptive_width: True
+ shorten: True
+ shorten_from: "right"
+ font_style: "Body"
+ role: "small"
+ theme_text_color: "Custom"
diff --git a/kivymd/uix/textfield/textfield.py b/kivymd/uix/textfield/textfield.py
index a351580f7..820cd3a45 100755
--- a/kivymd/uix/textfield/textfield.py
+++ b/kivymd/uix/textfield/textfield.py
@@ -1,295 +1,249 @@
"""
-Components/TextField
-====================
+Components/Text fields
+======================
.. seealso::
- `Material Design spec, Text fields `_
+ `Material Design spec, Text fields