Package tdl :: Module event |
[frames] | no frames]

# Source Code for Module tdl.event

      1  """
      2      This module handles user input.
      3
      4      To handle user input you will likely want to use the L{event.get} function
      5      or create a subclass of L{event.App}.
      6       - L{event.get} iterates over recent events.
      7       - L{event.App} passes events to the overridable methods: ev_* and key_*.
      8
      9      But there are other options such as L{event.keyWait} and L{event.isWindowClosed}.
     10
     11      A few event attributes are actually string constants.
     12      Here's a reference for those:
     13       - L{Event.type}
     14
     15         'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP', or 'MOUSEMOTION.'
     16
     17       - L{MouseButtonEvent.button} (found in L{MouseDown} and L{MouseUp} events)
     18
     19         'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN'
     20
     21       - L{KeyEvent.key} (found in L{KeyDown} and L{KeyUp} events)
     22
     23         'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
     24         'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
     25         'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
     26         'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
     27         'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
     28         'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
     29         'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
     30         'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
     31  """
     32
     33  import time
     34  import ctypes
     35
     36  from .__tcod import _lib, _Mouse, _Key
     37  from . import __tcod as _tcod
     38  import tdl as _tdl
     39
     40  _eventQueue = []
     41  _pushedEvents = []
     42
     43  _mousel = 0
     44  _mousem = 0
     45  _mouser = 0
     46
     47  # this interpets the constants from libtcod and makes a key -> keyname dictionary
     48 -def _parseKeyNames(module):
     49      """
     50      returns a dictionary mapping of human readable key names to their keycodes
     51      this parses constants with the names of K_* and makes code=name pairs
     52      this is for KeyEvent.key variable and that enables things like:
     53      if (event.key == 'PAGEUP'):
     54      """
     55      _keyNames = {}
     56      for attr in dir(module): # from the modules variables
     57          if attr[:2] == 'K_': # get the K_* constants
     58              _keyNames[getattr(_tcod, attr)] = attr[2:] # and make CODE=NAME pairs
     59      return _keyNames
     60
     61  _keyNames = _parseKeyNames(_tcod)
     62
     63 -class Event(object):
     64      """Base Event class.
     65
     66      You can easily subclass this to make your own events.  Be sure to set
     67      the class attribute L{Event.type} for it to be passed to a custom L{App}
     68      ev_* method."""
     69      __slots__ = ('__weakref__',)
     70      type = None
     71      """String constant representing the type of event.
     72
     73      The L{App} ev_* methods depend on this attribute.
     74
     75      Can be: 'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP', or 'MOUSEMOTION.'
     76      """
     77
     78 -    def __repr__(self):
     79          """List an events public attributes in print calls.
     80          """
     81          attrdict = {}
     82          for varname in dir(self):
     83              if '_' == varname[0]:
     84                  continue
     85              attrdict[varname] = self.__getattribute__(varname)
     86          return '%s Event %s' % (self.__class__.__name__, repr(attrdict))
     87
     88 -class Quit(Event):
     89      """Fired when the window is closed by the user.
     90      """
     91      __slots__ = ()
     92      type = 'QUIT'
     93
     94 -class KeyEvent(Event):
     95      __slots__ = ('key', 'char', 'shift', 'alt', 'control',
     96                   'leftAlt', 'leftCtrl', 'rightAlt', 'rightCtrl')
     97
     98 -    def __init__(self, key, char, lalt, lctrl, ralt, rctrl, shift):
     99          self.key = _keyNames[key]
    100          """Human readable name of the key pressed.
    101
    102          Can be one of
    103          'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
    104          'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
    105          'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
    106          'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    107          'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
    108          'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
    109          'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
    110          'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
    111          @type: string"""
    112          char = char if isinstance(char, str) else char.decode()
    113          self.char = char.replace('\x00', '') # change null to empty string
    114          """A single character string of the letter or symbol pressed.
    115
    116          Special characters like delete and return are not cross-platform.
    117          L{key} should be used instead for special keys.
    118          @type: string"""
    119          self.leftAlt = bool(lalt)
    120          """@type: boolean"""
    121          self.rightAlt = bool(ralt)
    122          """@type: boolean"""
    123          self.leftCtrl = bool(lctrl)
    124          """@type: boolean"""
    125          self.rightCtrl = bool(rctrl)
    126          """@type: boolean"""
    127          self.shift = bool(shift)
    128          """True if shift was held down during this event.
    129          @type: boolean"""
    130          self.alt = bool(lalt or ralt)
    131          """True if alt was held down during this event.
    132          @type: boolean"""
    133          self.control = bool(lctrl or rctrl)
    134          """True if control was held down during this event.
    135          @type: boolean"""
    136
    137 -class KeyDown(KeyEvent):
    138      """Fired when the user presses a key on the keyboard or a key repeats.
    139      """
    140      __slots__ = ()
    141      type = 'KEYDOWN'
    142
    143 -class KeyUp(KeyEvent):
    144      """Fired when the user releases a key on the keyboard.
    145      """
    146      __slots__ = ()
    147      type = 'KEYUP'
    148
    149  _mouseNames = {1: 'LEFT', 2: 'MIDDLE', 3: 'RIGHT', 4: 'SCROLLUP', 5: 'SCROLLDOWN'}
    150 -class MouseButtonEvent(Event):
    151      __slots__ = ('button', 'pos', 'cell')
    152
    153 -    def __init__(self, button, pos, cell):
    154          self.button = _mouseNames[button]
    155          """Can be one of
    156          'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN'
    157          @type: string"""
    158          self.pos = pos
    159          """(x, y) position of the mouse on the screen
    160          @type: (int, int)"""
    161          self.cell = cell
    162          """(x, y) position of the mouse snapped to a cell on the root console
    163          @type: (int, int)"""
    164
    165 -class MouseDown(MouseButtonEvent):
    166      """Fired when a mouse button is pressed."""
    167      __slots__ = ()
    168      type = 'MOUSEDOWN'
    169
    170 -class MouseUp(MouseButtonEvent):
    171      """Fired when a mouse button is released."""
    172      __slots__ = ()
    173      type = 'MOUSEUP'
    174
    175 -class MouseMotion(Event):
    176      """Fired when the mouse is moved."""
    177      __slots__ = ('pos',  'motion', 'cell', 'cellmotion')
    178      type = 'MOUSEMOTION'
    179
    180 -    def __init__(self, pos, cell, motion, cellmotion):
    181          self.pos = pos
    182          """(x, y) position of the mouse on the screen.
    183          type: (int, int)"""
    184          self.cell = cell
    185          """(x, y) position of the mouse snapped to a cell on the root console.
    186          type: (int, int)"""
    187          self.motion = motion
    188          """(x, y) motion of the mouse on the screen.
    189          type: (int, int)"""
    190          self.cellmotion = cellmotion
    191          """(x, y) mostion of the mouse moving over cells on the root console.
    192          type: (int, int)"""
    193
    194 -class App(object):
    195      """
    196      Application framework.
    197
    198       - ev_*: Events are passed to methods based on their L{Event.type} attribute.
    199         If an event type is 'KEYDOWN' the ev_KEYDOWN method will be called
    200         with the event instance as a parameter.
    201
    202       - key_*: When a key is pressed another method will be called based on the
    203         L{KeyEvent.key} attribute.  For example the 'ENTER' key will call key_ENTER
    204         with the associated L{KeyDown} event as its parameter.
    205
    206       - L{update}: This method is called every loop.  It is passed a single
    207         parameter detailing the time in seconds since the last update
    208         (often known as deltaTime.)
    209
    210         You may want to call drawing routines in this method followed by
    211         L{tdl.flush}.
    212      """
    213      __slots__ = ('__running', '__prevTime')
    214      __running = False
    215      __prevTime = None
    216
    217 -    def ev_QUIT(self, event):
    218          """Unless overridden this method raises a SystemExit exception closing
    219          the program."""
    220          raise SystemExit()
    221
    222 -    def ev_KEYDOWN(self, event):
    223          """Override this method to handle a L{KeyDown} event."""
    224
    225 -    def ev_KEYUP(self, event):
    226          """Override this method to handle a L{KeyUp} event."""
    227
    228 -    def ev_MOUSEDOWN(self, event):
    229          """Override this method to handle a L{MouseDown} event."""
    230
    231 -    def ev_MOUSEUP(self, event):
    232          """Override this method to handle a L{MouseUp} event."""
    233
    234 -    def ev_MOUSEMOTION(self, event):
    235          """Override this method to handle a L{MouseMotion} event."""
    236
    237 -    def update(self, deltaTime):
    238          """Override this method to handle per frame logic and drawing.
    239
    240          @type deltaTime: float
    241          @param deltaTime: This parameter tells the amount of time passed since
    242                            the last call measured in seconds as a floating point
    243                            number.
    244
    245                            You can use this variable to make your program
    246                            frame rate independent.
    247                            Use this parameter to adjust the speed of motion,
    248                            timers, and other game logic.
    249          """
    250          pass
    251
    252 -    def suspend(self):
    253          """When called the App will begin to return control to where
    254          L{App.run} was called.
    255
    256          Some further events are processed and the L{App.update} method will be
    257          called one last time before exiting
    258          (unless suspended during a call to L{App.update}.)
    259          """
    260          self.__running = False
    261
    262 -    def run(self):
    263          """Delegate control over to this App instance.  This function will
    264          process all events and send them to the special methods ev_* and key_*.
    265
    266          A call to L{App.suspend} will return the control flow back to where
    267          this function is called.  And then the App can be run again.
    268          But a single App instance can not be run multiple times simultaneously.
    269          """
    270          if self.__running:
    271              raise _tdl.TDLError('An App can not be run multiple times simultaneously')
    272          self.__running = True
    273          while self.__running:
    274              self.runOnce()
    275
    276 -    def runOnce(self):
    277          """Pump events to this App instance and then return.
    278
    279          This works in the way described in L{App.run} except it immediately
    280          returns after the first L{update} call.
    281
    282          Having multiple L{App} instances and selectively calling runOnce on
    283          them is a decent way to create a state machine.
    284          """
    285          if self.__prevTime is None:
    286              self.__prevTime = time.clock() # initiate __prevTime
    287          for event in get():
    288              if event.type: # exclude custom events with a blank type variable
    289                  # call the ev_* methods
    290                  method = 'ev_%s' % event.type # ev_TYPE
    291                  getattr(self, method)(event)
    292              if event.type == 'KEYDOWN':
    293                  # call the key_* methods
    294                  method = 'key_%s' % event.key # key_KEYNAME
    295                  if hasattr(self, method): # silently exclude undefined methods
    296                      getattr(self, method)(event)
    297          newTime = time.clock()
    298          self.update(newTime - self.__prevTime)
    299          self.__prevTime = newTime
    300          #_tdl.flush()
    301
    302 -def _processEvents():
    303      """Flushes the event queue from libtcod into the global list _eventQueue"""
    304      global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
    305      _eventsflushed = True
    306      events = _pushedEvents # get events from event.push
    307      _pushedEvents = [] # then clear the pushed events queue
    308
    309      mouse = _Mouse()
    310      libkey = _Key()
    311      while 1:
    312          libevent = _lib.TCOD_sys_check_for_event(_tcod.TCOD_EVENT_ANY, libkey, mouse)
    313          if not libevent: # no more events from libtcod
    314              break
    315
    316          #if mouse.dx or mouse.dy:
    317          if libevent & _tcod.TCOD_EVENT_MOUSE_MOVE:
    318              events.append(MouseMotion(*mouse.motion))
    319
    320          mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
    321
    322          for oldstate, newstate, released, button in zip((_mousel, _mousem, _mouser),
    323                                      mouse.button, mouse.button_pressed, (1, 2, 3)):
    324              if released:
    325                  if not oldstate:
    326                      events.append(MouseDown(button, *mousepos))
    327                  events.append(MouseUp(button, *mousepos))
    328                  if newstate:
    329                      events.append(MouseDown(button, *mousepos))
    330              elif newstate and not oldstate:
    331                  events.append(MouseDown(button, *mousepos))
    332
    333          if mouse.wheel_up:
    334              events.append(MouseDown(4, *mousepos))
    335          if mouse.wheel_down:
    336              events.append(MouseDown(5, *mousepos))
    337
    338          _mousel = mouse.lbutton
    339          _mousem = mouse.mbutton
    340          _mouser = mouse.rbutton
    341
    342          if libkey.vk == _tcod.K_NONE:
    343              break
    344          if libkey.pressed:
    345              keyevent = KeyDown
    346          else:
    347              keyevent = KeyUp
    348          events.append(keyevent(*tuple(libkey)))
    349
    350      if _lib.TCOD_console_is_window_closed():
    351          events.append(Quit())
    352
    353      _eventQueue.extend(events)
    354
    355 -def get():
    356      """Flushes the event queue and returns the list of events.
    357
    358      This function returns L{Event} objects that can be indentified by their
    359      type attribute or their class.
    360
    361      @rtype: iterator
    362      @return: Returns an iterable of objects derived from L{Event} or anything
    363               put in a L{push} call.  If the iterator is deleted or otherwise
    364               interrupted before finishing the excess items are preserved for the
    365               next call.
    366      """
    367      _processEvents()
    368      while _eventQueue:
    369          # if there is an interruption the rest of the events stay untouched
    370          # this means you can break out of a event.get loop without losing
    371          # the leftover events
    372          yield(_eventQueue.pop(0))
    373      raise StopIteration()
    374
    375 -def push(event):
    376      """Push an event into the event buffer.
    377
    378      @type event: L{Event}-like object
    379      @param event: The event will be available on the next call to L{event.get}.
    380                    An event pushed in the middle of a L{get} will not show until
    381                    the next time L{get} called preventing push related
    382                    infinite loops.
    383      """
    384      _pushedEvents.append(event)
    385
    386 -def keyWait():
    387      """Waits until the user presses a key.  Then returns a L{KeyDown} event.
    388
    389      @rtype: L{KeyDown}
    390      """
    391      global _eventsflushed
    392      _eventsflushed = True
    393      flush = False
    394      libkey = _Key()
    395      _lib.TCOD_console_wait_for_keypress_wrapper(libkey, flush)
    396      return KeyDown(*libkey)
    397
    398 -def isWindowClosed():
    399      """Returns True if the exit button on the window has been clicked and
    400      stays True afterwards.
    401
    402      @rtype: boolean
    403      """
    404      return _lib.TCOD_console_is_window_closed()
    405
    406  __all__ = [_var for _var in locals().keys() if _var[0] != '_' and _var not in ['time', 'ctypes']]
    407