Source code for pyparadigm.eventlistener

"""The Eventlistener wraps pygames event-loop.

The Core method is the listen() method.
It gathers the events that have piled up in pygame so far
and processes them acording to handler functions.
This allows for a main-loop free script design, which is more suited for
experimental paradigms.
In a typical experiment the script just waits for a userinput and does nothing,
or only a very few things in between. Approaching this need with a main
event-loop requires the implementation of some sort of statemachine, which again
requires quite some code.

The EventListener enables one to write scripts in a time-linear manner, and only
dab into local event-loops whenever neccessary throught the listen-function.

There are a few pre-implemented methods, which cover most of those use-cases in
the developement of experimental paradigms.

* wait_for_keypress() will return once a key was pressed n times.
* wait_for_keys_timed_out() will wait for one of multiple possible keys, 
    but return after the given timeout in any case
* and wait_for_seconds will simply wait the given time, but in the mean-time run
  what ever handlers were passed to the EventListener.

By default, there is one permanent handler, which will call exit(1) when 
Ctrl + c is pressed.
"""
import pygame

from enum import Enum

import time
import itertools as itt

[docs]class EventConsumerInfo(Enum): """Can be returned by event-handler functions to communicate with the listener. For Details see EventListener""" DONT_CARE = 0
CONSUMED = 1
[docs]class EventListener(object): """ :param permanent_handlers: iterable of permanent handlers :type permanent_handlers: iterable :param use_ctrl_c_handler: specifies whether a handler that quits the script when ctrl + c is pressed should be used :type use_ctrl_c_handler: Bool """ @staticmethod def _exit_on_ctrl_c(event): if event.type == pygame.KEYDOWN \ and event.key == pygame.K_c \ and pygame.key.get_mods() & pygame.KMOD_CTRL: pygame.quit() exit(1) else: return EventConsumerInfo.DONT_CARE def __init__(self, permanent_handlers=None, use_ctrl_c_handler=True): self._current_q = [] if use_ctrl_c_handler: self.permanent_handlers = ( EventListener._exit_on_ctrl_c, ) if permanent_handlers: self.permanent_handlers += permanent_handlers else: self.permanent_handlers = permanent_handlers or [] def _get_q(self): self._current_q = itt.chain(self._current_q, pygame.event.get()) return self._current_q
[docs] def listen(self, temporary_handlers = None): """When listen() is called all queued pygame.Events will be passed to all registered listeners. There are two ways to register a listener: 1. as a permanent listener, that is always executed for every event. These are registered by passing the handler-functions during construction 2. as a temporary listener, that will only be executed during the current call to listen(). These are registered by passing the handler functions as arguments to listen() When a handler is called it can provoke three different reactions through its return value. 1. It can return EventConsumerInfo.DONT_CARE in which case the EventListener will pass the event to the next handler in line, or go to the next event, if the last handler was called. 2. It can return EventConsumerInfo.CONSUMED in which case the event will not be passed to following handlers, and the next event in line will be processed. 3. It can return anything else (including None, which will be returned if no return value is specified) in this case the listen()-method will return the result of the handler. Therefore all permanent handlers should usually return EventConsumerInfo.DONT_CARE """ if temporary_handlers: funcs = self.permanent_handlers + temporary_handlers else: funcs = self.permanent_handlers for event in self._get_q(): for func in funcs: ret = func(event) if ret == EventConsumerInfo.CONSUMED: break if ret == EventConsumerInfo.DONT_CARE: continue
else: return ret
[docs] def wait_for_n_keypresses(self, key, n=1): """Waits till one key was pressed n times. :param key: the key to be pressed as defined by pygame. E.g. pygame.K_LEFT for the left arrow key :type key: int :param n: number of repetitions till the function returns :type n: int """ my_const = "key_consumed" counter = 0 keypress_listener = lambda e: my_const \ if e.type == pygame.KEYDOWN and e.key == key \ else EventConsumerInfo.DONT_CARE while counter < n: if self.listen((keypress_listener,)) == my_const:
counter += 1
[docs] def wait_for_keys(self, keys, timeout=0): """Waits until one of the specified keys was pressed, and returns which key was pressed. :param keys: iterable of integers of pygame-keycodes :type keys: iterable :param timeout: number of seconds to wait till the function returns :type timeout: float :returns: The keycode of the pressed key, or None in case of timeout :rtype: int """ listeners = tuple(lambda e, key=key: key \ if e.type == pygame.KEYDOWN and e.key == key \ else EventConsumerInfo.DONT_CARE for key in keys) start = time.time() while timeout == 0 or time.time() - start < timeout: res = self.listen(listeners) if res in keys: return res
return None
[docs] def wait_for_seconds(self, seconds): """basically time.sleep() but in the mean-time the permanent handlers are executed""" start = time.time() while time.time() - start < seconds:
self.listen()