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