Source code for spyral.event

"""This module contains functions and classes for creating and issuing events.
For a list of the events that are built into Spyral, check the 
:ref:`Event List<ref.events>`.

    .. attribute:: keys

        A special attribute for accessing the constants associated with a given
        key. For instance, ``spyral.keys.down`` and ``spyral.keys.f``. This is
        useful for testing for keyboard events. A complete list of all the key
        constants can be found in the 
        :ref:`Keyboard Keys <ref.keys>` appendix.

    .. attribute:: mods

        A special attribute for accessing the constants associated with a given
        mod key. For instance, ``spyral.mods.lshift`` (left shift) and
        ``spyral.mods.ralt`` (Right alt). This is useful for testing for keyboard
        events. A complete list of all the key
        constants can be found in the 
        :ref:`Keyboard Modifiers <ref.mods>` appendix.

"""

import pygame
try:
    import json
except ImportError:
    import simplejson as json
import spyral
import os
import random
import base64
from weakmethod import WeakMethod as _wm

def WeakMethod(func):
    try:
        return _wm(func)
    except TypeError:
        return func

_TYPE_TO_ATTRS = None
_TYPE_TO_TYPE = None

[docs]class Event(object): """ A class for building for attaching data to an event. Keyword arguments will be named attributes of the Event when it is passed into :func:`queue <spyral.event.queue>`:: collision_event = Event(ball=ball, paddle=paddle) spyral.event.queue("ball.collides.paddle", collision_event) """ def __init__(self, **kwargs): self.__dict__.update(kwargs) # This might actually be unused!
_EVENT_NAMES = ['QUIT', 'ACTIVEEVENT', 'KEYDOWN', 'KEYUP', 'MOUSEMOTION', 'MOUSEBUTTONUP', 'VIDEORESIZE', 'VIDEOEXPOSE', 'USEREVENT', 'MOUSEBUTTONDOWN'] MOUSE_MAP = ['left', 'middle', 'right', 'scroll_up', 'scroll_down'] def _init(): """ Initializes the Event system, which requires mapping the Pygame event constants to Spyral strings. """ global _TYPE_TO_ATTRS global _TYPE_TO_TYPE _TYPE_TO_ATTRS = { pygame.QUIT: tuple(), pygame.ACTIVEEVENT: ('gain', 'state'), pygame.KEYDOWN: ('unicode', 'key', 'mod'), pygame.KEYUP: ('key', 'mod'), pygame.MOUSEMOTION: ('pos', 'rel', 'buttons'), pygame.MOUSEBUTTONUP: ('pos', 'button'), pygame.MOUSEBUTTONDOWN: ('pos', 'button'), pygame.VIDEORESIZE: ('size', 'w', 'h'), pygame.VIDEOEXPOSE: ('none'), } _TYPE_TO_TYPE = { pygame.QUIT: "system.quit", pygame.ACTIVEEVENT: "system.focus_change", pygame.KEYDOWN: "input.keyboard.down", pygame.KEYUP: "input.keyboard.up", pygame.MOUSEMOTION: "input.mouse.motion", pygame.MOUSEBUTTONUP: "input.mouse.up", pygame.MOUSEBUTTONDOWN: "input.mouse.down", pygame.VIDEORESIZE: "system.video_resize", pygame.VIDEOEXPOSE: "system.video_expose", }
[docs]def queue(event_name, event=None, scene=None): """ Queues a new event in the system, meaning that it will be run at the next available opportunity. :param str event_name: The type of event (e.g., ``"system.quit"``, ``"input.mouse.up"``, or ``"pong.score"``. :param event: An Event object that holds properties for the event. :type event: :class:`Event <spyral.event.Event>` :param scene: The scene to queue this event on; if `None` is given, the currently executing scene will be used. :type scene: :class:`Scene <spyral.Scene>` or `None`. """ if scene is None: scene = spyral._get_executing_scene() scene._queue_event(event_name, event)
[docs]def handle(event_name, event=None, scene=None): """ Instructs spyral to execute the handlers for this event right now. When you have a custom event, this is the function you call to have the event occur. :param str event_name: The type of event (e.g., ``"system.quit"``, ``"input.mouse.up"``, or ``"pong.score"``. :param event: An Event object that holds properties for the event. :type event: :class:`Event <spyral.event.Event>` :param scene: The scene to queue this event on; if ``None`` is given, the currently executing scene will be used. :type scene: :class:`Scene <spyral.Scene>` or ``None``. """ if scene is None: scene = spyral._get_executing_scene() scene._handle_event(event_name, event)
[docs]def register(event_namespace, handler, args=None, kwargs=None, priority=0, scene=None): """ Registers an event `handler` to a namespace. Whenever an event in that `event_namespace` is fired, the event `handler` will execute with that event. :param event_namespace: the namespace of the event, e.g. ``"input.mouse.left.click"`` or ``"pong.score"``. :type event_namespace: str :param handler: A function that will handle the event. The first argument to the function will be the event. :type handler: function :param args: any additional arguments that need to be passed in to the handler. :type args: sequence :param kwargs: any additional keyword arguments that need to be passed into the handler. :type kwargs: dict :param int priority: the higher the `priority`, the sooner this handler will be called in reaction to the event, relative to the other event handlers registered. :param scene: The scene to register this event on; if it is ``None``, then it will be attached to the currently running scene. :type scene: :class:`Scene <spyral.Scene>` or ``None`` """ if scene is None: scene = spyral._get_executing_scene() scene._reg_internal(event_namespace, (WeakMethod(handler),), args, kwargs, priority, False)
[docs]def register_dynamic(event_namespace, handler_string, args=None, kwargs=None, priority=0, scene=None): """ Similar to :func:`spyral.event.register` function, except that instead of passing in a function, you pass in the name of a property of this scene that holds a function. Example:: class MyScene(Scene): def __init__(self): ... self.register_dynamic("orc.dies", "future_function") ... :param str event_namespace: The namespace of the event, e.g. ``"input.mouse.left.click"`` or ``"pong.score"``. :param str handler: The name of an attribute on this scene that will hold a function. The first argument to the function will be the event. :param args: any additional arguments that need to be passed in to the handler. :type args: sequence :param kwargs: any additional keyword arguments that need to be passed into the handler. :type kwargs: dict :param int priority: the higher the `priority`, the sooner this handler will be called in reaction to the event, relative to the other event handlers registered. :param scene: The scene to register this event on; if it is ``None``, then it will be attached to the currently running scene. :type scene: :class:`Scene <spyral.Scene>` or ``None`` """ if scene is None: scene = spyral._get_executing_scene() scene._reg_internal(event_namespace, (handler_string,), args, kwargs, priority, True)
[docs]def register_multiple(event_namespace, handlers, args=None, kwargs=None, priority=0, scene=None): """ Similar to :func:`spyral.event.register` function, except a sequence of `handlers` are be given instead of just one. :param str event_namespace: the namespace of the event, e.g. ``"input.mouse.left.click"`` or ``"pong.score"``. :type event_namespace: string :param handler: A list of functions that will be run on this event. :type handler: list of functions :param args: any additional arguments that need to be passed in to the handler. :type args: sequence :param kwargs: any additional keyword arguments that need to be passed into the handler. :type kwargs: dict :param int priority: the higher the `priority`, the sooner this handler will be called in reaction to the event, relative to the other event handlers registered. :param scene: The scene to register this event on; if it is ``None``, then it will be attached to the currently running scene. :type scene: :class:`Scene <spyral.Scene>` or ``None`` """ if scene is None: scene = spyral._get_executing_scene() scene._reg_internal(event_namespace, map(WeakMethod, handlers), args, kwargs, priority, False)
[docs]def register_multiple_dynamic(event_namespace, handler_strings, args=None, kwargs=None, priority=0, scene=None): """ Similar to :func:`spyral.Scene.register` function, except a sequence of strings representing handlers can be given instead of just one. :param event_namespace: the namespace of the event, e.g. ``"input.mouse.left.click"`` or ``"pong.score"``. :type event_namespace: string :param handler: A list of names of an attribute on this scene that will hold a function. The first argument to the function will be the event. :type handler: list of strings :param args: any additional arguments that need to be passed in to the handler. :type args: sequence :param kwargs: any additional keyword arguments that need to be passed into the handler. :type kwargs: dict :param int priority: the higher the `priority`, the sooner this handler will be called in reaction to the event, relative to the other event handlers registered. :param scene: The scene to register this event on; if it is ``None``, then it will be attached to the currently running scene. :type scene: :class:`Scene <spyral.Scene>` or ``None`` """ if scene is None: scene = spyral._get_executing_scene() scene._reg_internal(event_namespace, handler_strings, args, kwargs, priority, True)
[docs]def unregister(event_namespace, handler, scene=None): """ Unregisters a registered handler for that namespace. Dynamic handler strings are supported as well. :param str event_namespace: An event namespace :param handler: The handler to unregister. :type handler: a function or string. :param scene: The scene to unregister the event; if it is ``None``, then it will be attached to the currently running scene. :type scene: :class:`Scene <spyral.Scene>` or ``None`` """ if scene is None: scene = spyral._get_executing_scene() if not isinstance(handler, str): handler = WeakMethod(handler) scene._unregister(event_namespace, handler)
[docs]def clear_namespace(namespace, scene=None): """ Clears all handlers from namespaces that are at least as specific as the provided `namespace`. :param str namespace: The complete namespace. :param scene: The scene to clear the namespace of; if it is ``None``, then it will be attached to the currently running scene. :type scene: :class:`Scene <spyral.Scene>` or ``None`` """ if scene is None: scene = spyral._get_executing_scene() scene._clear_namespace(namespace)
def _pygame_to_spyral(event): """ Convert a Pygame event to a Spyral event, correctly converting arguments to attributes. """ event_attrs = _TYPE_TO_ATTRS[event.type] event_type = _TYPE_TO_TYPE[event.type] e = Event() for attr in event_attrs: setattr(e, attr, getattr(event, attr)) if event_type.startswith("input"): setattr(e, "type", event_type.split(".")[-1]) if event_type.startswith('input.keyboard'): k = keys.reverse_map.get(event.key, 'unknown') event_type += '.' + k if event_type.startswith('input.mouse.motion'): e.left, e.middle, e.right = map(bool, event.buttons) elif event_type.startswith('input.mouse'): try: m = MOUSE_MAP[event.button-1] setattr(e, "button", m) except IndexError: m = str(event.button) event_type += '.' + m return (event_type, e)
[docs]class EventHandler(object): """ Base event handler class. """ def __init__(self): self._events = [] self._mouse_pos = (0, 0)
[docs] def tick(self): """ Should be called at the beginning of update cycle. For the event handler which is part of a scene, this function will be called automatically. For any additional event handlers, you must call this function manually. """ pass
[docs] def get(self, types=[]): """ Gets events from the event handler. Types is an optional iterable which has types which you would like to get. """ try: types[0] except IndexError: pass except TypeError: types = (types,) if types == []: ret = self._events self._events = [] return ret ret = [e for e in self._events if e['type'] in types] self._events = [e for e in self._events if e['type'] not in types] return ret
[docs]class LiveEventHandler(EventHandler): """ An event handler which pulls events from the operating system. The optional output_file argument specifies the path to a file where the event handler will save a custom json file that can be used with the `ReplayEventHandler` to show replays of a game in action, or be used for other clever purposes. .. note:: If you use the output_file parameter, this function will reseed the random number generator, save the seed used. It will then be restored by the ReplayEventHandler. """ def __init__(self, output_file=None): EventHandler.__init__(self) self._save = output_file is not None if self._save: self._file = open(output_file, 'w') seed = os.urandom(4) info = {'random_seed': base64.encodestring(seed)} random.seed(seed) self._file.write(json.dumps(info) + "\n") def tick(self): mouse = pygame.mouse.get_pos() events = pygame.event.get() self._mouse_pos = mouse self._events.extend(events) # if self._save: # d = {'mouse': mouse, 'events': events} # self._file.write(json.dumps(d) + "\n") def __del__(self): if self._save: self._file.close()
[docs]class ReplayEventHandler(EventHandler): """ An event handler which replays the events from a custom json file saved by the `LiveEventHandler`. """ def __init__(self, input_file): EventHandler.__init__(self) self._file = open(input_file) info = json.loads(self._file.readline()) random.seed(base64.decodestring(info['random_seed'])) self.paused = False
[docs] def pause(self): """ Pauses the replay of the events, making tick() a noop until resume is called. """ self.paused = True
[docs] def resume(self): """ Resumes the replay of events. """ self.paused = False
def tick(self): if self.paused: return try: d = json.loads(self._file.readline()) except ValueError: spyral.director.pop() events = d['events'] events = [EventDict(e) for e in events] self._mouse_pos = d['mouse'] self._events.extend(events)
class Mods(object): def __init__(self): self.none = pygame.KMOD_NONE self.lshift = pygame.KMOD_LSHIFT self.rshift = pygame.KMOD_RSHIFT self.shift = pygame.KMOD_SHIFT self.caps = pygame.KMOD_CAPS self.ctrl = pygame.KMOD_CTRL self.lctrl = pygame.KMOD_LCTRL self.rctrl = pygame.KMOD_RCTRL self.lalt = pygame.KMOD_LALT self.ralt = pygame.KMOD_RALT self.alt = pygame.KMOD_ALT class Keys(object): def __init__(self): self.reverse_map = {} self.load_keys_from_file(spyral._get_spyral_path() + 'resources/default_key_mappings.txt') self._fix_bad_names([("return", "enter"), ("break", "brk")]) def _fix_bad_names(self, renames): """ Used to replace any binding names with non-python keywords. """ for original, new in renames: setattr(self, new, getattr(self, original)) delattr(self, original) def load_keys_from_file(self, filename): fp = open(filename) key_maps = fp.readlines() fp.close() for single_mapping in key_maps: mapping = single_mapping[:-1].split(' ') if len(mapping) == 2: if mapping[1][0:2] == '0x': setattr(self, mapping[0], int(mapping[1], 16)) self.reverse_map[int(mapping[1], 16)] = mapping[0] else: setattr(self, mapping[0], int(mapping[1])) self.reverse_map[int(mapping[1])] = mapping[0] def add_key_mapping(self, name, number): setattr(self, name, number) keys = Keys() mods = Mods()