Source code for ggame.app

"""
The ggame :class:`App` class encapsulates functionality required for
initiating a graphics window in the browser, executing code using Javascript
animate, routing browser UI events (e.g. mouse and keyboard input) to
user Python code, and managing graphics elements (instances of
:class:`Sprite`, for example).
"""

# app.py

import traceback
from ggame.sysdeps import GFX_Window
from ggame.event import MouseEvent, KeyEvent


[docs] class App: """ The :class:`App` class is a (typically subclassed) class that encapsulates handling of the display system, and processing user events. The :class:`App` class also manages lists of all :class:`Sprite` instances in the application. When subclassing :class:`App` you may elect to instantiate most of your sprite objects in the initialization section. Processing that must occur on a per-frame basis may be included by overriding the :meth:`~App.run` method. This is also an appropriate location to call similar 'step' methods for your various customized sprite classes. Once your application class has been instantiated, begin the frame drawing process by calling its :meth:`~App.run` method. The :class:`App` class is instantiated either by specifying the desired app window size in pixels, as two parameters::: myapp = App(640,480) or by providing no size parameters at all::: myapp = App() in which case, the full browser window size is used. NOTE: Only **one** instance of an :class:`App` class or subclass may be instantiated at a time. """ spritelist = [] """ List of all sprites currently active in the application. """ _eventdict = {} _spritesdict = {} _spritesadded = False win = None def __init__(self, *args): if App.win is None and (not args or len(args) == 2): x = y = 0 if len(args) == 2: x = args[0] y = args[1] App.win = GFX_Window(x, y, type(self).destroy) App.width = App.win.width App.height = App.win.height # Add existing sprites to the window if not App._spritesadded and App.spritelist: App._spritesadded = True for sprite in App.spritelist: App.win.add(sprite.gfx) App.win.bind(KeyEvent.keydown, type(self)._keyEvent) App.win.bind(KeyEvent.keyup, type(self)._keyEvent) App.win.bind(KeyEvent.keypress, type(self)._keyEvent) App.win.bind(MouseEvent.mousewheel, type(self)._mouseEvent) App.win.bind(MouseEvent.mousemove, type(self)._mouseEvent) App.win.bind(MouseEvent.mousedown, type(self)._mouseEvent) App.win.bind(MouseEvent.mouseup, type(self)._mouseEvent) App.win.bind(MouseEvent.click, type(self)._mouseEvent) App.win.bind(MouseEvent.dblclick, type(self)._mouseEvent) self.userfunc = None @classmethod def _routeEvent(cls, event, evtlist): for callback in reversed(evtlist): if not event.consumed: try: callback(event) except BaseException: traceback.print_exc() raise @classmethod def _keyEvent(cls, hwevent): evtlist = App._eventdict.get( (hwevent.type, KeyEvent.keys.get(hwevent.keyCode, 0)), [] ) evtlist.extend(App._eventdict.get((hwevent.type, "*"), [])) if evtlist: evt = KeyEvent(hwevent) cls._routeEvent(evt, evtlist) return False @classmethod def _mouseEvent(cls, hwevent): evtlist = App._eventdict.get(hwevent.type, []) if evtlist: evt = MouseEvent(cls, hwevent) cls._routeEvent(evt, evtlist) return False @classmethod def add(cls, obj): """ Add a sprite object to the global lists. :param Sprite obj: The sprite reference to add. :returns: None """ if App.win is not None: App.win.add(obj.gfx) App.spritelist.append(obj) # find out if sprite type is in dictionary without triggering pylint if not App._spritesdict.get(type(obj), False): App._spritesdict[type(obj)] = [] App._spritesdict[type(obj)].append(obj) @classmethod def remove(cls, obj): """ Remove a sprite object from the global lists. :param Sprite obj: The sprite reference to remove. :returns: None """ App.spritelist.remove(obj) # remove from underlying layer only if existed in ours if App.win is not None: App.win.remove(obj.gfx) App._spritesdict[type(obj)].remove(obj) def _animate(self, _dummy): if App.win: try: if self.userfunc: self.userfunc() else: self.step() except BaseException: traceback.print_exc() raise App.win.animate(self._animate) @classmethod def destroy(cls): """ This will close the display window/tab, remove all references to sprites and place the `App` class in a state in which a new application could be instantiated. """ if App.win: App.win.unbind(KeyEvent.keydown) App.win.unbind(KeyEvent.keyup) App.win.unbind(KeyEvent.keypress) App.win.unbind(MouseEvent.mousewheel) App.win.unbind(MouseEvent.mousemove) App.win.unbind(MouseEvent.mousedown) App.win.unbind(MouseEvent.mouseup) App.win.unbind(MouseEvent.click) App.win.unbind(MouseEvent.dblclick) for s in list(App.spritelist): s.destroy() App.win.destroy() App.win = None App.spritelist = [] App._spritesdict = {} App._eventdict = {} App._spritesadded = False
[docs] @classmethod def listenKeyEvent(cls, eventtype, key, callback): """ Register to receive keyboard events. :param str eventtype: The type of key event to receive (value is one of: `'keydown'`, `'keyup'` or `'keypress'`). :param str key: Identify the keyboard key (e.g. `'space'`, `'left arrow'`, etc.) to receive events for. :param function callback: The function or method that will be called with the :class:`~ggame.event.KeyEvent` object when the event occurs. :returns: Nothing See the source for :class:`~ggame.event.KeyEvent` for a list of key names to use with the `key` paramter. """ evtlist = App._eventdict.get((eventtype, key), []) if callback not in evtlist: evtlist.append(callback) App._eventdict[(eventtype, key)] = evtlist
[docs] @classmethod def listenMouseEvent(cls, eventtype, callback): """ Register to receive mouse events. :param str eventtype: The type of mouse event to receive (value is one of: `'mousemove'`, `'mousedown'`, `'mouseup'`, `'click'`, `'dblclick'` or `'mousewheel'`). :param function callback: The function or method that will be called with the :class:`ggame.event.MouseEvent` object when the event occurs. :returns: Nothing """ evtlist = App._eventdict.get(eventtype, []) if callback not in evtlist: evtlist.append(callback) App._eventdict[eventtype] = evtlist
[docs] @classmethod def unlistenKeyEvent(cls, eventtype, key, callback): """ Use this method to remove a registration to receive a particular keyboard event. Arguments must exactly match those used when registering for the event. :param str eventtype: The type of key event to stop receiving (value is one of: `'keydown'`, `'keyup'` or `'keypress'`). :param str key: The keyboard key (e.g. `'space'`, `'left arrow'`, etc.) to stop receiving events for. :param function callback: The function or method that will no longer be called with the :class:`~ggame.event.KeyEvent` object when the event occurs. :returns: Nothing See the source for :class:`~ggame.event.KeyEvent` for a list of key names to use with the `key` paramter. """ App._eventdict[(eventtype, key)].remove(callback)
[docs] @classmethod def unlistenMouseEvent(cls, eventtype, callback): """ Use this method to remove a registration to receive a particular mouse event. Arguments must exactly match those used when registering for the event. :param str eventtype: The type of mouse event to stop receiving events for (value is one of: `'mousemove'`, `'mousedown'`, `'mouseup'`, `'click'`, `'dblclick'` or `'mousewheel'`). :param function callback: The function or method that will no longer be called with the :class:`ggame.event.MouseEvent` object when the event occurs. :returns: Nothing """ App._eventdict[eventtype].remove(callback)
[docs] @classmethod def getSpritesbyClass(cls, sclass): """ Returns a list of all active sprites of a given class. :param class sclass: A class name (e.g. 'Sprite') or subclass. :returns: A (potentially empty) list of sprite references. """ return App._spritesdict.get(sclass, [])[:]
[docs] def step(self): """ The :meth:`~App.step` method is called once per animation frame. Override this method in your own subclass of :class:`App` to perform periodic calculations, such as checking for sprite collisions, or calling 'step' functions in your own customized sprite classes. The base class :meth:`~App.step` method is empty and is intended to be overriden. :returns: Nothing """
[docs] def run(self, userfunc=None): """ Calling the :meth:`~App.run` method begins the animation process whereby the :meth:`~App.step` method is called once per animation frame. :param function userfunc: Any function or method which shall be called once per animation frame. :returns: Nothing """ self.userfunc = userfunc App.win.animate(self._animate)