changeset 210:b92f705bd8ea

Modal dialogs, and focus stealing bugfixes
author Stefano Rivera <stefano@rivera.za.net>
date Wed, 14 Sep 2011 20:49:48 +0200
parents 51d54026b153
children 077626113593
files mamba/widgets/base.py mamba/widgets/game.py mamba/widgets/messagebox.py
diffstat 3 files changed, 54 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/mamba/widgets/base.py	Wed Sep 14 20:31:16 2011 +0200
+++ b/mamba/widgets/base.py	Wed Sep 14 20:49:48 2011 +0200
@@ -16,6 +16,7 @@
         self.rect = rect
         self.focussable = False
         self.focussed = False
+        self.modal = False
         self.parent = None
         self.callbacks = collections.defaultdict(list)
 
@@ -42,9 +43,37 @@
         pass
 
     def grab_focus(self):
-        if self.focussable and self.parent:
-            self.parent.defocus()
+        if self.focussable:
+            # Find the root and current focus
+            root = self
+            this_modal_base = None
+            while root.parent is not None:
+                if root.modal and this_modal_base is None:
+                    this_modal_base = root
+                root = root.parent
+            focus = root
+            focus_modal_base = None
+            while (isinstance(focus, Container)
+                    and focus.focussed_child is not None):
+                if focus.modal:
+                    focus_modal_base = focus
+                focus = focus.children[focus.focussed_child]
+
+            # Don't leave a modal Widget
+            if focus_modal_base and focus_modal_base != this_modal_base:
+                return False
+
+            root.defocus()
+            widget = self
+            while widget.parent is not None:
+                parent = widget.parent
+                if isinstance(parent, Container):
+                    idx = parent.children.index(widget)
+                    parent.focussed_child = idx
+                widget = parent
             self.focussed = True
+            return True
+        return False
 
 
 class Button(Widget):
@@ -75,25 +104,13 @@
             for child in self.children:
                 if child.rect.collidepoint(ev.pos):
                     if ev.type == MOUSEBUTTONDOWN and child.focussable:
-                        root = self
-                        while root.parent is not None:
-                            root = root.parent
-                        root.defocus()
-                        widget = child
-                        while widget.parent is not None:
-                            parent = widget.parent
-                            if isinstance(parent, Container):
-                                idx = parent.children.index(widget)
-                                parent.focussed_child = idx
-                            widget = parent
-                        child.focussed = True
-
+                        child.grab_focus()
                     if child.event(ev):
                         return True
 
         elif ev.type == KEYDOWN:
-            for child in self.children:
-                if child.focussed:
+            for i, child in enumerate(self.children):
+                if child.focussed or i == self.focussed_child:
                     if child.event(ev):
                         return True
         else:
@@ -128,8 +145,11 @@
         if self.focussed_child is not None:
             child = self.children[self.focussed_child]
             if isinstance(child, Container):
-                child.defocus()
+                if not child.defocus():
+                    return False
             child.focussed = False
+            self.focussed_child = None
+            return True
 
     def adjust_focus(self, direction):
         """Try and adjust focus in direction (integer)
@@ -139,6 +159,10 @@
             if isinstance(child, Container):
                 if child.adjust_focus(direction):
                     return True
+                elif child.modal:
+                    # We're modal, go back
+                    if child.adjust_focus(-direction):
+                        return True
             else:
                 child.focussed = False
 
--- a/mamba/widgets/game.py	Wed Sep 14 20:31:16 2011 +0200
+++ b/mamba/widgets/game.py	Wed Sep 14 20:49:48 2011 +0200
@@ -16,6 +16,8 @@
         rect = Rect(offset, world.get_size())
         super(GameWidget, self).__init__(rect)
         self.focussable = True
+        self.add_callback(KEYDOWN, self.action_callback)
+        self.add_callback(SnakeDiedEvent, self.snake_died)
 
     def create_action_map(self):
         actions = {}
@@ -25,24 +27,22 @@
         actions[K_UP] = (self.world.snake.set_orientation, (UP,))
         return actions
 
-    def event(self, ev):
-        if ev.type == KEYDOWN and ev.key in self.actions:
+    def action_callback(self, ev, widget):
+        if ev.key in self.actions:
             func, args = self.actions[ev.key]
             func(*args)
             return True
-        elif SnakeDiedEvent.matches(ev):
-            self.snake_died()
-            return True
 
     def draw(self, surface):
         self.world.update()
         self.world.draw(surface)
 
-    def snake_died(self):
+    def snake_died(self, ev, widget):
         self.world.pause()
         messagebox = MessageBox((300, 200), 'You died!', self.restart)
         self.parent.add(messagebox)
         messagebox.grab_focus()
+        return True
 
     def restart(self):
         self.world.restart()
--- a/mamba/widgets/messagebox.py	Wed Sep 14 20:31:16 2011 +0200
+++ b/mamba/widgets/messagebox.py	Wed Sep 14 20:49:48 2011 +0200
@@ -1,5 +1,5 @@
 import pygame
-from pygame.constants import SRCALPHA, K_RETURN, KEYDOWN
+from pygame.constants import SRCALPHA
 
 from mamba.widgets.base import Container
 from mamba.widgets.text import TextWidget, TextButton
@@ -14,7 +14,7 @@
         self.post_callback = post_callback
         self.color = color
         self.prepare()
-        self.focussable = True
+        self.modal = True
 
     def prepare(self):
         cont = TextWidget((0, 0), "Press [OK] or Enter to continue")
@@ -34,7 +34,7 @@
         cont.rect.topleft = cont_pos
         self.add(message)
         self.add(cont)
-        ok_button = TextButton((0, 0), 'OK')
+        self.ok_button = ok_button = TextButton((0, 0), 'OK')
         ok_pos = (self.rect.left + 5 + width / 2 - ok_button.rect.width / 2,
                 cont_pos[1] + 5 + cont.rect.height)
         ok_button.rect.topleft = ok_pos
@@ -47,13 +47,10 @@
         surface.blit(background, self.rect)
         super(MessageBox, self).draw(surface)
 
-    def event(self, ev):
-        if ev.type == KEYDOWN and ev.key == K_RETURN:
-            self.close(ev, self)
-            return True
-        return super(MessageBox, self).event(ev)
-
     def close(self, ev, widget):
         self.parent.remove(self)
         if self.post_callback:
             self.post_callback()
+
+    def grab_focus(self):
+        return self.ok_button.grab_focus()