view mamba/widgets/base.py @ 283:11cf3a259eaa

Iterate over copies, to avoid some issues with children changing in the loop
author Neil Muller <drnlmuller@gmail.com>
date Thu, 15 Sep 2011 21:44:26 +0200
parents 190aa1481908
children a0c60e0c1ef2
line wrap: on
line source

import collections

import pygame
from pygame.constants import K_UP, K_DOWN, K_RETURN
from pygame.locals import (KEYDOWN, MOUSEMOTION, MOUSEBUTTONUP,
                           MOUSEBUTTONDOWN, USEREVENT)

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.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"
        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
            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):

    def event(self, ev):
        if super(Button, self).event(ev):
            return True
        if (ev.type == MOUSEBUTTONDOWN
                or (ev.type == KEYDOWN and ev.key == K_RETURN)):
            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"""
        for callback, args in self.callbacks['clicked']:
            if callback(None, self, *args):
                return True
        return False


class Container(Widget):

    def __init__(self, rect):
        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:
                        child.grab_focus()
                    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)