""" Components/Dialog ================= .. seealso:: `Material Design spec, Dialogs `_ .. rubric:: Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialogs.png :align: center Usage ----- .. code-block:: python from kivy.lang import Builder from kivymd.app import MDApp from kivymd.uix.button import MDFlatButton from kivymd.uix.dialog import MDDialog KV = ''' MDFloatLayout: MDFlatButton: text: "ALERT DIALOG" pos_hint: {'center_x': .5, 'center_y': .5} on_release: app.show_alert_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, ), MDFlatButton( text="DISCARD", 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/alert-dialog.png :align: center """ __all__ = ("MDDialog", "BaseDialog") 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. .. code-block:: python [...] 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) class MDDialog(BaseDialog): """ Dialog class. For more information, see in the :class:`~kivymd.theming.ThemableBehavior` and :class:`~kivy.uix.modalview.ModalView` and :class:`~kivymd.uix.behaviors.CommonElevationBehavior` classes documentation. """ title = StringProperty() """ Title dialog. .. code-block:: python [...] self.dialog = MDDialog( title="Reset settings?", buttons=[ MDFlatButton( text="CANCEL", theme_text_color="Custom", text_color=self.theme_cls.primary_color, ), MDFlatButton( text="ACCEPT", theme_text_color="Custom", text_color=self.theme_cls.primary_color, ), ], ) [...] .. 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 `''`. """ text = StringProperty() """ Text dialog. .. code-block:: python [...] self.dialog = MDDialog( title="Reset settings?", text="This will reset your device to its default factory settings.", buttons=[ MDFlatButton( text="CANCEL", theme_text_color="Custom", text_color=self.theme_cls.primary_color, ), MDFlatButton( text="ACCEPT", theme_text_color="Custom", text_color=self.theme_cls.primary_color, ), ], ) [...] .. 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 `''`. """ buttons = ListProperty() """ List of button objects for dialog. Objects must be inherited from :class:`~kivymd.uix.button.BaseButton` class. .. code-block:: python [...] self.dialog = MDDialog( text="Discard draft?", buttons=[ MDFlatButton(text="CANCEL"), MDRaisedButton(text="DISCARD"), ], ) [...] .. 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 `[]`. """ items = ListProperty() """ List of items objects for dialog. Objects must be inherited from :class:`~kivymd.uix.list.BaseListItem` class. With type 'simple' ~~~~~~~~~~~~~~~~~~ .. code-block:: python from kivy.lang import Builder from kivy.properties import StringProperty from kivymd.app import MDApp from kivymd.uix.dialog import MDDialog from kivymd.uix.list import OneLineAvatarListItem KV = ''' ImageLeftWidget: source: root.source MDFloatLayout: MDFlatButton: text: "ALERT DIALOG" pos_hint: {'center_x': .5, 'center_y': .5} on_release: app.show_simple_dialog() ''' class Item(OneLineAvatarListItem): divider = None source = StringProperty() 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_simple_dialog(self): if not self.dialog: self.dialog = MDDialog( title="Set backup account", type="simple", items=[ Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"), Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"), ], ) self.dialog.open() Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-items.png :align: center With type 'confirmation' ~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python from kivy.lang import Builder 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) CheckboxLeftWidget: id: check group: "check" MDFloatLayout: MDFlatButton: text: "ALERT DIALOG" pos_hint: {'center_x': .5, 'center_y': .5} on_release: app.show_confirmation_dialog() ''' class ItemConfirm(OneLineAvatarIconListItem): divider = None 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 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="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 `[]`. """ width_offset = NumericProperty(dp(48)) """ Dialog offset from device width. :attr:`width_offset` is an :class:`~kivy.properties.NumericProperty` 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() """ Custom content class. This attribute is only available when :attr:`type` is set to `'custom'`. .. tabs:: .. tab:: Declarative KV style .. 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'`. """ md_bg_color = ColorProperty(None) """ Background color in the (r, g, b, a) or string format. :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ _scroll_height = NumericProperty("28dp") _spacer_top = NumericProperty("24dp") def __init__(self, **kwargs): super().__init__(**kwargs) 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, 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 update_items(self, items: list) -> None: self.ids.box_items.clear_widgets() self.items = items self.create_items() def on_open(self) -> None: # TODO: Add scrolling text. self.height = self.ids.container.height super().on_open() def get_normal_height(self) -> float: return ( (Window.height * 80 / 100) - self._spacer_top - dp(52) - self.ids.container.padding[1] - self.ids.container.padding[-1] - 100 ) def edit_padding_for_item(self, instance_item) -> None: instance_item.ids._left_container.x = 0 instance_item._txt_left_pad = "56dp" 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 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) 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)