Skip to content

Commit

Permalink
Make modal and dropdown consistent (kivy#6650)
Browse files Browse the repository at this point in the history
* Dismiss popup only when down and up touch are outside.

* Add tests.

* Fix action bar test - click twice to close and then open.

* Starting touch outside should always close, inside should never.
  • Loading branch information
matham authored Dec 21, 2019
1 parent 59facf6 commit 2306a0a
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 20 deletions.
5 changes: 5 additions & 0 deletions kivy/tests/test_uix_actionbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ def test_2_switch(self, *args):
self.assertTrue(group2.is_open)
self.assertFalse(group1.is_open)

# click away from ActionBar and wait for it to disappear
TouchPoint(0, 0)
sleep(g2dd.min_state_time)
self.move_frames(1)

# click on Group 1
TouchPoint(*group1.center)

Expand Down
91 changes: 91 additions & 0 deletions kivy/tests/test_uix_dropdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from kivy.tests import async_run, UnitKivyApp


def dropdown_app():
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.label import Label

class RootButton(Button):

dropdown = None

def on_touch_down(self, touch):
assert self.dropdown.attach_to is None
return super(RootButton, self).on_touch_down(touch)

def on_touch_move(self, touch):
assert self.dropdown.attach_to is None
return super(RootButton, self).on_touch_move(touch)

def on_touch_up(self, touch):
assert self.dropdown.attach_to is None
return super(RootButton, self).on_touch_up(touch)

class TestApp(UnitKivyApp, App):
def build(self):
root = RootButton(text='Root')
self.attach_widget = Label(text='Attached widget')
root.add_widget(self.attach_widget)

root.dropdown = self.dropdown = DropDown(
auto_dismiss=True, min_state_time=0)
self.inner_widget = w = Label(
size_hint=(None, None), text='Dropdown')
root.dropdown.add_widget(w)
return root

return TestApp()


@async_run(app_cls_func=dropdown_app)
async def test_dropdown_app(kivy_app):
await kivy_app.wait_clock_frames(2)
dropdown = kivy_app.dropdown
button = kivy_app.root
assert dropdown.attach_to is None

kivy_app.attach_widget.size = button.width * 3 / 5, 2
kivy_app.attach_widget.top = button.height
kivy_app.inner_widget.size = button.width * 3 / 5, button.height

# just press button
async for _ in kivy_app.do_touch_down_up(widget=button):
pass
async for _ in kivy_app.do_touch_drag(widget=button, dx=button.width / 4):
pass

# open dropdown
dropdown.open(kivy_app.attach_widget)
dropdown.pos = 0, 0
await kivy_app.wait_clock_frames(2)
assert dropdown.attach_to is not None

# press within dropdown area - should stay open
async for _ in kivy_app.do_touch_down_up(widget=button):
pass
assert dropdown.attach_to is not None
# start in dropdown but release outside - should stay open
async for _ in kivy_app.do_touch_drag(widget=button, dx=button.width / 4):
pass
assert dropdown.attach_to is not None

# start outside but release in dropdown - should close
async for _ in kivy_app.do_touch_drag(
pos=(button.center_x + button.width / 4, button.center_y),
target_widget=button):
pass
assert dropdown.attach_to is None

# open dropdown again
dropdown.open(kivy_app.attach_widget)
dropdown.pos = 0, 0
await kivy_app.wait_clock_frames(2)
assert dropdown.attach_to is not None

# press outside dropdown area to close it - should close
async for _ in kivy_app.do_touch_down_up(
pos=(button.center_x + button.width / 4, button.center_y)):
pass
assert dropdown.attach_to is None
82 changes: 82 additions & 0 deletions kivy/tests/test_uix_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from kivy.tests import async_run, UnitKivyApp
from math import isclose


def modal_app():
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.modalview import ModalView

class ModalButton(Button):

modal = None

def on_touch_down(self, touch):
assert self.modal._window is None
return super(ModalButton, self).on_touch_down(touch)

def on_touch_move(self, touch):
assert self.modal._window is None
return super(ModalButton, self).on_touch_move(touch)

def on_touch_up(self, touch):
assert self.modal._window is None
return super(ModalButton, self).on_touch_up(touch)

class TestApp(UnitKivyApp, App):
def build(self):
root = ModalButton()
root.modal = self.modal_view = ModalView(
size_hint=(.2, .5), auto_dismiss=True)
return root

return TestApp()


@async_run(app_cls_func=modal_app)
async def test_modal_app(kivy_app):
await kivy_app.wait_clock_frames(2)
modal = kivy_app.modal_view
button = kivy_app.root
modal._anim_duration = 0
assert modal._window is None

# just press button
async for _ in kivy_app.do_touch_down_up(widget=button):
pass
async for _ in kivy_app.do_touch_drag(widget=button, dx=button.width / 4):
pass

# open modal
modal.open()
await kivy_app.wait_clock_frames(2)
assert modal._window is not None
assert isclose(modal.center_x, button.center_x, abs_tol=.1)
assert isclose(modal.center_y, button.center_y, abs_tol=.1)

# press within modal area - should stay open
async for _ in kivy_app.do_touch_down_up(widget=button):
pass
assert modal._window is not None
# start in modal but release outside - should stay open
async for _ in kivy_app.do_touch_drag(widget=button, dx=button.width / 4):
pass
assert modal._window is not None

# start outside but release in modal - should close
async for _ in kivy_app.do_touch_drag(
pos=(button.center_x + button.width / 4, button.center_y),
target_widget=button):
pass
assert modal._window is None

# open modal again
modal.open()
await kivy_app.wait_clock_frames(2)
assert modal._window is not None

# press outside modal area - should close
async for _ in kivy_app.do_touch_down_up(
pos=(button.center_x + button.width / 4, button.center_y)):
pass
assert modal._window is None
29 changes: 15 additions & 14 deletions kivy/uix/dropdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ class DropDown(ScrollView):
list. It is a :class:`~kivy.uix.gridlayout.GridLayout` by default.
'''

_touch_started_inside = None

__events__ = ('on_select', 'on_dismiss')

def __init__(self, **kwargs):
Expand Down Expand Up @@ -295,23 +297,22 @@ def clear_widgets(self):
return super(DropDown, self).clear_widgets()

def on_touch_down(self, touch):
if super(DropDown, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos):
return True
if (self.attach_to and self.attach_to.collide_point(
*self.attach_to.to_widget(*touch.pos))):
return True
self._touch_started_inside = self.collide_point(*touch.pos)
if not self.auto_dismiss or self._touch_started_inside:
super(DropDown, self).on_touch_down(touch)
return True

def on_touch_move(self, touch):
if not self.auto_dismiss or self._touch_started_inside:
super(DropDown, self).on_touch_move(touch)
return True

def on_touch_up(self, touch):
if super(DropDown, self).on_touch_up(touch):
return True
if 'button' in touch.profile and touch.button.startswith('scroll'):
return
if self.collide_point(*touch.pos):
return True
if self.auto_dismiss:
if self.auto_dismiss and not self._touch_started_inside:
self.dismiss()
return True
super(DropDown, self).on_touch_up(touch)
return True

def _reposition(self, *largs):
# calculate the coordinate of the attached widget in the window
Expand Down
16 changes: 10 additions & 6 deletions kivy/uix/modalview.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ class ModalView(AnchorLayout):

_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):
Expand Down Expand Up @@ -263,18 +265,20 @@ def _align_center(self, *l):
self.center = self._window.center

def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
if self.auto_dismiss:
self.dismiss()
return True
super(ModalView, self).on_touch_down(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

def on_touch_move(self, touch):
super(ModalView, self).on_touch_move(touch)
if not self.auto_dismiss or self._touch_started_inside:
super(ModalView, self).on_touch_move(touch)
return True

def on_touch_up(self, touch):
if self.auto_dismiss and not self._touch_started_inside:
self.dismiss()
return True
super(ModalView, self).on_touch_up(touch)
return True

Expand Down

0 comments on commit 2306a0a

Please sign in to comment.