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      """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         keyname attribute.  For example the 'ENTER' keyname will call key_ENTER
    204         with the associated L{KeyDown} event as its parameter.
    205
    206       - L{update}: This method is called every loop before making a call to
    207         L{tdl.flush}.  It is passed a single parameter detailing the time in
    208         seconds since the last update (often known as deltaTime.)
    209      """
    210      __slots__ = ('__running', '__prevTime')
    211      __running = False
    212
    213 -    def ev_QUIT(self, event):
    214          """Unless overridden this method raises a SystemExit exception closing
    215          the program."""
    216          raise SystemExit()
    217
    218 -    def ev_KEYDOWN(self, event):
    219          """Override this method to handle a L{KeyDown} event."""
    220
    221 -    def ev_KEYUP(self, event):
    222          """Override this method to handle a L{KeyUp} event."""
    223
    224 -    def ev_MOUSEDOWN(self, event):
    225          """Override this method to handle a L{MouseDown} event."""
    226
    227 -    def ev_MOUSEUP(self, event):
    228          """Override this method to handle a L{MouseUp} event."""
    229
    230 -    def ev_MOUSEMOTION(self, event):
    231          """Override this method to handle a L{MouseMotion} event."""
    232
    233 -    def update(self, deltaTime):
    234          """Override this method to handle per frame logic and drawing.
    235
    236          @type deltaTime: float
    237          @param deltaTime: This parameter tells the amount of time passed since
    238                            the last call measured in seconds as a floating point
    239                            number.
    240
    241                            You can use this variable to make your program
    242                            frame rate independent.  Use this parameter to adjust
    243                            the speed of physics, special visual effects,
    244                            and other game logic.
    245          """
    246          pass
    247
    248 -    def suspend(self):
    249          """When called the App will begin to return control to where
    250          L{App.run} was called.
    251
    252          Some further events are processed and the L{App.update} method will be
    253          called one last time before exiting
    254          (unless suspended during a call to L{App.update}.)
    255          """
    256          self.__running = False
    257
    258 -    def run(self):
    259          """Delegate control over to this App instance.  This function will
    260          process all events and send them to the special methods ev_* and key_*.
    261
    262          A call to L{App.suspend} will return the control flow back to where
    263          this function is called.  And then the App can be run again.
    264          But a single App instance can not be run multiple times simultaneously.
    265          """
    266          if self.__running:
    267              raise _tdl.TDLError('An App can not be run multiple times simultaneously')
    268          self.__running = True
    269          while self.__running:
    270              self.runOnce()
    271
    272 -    def runOnce(self):
    273          """Pump events to this App instance and then return.
    274
    275          This works in the way described in L{App.run} except it immediately
    276          returns after the first L{update} call.
    277
    278          Having multiple L{App} instances and selectively calling runOnce on
    279          them is a decent way to create a state machine.
    280          """
    281          if not hasattr(self, '__prevTime'):
    282              self.__prevTime = time.clock() # initiate __prevTime
    283          for event in get():
    284              if event.type: # exclude custom events with a blank type variable
    285                  # call the ev_* methods
    286                  method = 'ev_%s' % event.type # ev_TYPE
    287                  getattr(self, method)(event)
    288              if event.type == 'KEYDOWN':
    289                  # call the key_* methods
    290                  method = 'key_%s' % event.key # key_KEYNAME
    291                  if hasattr(self, method): # silently exclude undefined methods
    292                      getattr(self, method)(event)
    293          newTime = time.clock()
    294          self.update(newTime - self.__prevTime)
    295          self.__prevTime = newTime
    296          _tdl.flush()
    297
    298 -def _processEvents():
    299      """Flushes the event queue from libtcod into the global list _eventQueue"""
    300      global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
    301      _eventsflushed = True
    302      events = _pushedEvents # get events from event.push
    303      _pushedEvents = [] # then clear the pushed events queue
    304
    305      mouse = _Mouse()
    306      libkey = _Key()
    307      while 1:
    308          libevent = _lib.TCOD_sys_check_for_event(_tcod.TCOD_EVENT_ANY, libkey, mouse)
    309          if not libevent: # no more events from libtcod
    310              break
    311
    312          #if mouse.dx or mouse.dy:
    313          if libevent & _tcod.TCOD_EVENT_MOUSE_MOVE:
    314              events.append(MouseMotion(*mouse.motion))
    315
    316          mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
    317
    318          for oldstate, newstate, released, button in zip((_mousel, _mousem, _mouser),
    319                                      mouse.button, mouse.button_pressed, (1, 2, 3)):
    320              if released:
    321                  if not oldstate:
    322                      events.append(MouseDown(button, *mousepos))
    323                  events.append(MouseUp(button, *mousepos))
    324                  if newstate:
    325                      events.append(MouseDown(button, *mousepos))
    326              elif newstate and not oldstate:
    327                  events.append(MouseDown(button, *mousepos))
    328
    329          if mouse.wheel_up:
    330              events.append(MouseDown(4, *mousepos))
    331          if mouse.wheel_down:
    332              events.append(MouseDown(5, *mousepos))
    333
    334          _mousel = mouse.lbutton
    335          _mousem = mouse.mbutton
    336          _mouser = mouse.rbutton
    337
    338          if libkey.vk == _tcod.K_NONE:
    339              break
    340          if libkey.pressed:
    341              keyevent = KeyDown
    342          else:
    343              keyevent = KeyUp
    344          events.append(keyevent(*tuple(libkey)))
    345
    346      if _lib.TCOD_console_is_window_closed():
    347          events.append(Quit())
    348
    349      _eventQueue.extend(events)
    350
    351 -def get():
    352      """Flushes the event queue and returns the list of events.
    353
    354      This function returns L{Event} objects that can be indentified by their
    355      type attribute or their class.
    356
    357      @rtype: iterator
    358      @return: Returns an iterable of objects derived from L{Event} or anything
    359               put in a L{push} call.  If the iterator is deleted or otherwise
    360               interrupted before finishing the excess items are preserved for the
    361               next call.
    362      """
    363      _processEvents()
    364      while _eventQueue:
    365          # if there is an interruption the rest of the events stay untouched
    366          # this means you can break out of a event.get loop without losing
    367          # the leftover events
    368          yield(_eventQueue.pop(0))
    369      raise StopIteration()
    370
    371 -def push(event):
    372      """Push an event into the event buffer.
    373
    374      @type event: L{Event}-like object
    375      @param event: The event will be available on the next call to L{event.get}.
    376                    An event pushed in the middle of a L{get} will not show until
    377                    the next time L{get} called preventing push related
    378                    infinite loops.
    379      """
    380      _pushedEvents.append(event)
    381
    382 -def keyWait():
    383      """Waits until the user presses a key.  Then returns a L{KeyDown} event.
    384
    385      @rtype: L{KeyDown}
    386      """
    387      global _eventsflushed
    388      _eventsflushed = True
    389      flush = False
    390      libkey = _Key()
    391      _lib.TCOD_console_wait_for_keypress_wrapper(libkey, flush)
    392      return KeyDown(*libkey)
    393
    394 -def isWindowClosed():
    395      """Returns True if the exit button on the window has been clicked and
    396      stays True afterwards.
    397
    398      @rtype: boolean
    399      """
    400      return _lib.TCOD_console_is_window_closed()
    401
    402  __all__ = [_var for _var in locals().keys() if _var[0] != '_' and _var not in ['time', 'ctypes']]
    403