'''
ModalView
=========
.. versionadded:: 1.4.0
The :class:`ModalView` widget is used to create modal views. By default, the
view will cover the whole "parent" window.
Remember that the default size of a Widget is size_hint=(1, 1). If you don't
want your view to be fullscreen, either use size hints with values lower than
1 (for instance size_hint=(.8, .8)) or deactivate the size_hint and use fixed
size attributes.
Examples
--------
Example of a simple 400x400 Hello world view::
view = ModalView(size_hint=(None, None), size=(400, 400))
view.add_widget(Label(text='Hello world'))
By default, any click outside the view will dismiss it. If you don't
want that, you can set :attr:`ModalView.auto_dismiss` to False::
view = ModalView(auto_dismiss=False)
view.add_widget(Label(text='Hello world'))
view.open()
To manually dismiss/close the view, use the :meth:`ModalView.dismiss` method of
the ModalView instance::
view.dismiss()
Both :meth:`ModalView.open` and :meth:`ModalView.dismiss` are bindable. That
means you can directly bind the function to an action, e.g. to a button's
on_press ::
# create content and add it to the view
content = Button(text='Close me!')
view = ModalView(auto_dismiss=False)
view.add_widget(content)
# bind the on_press event of the button to the dismiss function
content.bind(on_press=view.dismiss)
# open the view
view.open()
ModalView Events
----------------
There are two events available: `on_open` which is raised when the view is
opening, and `on_dismiss` which is raised when the view is closed.
For `on_dismiss`, you can prevent the view from closing by explictly returning
True from your callback. ::
def my_callback(instance):
print('ModalView', instance, 'is being dismissed, but is prevented!')
return True
view = ModalView()
view.add_widget(Label(text='Hello world'))
view.bind(on_dismiss=my_callback)
view.open()
.. versionchanged:: 1.5.0
The ModalView can be closed by hitting the escape key on the
keyboard if the :attr:`ModalView.auto_dismiss` property is True (the
default).
'''
__all__ = ('ModalView', )
from kivy.logger import Logger
from kivy.animation import Animation
from kivy.uix.anchorlayout import AnchorLayout
from kivy.properties import StringProperty, BooleanProperty, ObjectProperty, \
NumericProperty, ListProperty, ColorProperty
[docs]class ModalView(AnchorLayout):
'''ModalView class. See module documentation for more information.
:Events:
`on_pre_open`:
Fired before the ModalView is opened. When this event is fired
ModalView is not yet added to window.
`on_open`:
Fired when the ModalView is opened.
`on_pre_dismiss`:
Fired before the ModalView is closed.
`on_dismiss`:
Fired when the ModalView is closed. If the callback returns True,
the dismiss will be canceled.
.. versionchanged:: 1.11.0
Added events `on_pre_open` and `on_pre_dismiss`.
.. versionchanged:: 2.0.0
Added property 'overlay_color'.
'''
auto_dismiss = BooleanProperty(True)
'''This property determines if the view is automatically
dismissed when the user clicks outside it.
:attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty` and
defaults to True.
'''
attach_to = ObjectProperty(None)
'''If a widget is set on attach_to, the view will attach to the nearest
parent window of the widget. If none is found, it will attach to the
main/global Window.
:attr:`attach_to` is an :class:`~kivy.properties.ObjectProperty` and
defaults to None.
'''
background_color = ColorProperty([1, 1, 1, 1])
'''Background color, in the format (r, g, b, a).
This acts as a *multiplier* to the texture colour. The default
texture is grey, so just setting the background color will give
a darker result. To set a plain color, set the
:attr:`background_normal` to ``''``.
The :attr:`background_color` is a
:class:`~kivy.properties.ColorProperty` and defaults to [1, 1, 1, 1].
.. versionchanged:: 2.0.0
Changed behavior to affect the background of the widget itself, not
the overlay dimming.
Changed from :class:`~kivy.properties.ListProperty` to
:class:`~kivy.properties.ColorProperty`.
'''
background = StringProperty(
'atlas://data/images/defaulttheme/modalview-background')
'''Background image of the view used for the view background.
:attr:`background` is a :class:`~kivy.properties.StringProperty` and
defaults to 'atlas://data/images/defaulttheme/modalview-background'.
'''
border = ListProperty([16, 16, 16, 16])
'''Border used for :class:`~kivy.graphics.vertex_instructions.BorderImage`
graphics instruction. Used for the :attr:`background_normal` and the
:attr:`background_down` properties. Can be used when using custom
backgrounds.
It must be a list of four values: (bottom, right, top, left). Read the
BorderImage instructions for more information about how to use it.
:attr:`border` is a :class:`~kivy.properties.ListProperty` and defaults to
(16, 16, 16, 16).
'''
overlay_color = ColorProperty([0, 0, 0, .7])
'''Overlay color in the format (r, g, b, a).
Used for dimming the window behind the modal view.
:attr:`overlay_color` is a :class:`~kivy.properties.ColorProperty` and
defaults to [0, 0, 0, .7].
.. versionadded:: 2.0.0
'''
# Internals properties used for graphical representation.
_anim_alpha = NumericProperty(0)
_anim_duration = NumericProperty(.1)
_window = ObjectProperty(None, allownone=True, rebind=True)
_touch_started_inside = None
__events__ = ('on_pre_open', 'on_open', 'on_pre_dismiss', 'on_dismiss')
def __init__(self, **kwargs):
self._parent = None
super(ModalView, self).__init__(**kwargs)
def _search_window(self):
# get window to attach to
window = None
if self.attach_to is not None:
window = self.attach_to.get_parent_window()
if not window:
window = self.attach_to.get_root_window()
if not window:
from kivy.core.window import Window
window = Window
return window
[docs] def open(self, *largs, **kwargs):
'''Show the view window from the :attr:`attach_to` widget. If set, it
will attach to the nearest window. If the widget is not attached to any
window, the view will attach to the global
:class:`~kivy.core.window.Window`.
When the view is opened, it will be faded in with an animation. If you
don't want the animation, use::
view.open(animation=False)
'''
if self._window is not None:
Logger.warning('ModalView: you can only open once.')
return
# search window
self._window = self._search_window()
if not self._window:
Logger.warning('ModalView: cannot open view, no window found.')
return
self.dispatch('on_pre_open')
self._window.add_widget(self)
self._window.bind(
on_resize=self._align_center,
on_keyboard=self._handle_keyboard)
self.center = self._window.center
self.fbind('center', self._align_center)
self.fbind('size', self._align_center)
if kwargs.get('animation', True):
a = Animation(_anim_alpha=1., d=self._anim_duration)
a.bind(on_complete=lambda *x: self.dispatch('on_open'))
a.start(self)
else:
self._anim_alpha = 1.
self.dispatch('on_open')
[docs] def dismiss(self, *largs, **kwargs):
'''Close the view if it is open. If you really want to close the
view, whatever the on_dismiss event returns, you can use the *force*
argument:
::
view = ModalView()
view.dismiss(force=True)
When the view is dismissed, it will be faded out before being
removed from the parent. If you don't want animation, use::
view.dismiss(animation=False)
'''
if self._window is None:
return
self.dispatch('on_pre_dismiss')
if self.dispatch('on_dismiss') is True:
if kwargs.get('force', False) is not True:
return
if kwargs.get('animation', True):
Animation(_anim_alpha=0., d=self._anim_duration).start(self)
else:
self._anim_alpha = 0
self._real_remove_widget()
def _align_center(self, *l):
if self._window:
self.center = self._window.center
[docs] def on_touch_down(self, touch):
self._touch_started_inside = self.collide_point(*touch.pos)
if not self.auto_dismiss or self._touch_started_inside:
super(ModalView, self).on_touch_down(touch)
return True
[docs] def on_touch_move(self, touch):
if not self.auto_dismiss or self._touch_started_inside:
super(ModalView, self).on_touch_move(touch)
return True
[docs] def on_touch_up(self, touch):
# Explicitly test for False as None occurs when shown by on_touch_down
if self.auto_dismiss and self._touch_started_inside is False:
self.dismiss()
else:
super(ModalView, self).on_touch_up(touch)
self._touch_started_inside = None
return True
def on__anim_alpha(self, instance, value):
if value == 0 and self._window is not None:
self._real_remove_widget()
def _real_remove_widget(self):
if self._window is None:
return
self._window.remove_widget(self)
self._window.unbind(
on_resize=self._align_center,
on_keyboard=self._handle_keyboard)
self._window = None
def on_pre_open(self):
pass
def on_open(self):
pass
def on_pre_dismiss(self):
pass
def on_dismiss(self):
pass
def _handle_keyboard(self, window, key, *largs):
if key == 27 and self.auto_dismiss:
self.dismiss()
return True
if __name__ == '__main__':
from kivy.base import runTouchApp
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window
# add view
content = GridLayout(cols=1)
content.add_widget(Label(text='This is a hello world'))
view = ModalView(size_hint=(None, None), size=(256, 256),
auto_dismiss=True)
view.add_widget(content)
def open_view(btn):
view.open()
layout = GridLayout(cols=3)
for x in range(9):
btn = Button(text='click me %s' % x)
btn.bind(on_release=view.open)
layout.add_widget(btn)
Window.add_widget(layout)
view.open()
runTouchApp()