Skip to content
Advertisement

Kivy: how to handle touch events triggered from on_touch_down method of ModalView

Is there a way to make it so when my ModalView/ Popup widget is active but none of the buttons behind it will be triggered?

Below is an example of a ModalView/popup widget that will notify the user they entered something incorrect. I want the user to be able to click anywhere and the ModalView will disappear. The problem is that when this ModalView is visiable the buttons in the background are still active.

In this case, the BaseBoxLayout has a Label on top and Button on the bottom. I click on the button to trigger the ModalView/popup. If the user clicks on the top, the ModalView/popup disappears – as designed by my on_touch_down method.

However, if the user clicks anywhere in the bottom half (when the ModalView/popup is already triggered) then the ModalView/popup gets removed but then added again because the button in the background is still active.

How do I prevent this?

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView

KV = '''
Screen:
  canvas.before:
    Color:
      rgb: [127/255,160/255,100/255]
    Rectangle:
      pos: self.pos
      size: self.size
  BaseBoxLayout:
    orientation: "vertical"
    Label:
      text: "BaseBoxLayout "
    Button:
      text: "Ok button"
      on_press: self.parent.call_popup()

<CustomPopup>
  BoxLayout:
    Label:
      text: "You can do this!"
'''

class BaseBoxLayout(BoxLayout):
  def __init__(self,**kwargs):
    super().__init__(**kwargs)

  def call_popup(self):
    print('created popup')
    self.popup = CustomPopup()
    self.popup.open()


class CustomPopup(ModalView):
  def __init__(self, **kwargs):
    super().__init__(**kwargs)

  def on_touch_down(self,touch):
    print('removed popup')
    self.parent.remove_widget(self)


class MainApp(MDApp):
  def build(self):
    return Builder.load_string(KV)

if __name__ == '__main__':
  MainApp().run()

Advertisement

Answer

As kivy gives you maximum flexibility in dispatching touch events, you can decide and implement that as you want.

Let’s consider some typical situations you might come across in this scenario.

  1. Your ModalView (I’ll call it popup) needs to be dismissed as soon as you press (corresponds to on_touch_down event) anywhere on the screen (window). But you don’t want any underlying widget to interfere at this point. Here, this means you don’t want to dispatch the touch event further down the widget tree. You can do this by returning True from method on_touch_down which will digest the event and stop it propagate further.
    def on_touch_down(self,touch):
        self.parent.remove_widget(self)
        return True
  1. You want the popup to be dismissed as before but also you don’t want to override any default behavior. Call the superclass method. This is more preferable than in 1.
    def on_touch_down(self,touch):
        self.parent.remove_widget(self)
        return super().on_touch_down(touch)
  1. Perhaps your popup will not cover whole window. In that case you want it to be dismissed only if touch is within its physical boundary otherwise not. Go as in 1 or 2. But what if the touch is outside its physical boundary ? Return True to digest the event. If you don’t handle that youself, call super (but remember it will dismiss the popup unless you set auto_dismiss to False).
    def on_touch_down(self,touch):
        if self.collide_point(*touch.pos):
            self.parent.remove_widget(self)
        # Otherwise stop propagation and digest.
        return True
        # Or for default handling.
#       return super().on_touch_down(touch)

There might be countable (if not infinite) no. of situations you can face here. So better stop here.

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement