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 when printed.
     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', 'keychar', 'shift', 'alt', 'control',
     96                   'leftAlt', 'leftCtrl', 'rightAlt', 'rightCtrl')
     97
     98 -    def __init__(self, key, char, lalt, lctrl, ralt, rctrl, shift):
     99          # Convert keycodes into string, but use string if passed
    100          self.key = key if isinstance(key, str) else _keyNames[key]
    101          """Human readable names of the key pressed.
    102          Non special characters will show up as 'CHAR'.
    103
    104          Can be one of
    105          'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
    106          'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
    107          'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
    108          'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    109          'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
    110          'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
    111          'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
    112          'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
    113
    114          For the actual character instead of 'CHAR' use L{keychar}.
    115          @type: string"""
    116          char = char if isinstance(char, str) else char.decode()
    117          self.char = char.replace('\x00', '') # change null to empty string
    118          """A single character string of the letter or symbol pressed.
    119
    120          Special characters like delete and return are not cross-platform.
    121          L{key} or L{keychar} should be used instead for special keys.
    122          Characters are also case sensitive.
    123          @type: string"""
    124          # get the best out of self.key and self.char
    125          self.keychar = self.char if self.key == 'CHAR' else self.key
    126          """Similar to L{key} but returns a case sensitive letter or symbol
    127          instead of 'CHAR'.
    128
    129          This variable makes available the widest variety of symbols and should
    130          be used for key-mappings or anywhere where a narrower sample of keys
    131          isn't needed.
    132          """
    133          self.leftAlt = bool(lalt)
    134          """@type: boolean"""
    135          self.rightAlt = bool(ralt)
    136          """@type: boolean"""
    137          self.leftCtrl = bool(lctrl)
    138          """@type: boolean"""
    139          self.rightCtrl = bool(rctrl)
    140          """@type: boolean"""
    141          self.shift = bool(shift)
    142          """True if shift was held down during this event.
    143          @type: boolean"""
    144          self.alt = bool(lalt or ralt)
    145          """True if alt was held down during this event.
    146          @type: boolean"""
    147          self.control = bool(lctrl or rctrl)
    148          """True if control was held down during this event.
    149          @type: boolean"""
    150
    151 -class KeyDown(KeyEvent):
    152      """Fired when the user presses a key on the keyboard or a key repeats.
    153      """
    154      __slots__ = ()
    155      type = 'KEYDOWN'
    156
    157 -class KeyUp(KeyEvent):
    158      """Fired when the user releases a key on the keyboard.
    159      """
    160      __slots__ = ()
    161      type = 'KEYUP'
    162
    163  _mouseNames = {1: 'LEFT', 2: 'MIDDLE', 3: 'RIGHT', 4: 'SCROLLUP', 5: 'SCROLLDOWN'}
    164 -class MouseButtonEvent(Event):
    165      __slots__ = ('button', 'pos', 'cell')
    166
    167 -    def __init__(self, button, pos, cell):
    168          self.button = _mouseNames[button]
    169          """Can be one of
    170          'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN'
    171          @type: string"""
    172          self.pos = pos
    173          """(x, y) position of the mouse on the screen
    174          @type: (int, int)"""
    175          self.cell = cell
    176          """(x, y) position of the mouse snapped to a cell on the root console
    177          @type: (int, int)"""
    178
    179 -class MouseDown(MouseButtonEvent):
    180      """Fired when a mouse button is pressed."""
    181      __slots__ = ()
    182      type = 'MOUSEDOWN'
    183
    184 -class MouseUp(MouseButtonEvent):
    185      """Fired when a mouse button is released."""
    186      __slots__ = ()
    187      type = 'MOUSEUP'
    188
    189 -class MouseMotion(Event):
    190      """Fired when the mouse is moved."""
    191      __slots__ = ('pos',  'motion', 'cell', 'cellmotion')
    192      type = 'MOUSEMOTION'
    193
    194 -    def __init__(self, pos, cell, motion, cellmotion):
    195          self.pos = pos
    196          """(x, y) position of the mouse on the screen.
    197          type: (int, int)"""
    198          self.cell = cell
    199          """(x, y) position of the mouse snapped to a cell on the root console.
    200          type: (int, int)"""
    201          self.motion = motion
    202          """(x, y) motion of the mouse on the screen.
    203          type: (int, int)"""
    204          self.cellmotion = cellmotion
    205          """(x, y) mostion of the mouse moving over cells on the root console.
    206          type: (int, int)"""
    207
    208 -class App(object):
    209      """
    210      Application framework.
    211
    212       - ev_*: Events are passed to methods based on their L{Event.type} attribute.
    213         If an event type is 'KEYDOWN' the ev_KEYDOWN method will be called
    214         with the event instance as a parameter.
    215
    216       - key_*: When a key is pressed another method will be called based on the
    217         L{KeyEvent.key} attribute.  For example the 'ENTER' key will call key_ENTER
    218         with the associated L{KeyDown} event as its parameter.
    219
    220       - L{update}: This method is called every loop.  It is passed a single
    221         parameter detailing the time in seconds since the last update
    222         (often known as deltaTime.)
    223
    224         You may want to call drawing routines in this method followed by
    225         L{tdl.flush}.
    226      """
    227      __slots__ = ('__running', '__prevTime')
    228      __running = False
    229      __prevTime = None
    230
    231 -    def ev_QUIT(self, event):
    232          """Unless overridden this method raises a SystemExit exception closing
    233          the program."""
    234          raise SystemExit()
    235
    236 -    def ev_KEYDOWN(self, event):
    237          """Override this method to handle a L{KeyDown} event."""
    238
    239 -    def ev_KEYUP(self, event):
    240          """Override this method to handle a L{KeyUp} event."""
    241
    242 -    def ev_MOUSEDOWN(self, event):
    243          """Override this method to handle a L{MouseDown} event."""
    244
    245 -    def ev_MOUSEUP(self, event):
    246          """Override this method to handle a L{MouseUp} event."""
    247
    248 -    def ev_MOUSEMOTION(self, event):
    249          """Override this method to handle a L{MouseMotion} event."""
    250
    251 -    def update(self, deltaTime):
    252          """Override this method to handle per frame logic and drawing.
    253
    254          @type deltaTime: float
    255          @param deltaTime: This parameter tells the amount of time passed since
    256                            the last call measured in seconds as a floating point
    257                            number.
    258
    259                            You can use this variable to make your program
    260                            frame rate independent.
    261                            Use this parameter to adjust the speed of motion,
    262                            timers, and other game logic.
    263          """
    264          pass
    265
    266 -    def suspend(self):
    267          """When called the App will begin to return control to where
    268          L{App.run} was called.
    269
    270          Some further events are processed and the L{App.update} method will be
    271          called one last time before exiting
    272          (unless suspended during a call to L{App.update}.)
    273          """
    274          self.__running = False
    275
    276 -    def run(self):
    277          """Delegate control over to this App instance.  This function will
    278          process all events and send them to the special methods ev_* and key_*.
    279
    280          A call to L{App.suspend} will return the control flow back to where
    281          this function is called.  And then the App can be run again.
    282          But a single App instance can not be run multiple times simultaneously.
    283          """
    284          if self.__running:
    285              raise _tdl.TDLError('An App can not be run multiple times simultaneously')
    286          self.__running = True
    287          while self.__running:
    288              self.runOnce()
    289
    290 -    def runOnce(self):
    291          """Pump events to this App instance and then return.
    292
    293          This works in the way described in L{App.run} except it immediately
    294          returns after the first L{update} call.
    295
    296          Having multiple L{App} instances and selectively calling runOnce on
    297          them is a decent way to create a state machine.
    298          """
    299          if self.__prevTime is None:
    300              self.__prevTime = time.clock() # initiate __prevTime
    301          for event in get():
    302              if event.type: # exclude custom events with a blank type variable
    303                  # call the ev_* methods
    304                  method = 'ev_%s' % event.type # ev_TYPE
    305                  getattr(self, method)(event)
    306              if event.type == 'KEYDOWN':
    307                  # call the key_* methods
    308                  method = 'key_%s' % event.key # key_KEYNAME
    309                  if hasattr(self, method): # silently exclude undefined methods
    310                      getattr(self, method)(event)
    311          newTime = time.clock()
    312          self.update(newTime - self.__prevTime)
    313          self.__prevTime = newTime
    314          #_tdl.flush()
    315
    316 -def _processEvents():
    317      """Flushes the event queue from libtcod into the global list _eventQueue"""
    318      global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
    319      _eventsflushed = True
    320      events = _pushedEvents # get events from event.push
    321      _pushedEvents = [] # then clear the pushed events queue
    322
    323      mouse = _Mouse()
    324      libkey = _Key()
    325      while 1:
    326          libevent = _lib.TCOD_sys_check_for_event(_tcod.TCOD_EVENT_ANY, libkey, mouse)
    327          if not libevent: # no more events from libtcod
    328              break
    329
    330          #if mouse.dx or mouse.dy:
    331          if libevent & _tcod.TCOD_EVENT_MOUSE_MOVE:
    332              events.append(MouseMotion(*mouse.motion))
    333
    334          mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
    335
    336          for oldstate, newstate, released, button in zip((_mousel, _mousem, _mouser),
    337                                      mouse.button, mouse.button_pressed, (1, 2, 3)):
    338              if released:
    339                  if not oldstate:
    340                      events.append(MouseDown(button, *mousepos))
    341                  events.append(MouseUp(button, *mousepos))
    342                  if newstate:
    343                      events.append(MouseDown(button, *mousepos))
    344              elif newstate and not oldstate:
    345                  events.append(MouseDown(button, *mousepos))
    346
    347          if mouse.wheel_up:
    348              events.append(MouseDown(4, *mousepos))
    349          if mouse.wheel_down:
    350              events.append(MouseDown(5, *mousepos))
    351
    352          _mousel = mouse.lbutton
    353          _mousem = mouse.mbutton
    354          _mouser = mouse.rbutton
    355
    356          if libkey.vk == _tcod.K_NONE:
    357              break
    358          if libkey.pressed:
    359              keyevent = KeyDown
    360          else:
    361              keyevent = KeyUp
    362          events.append(keyevent(*tuple(libkey)))
    363
    364      if _lib.TCOD_console_is_window_closed():
    365          events.append(Quit())
    366
    367      _eventQueue.extend(events)
    368
    369 -def get():
    370      """Flushes the event queue and returns the list of events.
    371
    372      This function returns L{Event} objects that can be indentified by their
    373      type attribute or their class.
    374
    375      @rtype: iterator
    376      @return: Returns an iterable of objects derived from L{Event} or anything
    377               put in a L{push} call.  If the iterator is deleted or otherwise
    378               interrupted before finishing the excess items are preserved for the
    379               next call.
    380      """
    381      def eventGenerator():
    382          while _eventQueue:
    383              # if there is an interruption the rest of the events stay untouched
    384              # this means you can break out of a event.get loop without losing
    385              # the leftover events
    386              yield(_eventQueue.pop(0))
    387          raise StopIteration()
    388      _processEvents()
    389      return eventGenerator()
    390
    391 -def push(event):
    392      """Push an event into the event buffer.
    393
    394      @type event: L{Event}-like object
    395      @param event: The event will be available on the next call to L{event.get}.
    396                    An event pushed in the middle of a L{get} will not show until
    397                    the next time L{get} called preventing push related
    398                    infinite loops.
    399      """
    400      _pushedEvents.append(event)
    401
    402 -def keyWait():
    403      """Waits until the user presses a key.
    404      Then returns a L{KeyDown} event.
    405
    406      Key events will repeat if held down.
    407
    408      A click to close the window will be converted into an Alt+F4 KeyDown event.
    409
    410      @rtype: L{KeyDown}
    411      """
    412      while 1:
    413          for event in get():
    414              if event.type == 'KEYDOWN':
    415                  return event
    416              if event.type == 'QUIT':
    417                  # convert QUIT into alt+F4
    418                  return KeyDown('F4', '', True, False, True, False, False)
    419          time.sleep(.001)
    420
    421 -def isWindowClosed():
    422      """Returns True if the exit button on the window has been clicked and
    423      stays True afterwards.
    424
    425      @rtype: boolean
    426      """
    427      return _lib.TCOD_console_is_window_closed()
    428
    429  __all__ = [_var for _var in locals().keys() if _var[0] != '_' and _var not in ['time', 'ctypes']]
    430