diff pyntnclick/widgets/base.py @ 555:c0474fe18b96 pyntnclick

Copy in widgets from mamba (currently unused)
author Stefano Rivera <stefano@rivera.za.net>
date Sat, 11 Feb 2012 14:09:46 +0200
parents
children f9f04cb35697
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pyntnclick/widgets/base.py	Sat Feb 11 14:09:46 2012 +0200
@@ -0,0 +1,310 @@
+import collections
+
+import pygame
+from pygame.locals import (KEYDOWN, K_DOWN, K_LEFT, K_RETURN, K_RIGHT, K_UP,
+                           K_KP_ENTER, MOUSEBUTTONDOWN, MOUSEBUTTONUP,
+                           MOUSEMOTION, SRCALPHA, USEREVENT)
+
+from mamba.constants import UP, DOWN, LEFT, RIGHT
+from mamba.engine import UserEvent
+
+
+class Widget(object):
+
+    def __init__(self, rect):
+        if not isinstance(rect, pygame.Rect):
+            rect = pygame.Rect(rect, (0, 0))
+        self.rect = rect
+        self.focussable = False
+        self.focussed = False
+        self.modal = False
+        self.parent = None
+        self.disabled = False
+        self.callbacks = collections.defaultdict(list)
+
+    def add_callback(self, eventtype, callback, *args):
+        self.callbacks[eventtype].append((callback, args))
+
+    def event(self, ev):
+        "Don't override this without damn good reason"
+        if self.disabled:
+            return True
+
+        type_ = ev.type
+        if type_ == USEREVENT:
+            for k in self.callbacks.iterkeys():
+                if (isinstance(k, type) and issubclass(k, UserEvent)
+                        and k.matches(ev)):
+                    type_ = k
+                    break
+
+        for callback, args in self.callbacks[type_]:
+            if callback(ev, self, *args):
+                return True
+        return False
+
+    def draw(self, surface):
+        "Override me"
+        pass
+
+    def grab_focus(self):
+        if self.focussable:
+            # Find the root and current focus
+            root = self
+            while root.parent is not None:
+                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 heirarchy
+            if focus_modal_base:
+                widget = self
+                while widget.parent is not None:
+                    if widget == focus_modal_base:
+                        break
+                    widget = widget.parent
+                else:
+                    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
+
+    def disable(self):
+        if not self.disabled:
+            self.disabled = True
+            self._focussable_when_enabled = self.focussable
+            self.focussable = False
+            if hasattr(self, 'prepare'):
+                self.prepare()
+
+    def enable(self):
+        if self.disabled:
+            self.disabled = False
+            self.focussable = self._focussable_when_enabled
+            if hasattr(self, 'prepare'):
+                self.prepare()
+
+
+class Button(Widget):
+
+    def event(self, ev):
+        if super(Button, self).event(ev):
+            return True
+        if (ev.type == MOUSEBUTTONDOWN
+                or (ev.type == KEYDOWN and ev.key in (K_RETURN, K_KP_ENTER))):
+            for callback, args in self.callbacks['clicked']:
+                if callback(ev, self, *args):
+                    return True
+            return False
+
+    def forced_click(self):
+        """Force calling the clicked handler"""
+        self.grab_focus()
+        for callback, args in self.callbacks['clicked']:
+            if callback(None, self, *args):
+                return True
+        return False
+
+
+class Container(Widget):
+
+    def __init__(self, rect=None):
+        if rect is None:
+            rect = pygame.Rect(0, 0, 0, 0)
+        super(Container, self).__init__(rect)
+        self.children = []
+        self.focussed_child = None
+
+    def event(self, ev):
+        """Push an event down through the tree, and fire our own event as a
+        last resort
+        """
+        if ev.type in (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN):
+            for child in self.children[:]:
+                if child.rect.collidepoint(ev.pos):
+                    if ev.type == MOUSEBUTTONDOWN and child.focussable:
+                        if not child.grab_focus():
+                            continue
+                    if child.event(ev):
+                        return True
+
+        elif ev.type == KEYDOWN:
+            for i, child in enumerate(self.children):
+                if child.focussed or i == self.focussed_child:
+                    if child.event(ev):
+                        return True
+        else:
+            # Other events go to all children first
+            for child in self.children[:]:
+                if child.event(ev):
+                    return True
+        if super(Container, self).event(ev):
+            return True
+        if (self.parent is None and ev.type == KEYDOWN
+                and ev.key in (K_UP, K_DOWN)):
+            return self.adjust_focus(1 if ev.key == K_DOWN else -1)
+
+    def add(self, widget):
+        widget.parent = self
+        self.children.append(widget)
+        self.rect = self.rect.union(widget.rect)
+
+    def remove(self, widget):
+        widget.parent = None
+        if self.focussed_child is not None:
+            child = self.children[self.focussed_child]
+        self.children.remove(widget)
+        # We don't update the rect, for reasons of simplificty and laziness
+        if self.focussed_child is not None and child in self.children:
+            # Fix focus index
+            self.focussed_child = self.children.index(child)
+        else:
+            self.focussed_child = None
+
+    def defocus(self):
+        if self.focussed_child is not None:
+            child = self.children[self.focussed_child]
+            if isinstance(child, Container):
+                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)
+        """
+        if self.focussed_child is not None:
+            child = self.children[self.focussed_child]
+            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
+
+        current = self.focussed_child
+        if current is None:
+            current = -1 if direction > 0 else len(self.children)
+        if direction > 0:
+            possibles = list(enumerate(self.children))[current + 1:]
+        else:
+            possibles = list(enumerate(self.children))[:current]
+            possibles.reverse()
+        for i, child in possibles:
+            if child.focussable:
+                child.focussed = True
+                self.focussed_child = i
+                return True
+            if isinstance(child, Container):
+                if child.adjust_focus(direction):
+                    self.focussed_child = i
+                    return True
+        else:
+            if self.parent is None:
+                if self.focussed_child is not None:
+                    # At the end, mark the last one as focussed, again
+                    child = self.children[self.focussed_child]
+                    if isinstance(child, Container):
+                        if child.adjust_focus(-direction):
+                            return True
+                    else:
+                        child.focussed = True
+                        return True
+            else:
+                self.focussed_child = None
+            return False
+
+    def draw(self, surface):
+        if self.parent is None and not self.focussed:
+            self.focussed = True
+            self.adjust_focus(1)
+        for child in self.children:
+            child.draw(surface)
+
+
+class GridContainer(Container):
+    """Hacky container that only supports grids, won't work with Container
+    children, or modal children.
+    """
+
+    def __init__(self, width, rect=None):
+        super(GridContainer, self).__init__(rect)
+        self.width = width
+
+    def event(self, ev):
+        if (ev.type == KEYDOWN and ev.key in (K_UP, K_DOWN, K_LEFT, K_RIGHT)):
+            direction = None
+            if ev.key == K_UP:
+                direction = UP
+            elif ev.key == K_DOWN:
+                direction = DOWN
+            elif ev.key == K_LEFT:
+                direction = LEFT
+            elif ev.key == K_RIGHT:
+                direction = RIGHT
+            return self.adjust_focus(direction)
+        super(GridContainer, self).event(ev)
+
+    def add(self, widget):
+        assert not isinstance(widget, Container)
+        assert not widget.modal
+        super(GridContainer, self).add(widget)
+
+    def adjust_focus(self, direction):
+        if isinstance(direction, int):
+            direction = (direction, 0)
+
+        if len(self.children) == 0:
+            return False
+
+        if self.focussed_child is None:
+            if sum(direction) > 0:
+                self.focussed_child = 0
+            else:
+                self.focussed_child = len(self.children) - 1
+        else:
+            self.children[self.focussed_child].focussed = False
+            if direction[0] != 0:
+                self.focussed_child += direction[0]
+            if direction[1] != 0:
+                self.focussed_child += self.width * direction[1]
+        if not 0 <= self.focussed_child < len(self.children):
+            self.focussed_child = None
+            return False
+        self.children[self.focussed_child].focussed = True
+        return True
+
+
+class Box(Container):
+    """A container that draws a filled background with a border"""
+    padding = 4
+
+    def draw(self, surface):
+        expandrect = self.rect.move((-self.padding, -self.padding))
+        expandrect.width = self.rect.width + 2 * self.padding
+        expandrect.height = self.rect.height + 2 * self.padding
+        border = pygame.Surface(expandrect.size, SRCALPHA)
+        border.fill(pygame.Color('black'))
+        surface.blit(border, expandrect)
+        background = pygame.Surface(self.rect.size, SRCALPHA)
+        background.fill(pygame.Color('gray'))
+        surface.blit(background, self.rect)
+        super(Box, self).draw(surface)