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
     34  import time as _time
     35
     36  from tcod import ffi as _ffi
     37  from tcod import lib as _lib
     38
     39  import tdl as _tdl
     40  from . import style as _style
     41
     42  _eventQueue = []
     43  _pushedEvents = []
     44
     45  _mousel = 0
     46  _mousem = 0
     47  _mouser = 0
     48
     49  # this interprets the constants from libtcod and makes a key -> keyname dictionary
     50 -def _parseKeyNames(lib):
     51      """
     52      returns a dictionary mapping of human readable key names to their keycodes
     53      this parses constants with the names of K_* and makes code=name pairs
     54      this is for KeyEvent.key variable and that enables things like:
     55      if (event.key == 'PAGEUP'):
     56      """
     57      _keyNames = {}
     58      for attr in dir(lib): # from the modules variables
     59          if attr[:6] == 'TCODK_': # get the K_* constants
     60              _keyNames[getattr(lib, attr)] = attr[6:] # and make CODE=NAME pairs
     61      return _keyNames
     62
     63  _keyNames = _parseKeyNames(_lib)
     64
     65 -class Event(object):
     66      """Base Event class.
     67
     68      You can easily subclass this to make your own events.  Be sure to set
     69      the class attribute L{Event.type} for it to be passed to a custom L{App}
     70      ev_* method."""
     71      type = None
     72      """String constant representing the type of event.
     73
     74      The L{App} ev_* methods depend on this attribute.
     75
     76      Can be: 'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP', or 'MOUSEMOTION.'
     77      """
     78
     79 -    def __repr__(self):
     80          """List an events public attributes when printed.
     81          """
     82          attrdict = {}
     83          for varname in dir(self):
     84              if '_' == varname[0]:
     85                  continue
     86              attrdict[varname] = self.__getattribute__(varname)
     87          return '%s Event %s' % (self.__class__.__name__, repr(attrdict))
     88
     89 -class Quit(Event):
     90      """Fired when the window is closed by the user.
     91      """
     92      __slots__ = ()
     93      type = 'QUIT'
     94
     95 -class KeyEvent(Event):
     96
     97 -    def __init__(self, key, char, lalt, lctrl, ralt, rctrl, shift):
     98          # Convert keycodes into string, but use string if passed
     99          self.key = key if isinstance(key, str) else _keyNames[key]
    100          """Human readable names of the key pressed.
    101          Non special characters will show up as 'CHAR'.
    102
    103          Can be one of
    104          'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
    105          'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
    106          'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
    107          'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    108          'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
    109          'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
    110          'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
    111          'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
    112
    113          For the actual character instead of 'CHAR' use L{keychar}.
    114          @type: string"""
    115          char = char if isinstance(char, str) else char.decode()
    116          self.char = char.replace('\x00', '') # change null to empty string
    117          """A single character string of the letter or symbol pressed.
    118
    119          Special characters like delete and return are not cross-platform.
    120          L{key} or L{keychar} should be used instead for special keys.
    121          Characters are also case sensitive.
    122          @type: string"""
    123          # get the best out of self.key and self.char
    124          self.keychar = self.char if self.key == 'CHAR' else self.key
    125          """Similar to L{key} but returns a case sensitive letter or symbol
    126          instead of 'CHAR'.
    127
    128          This variable makes available the widest variety of symbols and should
    129          be used for key-mappings or anywhere where a narrower sample of keys
    130          isn't needed.
    131          """
    132          self.leftAlt = bool(lalt)
    133          """@type: boolean"""
    134          self.rightAlt = bool(ralt)
    135          """@type: boolean"""
    136          self.leftCtrl = bool(lctrl)
    137          """@type: boolean"""
    138          self.rightCtrl = bool(rctrl)
    139          """@type: boolean"""
    140          self.shift = bool(shift)
    141          """True if shift was held down during this event.
    142          @type: boolean"""
    143          self.alt = bool(lalt or ralt)
    144          """True if alt was held down during this event.
    145          @type: boolean"""
    146          self.control = bool(lctrl or rctrl)
    147          """True if control was held down during this event.
    148          @type: boolean"""
    149
    150 -class KeyDown(KeyEvent):
    151      """Fired when the user presses a key on the keyboard or a key repeats.
    152      """
    153      type = 'KEYDOWN'
    154
    155 -class KeyUp(KeyEvent):
    156      """Fired when the user releases a key on the keyboard.
    157      """
    158      type = 'KEYUP'
    159
    160  _mouseNames = {1: 'LEFT', 2: 'MIDDLE', 3: 'RIGHT', 4: 'SCROLLUP', 5: 'SCROLLDOWN'}
    161 -class MouseButtonEvent(Event):
    162
    163 -    def __init__(self, button, pos, cell):
    164          self.button = _mouseNames[button]
    165          """Can be one of
    166          'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN'
    167          @type: string"""
    168          self.pos = pos
    169          """(x, y) position of the mouse on the screen
    170          @type: (int, int)"""
    171          self.cell = cell
    172          """(x, y) position of the mouse snapped to a cell on the root console
    173          @type: (int, int)"""
    174
    175 -class MouseDown(MouseButtonEvent):
    176      """Fired when a mouse button is pressed."""
    177      __slots__ = ()
    178      type = 'MOUSEDOWN'
    179
    180 -class MouseUp(MouseButtonEvent):
    181      """Fired when a mouse button is released."""
    182      __slots__ = ()
    183      type = 'MOUSEUP'
    184
    185 -class MouseMotion(Event):
    186      """Fired when the mouse is moved."""
    187      type = 'MOUSEMOTION'
    188
    189 -    def __init__(self, pos, cell, motion, cellmotion):
    190          self.pos = pos
    191          """(x, y) position of the mouse on the screen.
    192          type: (int, int)"""
    193          self.cell = cell
    194          """(x, y) position of the mouse snapped to a cell on the root console.
    195          type: (int, int)"""
    196          self.motion = motion
    197          """(x, y) motion of the mouse on the screen.
    198          type: (int, int)"""
    199          self.cellmotion = cellmotion
    200          """(x, y) mostion of the mouse moving over cells on the root console.
    201          type: (int, int)"""
    202
    203 -class App(object):
    204      """
    205      Application framework.
    206
    207       - ev_*: Events are passed to methods based on their L{Event.type} attribute.
    208         If an event type is 'KEYDOWN' the ev_KEYDOWN method will be called
    209         with the event instance as a parameter.
    210
    211       - key_*: When a key is pressed another method will be called based on the
    212         L{KeyEvent.key} attribute.  For example the 'ENTER' key will call key_ENTER
    213         with the associated L{KeyDown} event as its parameter.
    214
    215       - L{update}: This method is called every loop.  It is passed a single
    216         parameter detailing the time in seconds since the last update
    217         (often known as deltaTime.)
    218
    219         You may want to call drawing routines in this method followed by
    220         L{tdl.flush}.
    221
    222      """
    223      __slots__ = ('__running', '__prevTime')
    224
    225 -    def ev_QUIT(self, event):
    226          """Unless overridden this method raises a SystemExit exception closing
    227          the program."""
    228          raise SystemExit()
    229
    230 -    def ev_KEYDOWN(self, event):
    231          """Override this method to handle a L{KeyDown} event."""
    232
    233 -    def ev_KEYUP(self, event):
    234          """Override this method to handle a L{KeyUp} event."""
    235
    236 -    def ev_MOUSEDOWN(self, event):
    237          """Override this method to handle a L{MouseDown} event."""
    238
    239 -    def ev_MOUSEUP(self, event):
    240          """Override this method to handle a L{MouseUp} event."""
    241
    242 -    def ev_MOUSEMOTION(self, event):
    243          """Override this method to handle a L{MouseMotion} event."""
    244
    245 -    def update(self, deltaTime):
    246          """Override this method to handle per frame logic and drawing.
    247
    248          @type deltaTime: float
    249          @param deltaTime: This parameter tells the amount of time passed since
    250                            the last call measured in seconds as a floating point
    251                            number.
    252
    253                            You can use this variable to make your program
    254                            frame rate independent.
    255                            Use this parameter to adjust the speed of motion,
    256                            timers, and other game logic.
    257          """
    258          pass
    259
    260 -    def suspend(self):
    261          """When called the App will begin to return control to where
    262          L{App.run} was called.
    263
    264          Some further events are processed and the L{App.update} method will be
    265          called one last time before exiting
    266          (unless suspended during a call to L{App.update}.)
    267          """
    268          self.__running = False
    269
    270 -    def run(self):
    271          """Delegate control over to this App instance.  This function will
    272          process all events and send them to the special methods ev_* and key_*.
    273
    274          A call to L{App.suspend} will return the control flow back to where
    275          this function is called.  And then the App can be run again.
    276          But a single App instance can not be run multiple times simultaneously.
    277          """
    278          if getattr(self, '_App__running', False):
    279              raise _tdl.TDLError('An App can not be run multiple times simultaneously')
    280          self.__running = True
    281          while self.__running:
    282              self.runOnce()
    283
    284 -    def run_once(self):
    285          """Pump events to this App instance and then return.
    286
    287          This works in the way described in L{App.run} except it immediately
    288          returns after the first L{update} call.
    289
    290          Having multiple L{App} instances and selectively calling runOnce on
    291          them is a decent way to create a state machine.
    292          """
    293          if not hasattr(self, '_App__prevTime'):
    294              self.__prevTime = _time.clock() # initiate __prevTime
    295          for event in get():
    296              if event.type: # exclude custom events with a blank type variable
    297                  # call the ev_* methods
    298                  method = 'ev_%s' % event.type # ev_TYPE
    299                  getattr(self, method)(event)
    300              if event.type == 'KEYDOWN':
    301                  # call the key_* methods
    302                  method = 'key_%s' % event.key # key_KEYNAME
    303                  if hasattr(self, method): # silently exclude undefined methods
    304                      getattr(self, method)(event)
    305          newTime = _time.clock()
    306          self.update(newTime - self.__prevTime)
    307          self.__prevTime = newTime
    308          #_tdl.flush()
    309
    310 -def _processEvents():
    311      """Flushes the event queue from libtcod into the global list _eventQueue"""
    312      global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
    313      _eventsflushed = True
    314      events = _pushedEvents # get events from event.push
    315      _pushedEvents = [] # then clear the pushed events queue
    316
    317      mouse = _ffi.new('TCOD_mouse_t *')
    318      libkey = _ffi.new('TCOD_key_t *')
    319      while 1:
    320          libevent = _lib.TCOD_sys_check_for_event(_lib.TCOD_EVENT_ANY, libkey, mouse)
    321          if not libevent: # no more events from libtcod
    322              break
    323
    324          #if mouse.dx or mouse.dy:
    325          if libevent & _lib.TCOD_EVENT_MOUSE_MOVE:
    326              events.append(MouseMotion((mouse.x, mouse.y),
    327                                        (mouse.cx, mouse.cy),
    328                                        (mouse.dx, mouse.dy),
    329                                        (mouse.dcx, mouse.dcy)))
    330
    331          mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
    332
    333          for oldstate, newstate, released, button in \
    334              zip((_mousel, _mousem, _mouser),
    335                  (mouse.lbutton, mouse.mbutton, mouse.rbutton),
    336                  (mouse.lbutton_pressed, mouse.mbutton_pressed,
    337                   mouse.rbutton_pressed),
    338                  (1, 2, 3)):
    339              if released:
    340                  if not oldstate:
    341                      events.append(MouseDown(button, *mousepos))
    342                  events.append(MouseUp(button, *mousepos))
    343                  if newstate:
    344                      events.append(MouseDown(button, *mousepos))
    345              elif newstate and not oldstate:
    346                  events.append(MouseDown(button, *mousepos))
    347
    348          if mouse.wheel_up:
    349              events.append(MouseDown(4, *mousepos))
    350          if mouse.wheel_down:
    351              events.append(MouseDown(5, *mousepos))
    352
    353          _mousel = mouse.lbutton
    354          _mousem = mouse.mbutton
    355          _mouser = mouse.rbutton
    356
    357          if libkey.vk == _lib.TCODK_NONE:
    358              break
    359          if libkey.pressed:
    360              keyevent = KeyDown
    361          else:
    362              keyevent = KeyUp
    363          events.append(keyevent(libkey.vk, libkey.c,
    364                                 libkey.lalt, libkey.lctrl,
    365                                 libkey.ralt, libkey.rctrl, libkey.shift))
    366
    367      if _lib.TCOD_console_is_window_closed():
    368          events.append(Quit())
    369
    370      _eventQueue.extend(events)
    371
    372 -def get():
    373      """Flushes the event queue and returns the list of events.
    374
    375      This function returns L{Event} objects that can be identified by their
    376      type attribute or their class.
    377
    378      @rtype: iterator
    379      @return: Returns an iterable of objects derived from L{Event} or anything
    380               put in a L{push} call.  If the iterator is deleted or otherwise
    381               interrupted before finishing the excess items are preserved for the
    382               next call.
    383      """
    384      _processEvents()
    385      return _event_generator()
    386
    387 -def _event_generator():
    388      while _eventQueue:
    389          # if there is an interruption the rest of the events stay untouched
    390          # this means you can break out of a event.get loop without losing
    391          # the leftover events
    392          yield(_eventQueue.pop(0))
    393      raise StopIteration()
    394
    395
    396 -def wait(timeout=None, flush=True):
    397      """Wait for an event.
    398
    399      @type timeout: int or None
    400      @param timeout: The time in seconds that this function will wait before
    401                      giving up and returning None.
    402
    403                      With the default value of None, this will block forever.
    404      @type flush: boolean
    405      @param flush: If True a call to L{tdl.flush} will be made before listening
    406                    for events.
    407      @rtype: L{Event} or None
    408      @return: Returns an instance derived from L{Event}, or None if the function
    409               has timed out.
    410               Anything added via L{push} will also be returned.
    411
    412      @since: 1.4.0
    413      """
    414      if timeout is not None:
    415          timeout = timeout + _time.clock() # timeout at this time
    416      while True:
    417          if _eventQueue:
    418              return _eventQueue.pop(0)
    419          if flush:
    420              # a full 'round' of events need to be processed before flushing
    421              _tdl.flush()
    422          if timeout and _time.clock() >= timeout:
    423              return None # return None on timeout
    424          _time.sleep(0.001) # sleep 1ms
    425          _processEvents()
    426
    427
    428 -def push(event):
    429      """Push an event into the event buffer.
    430
    431      @type event: L{Event}-like object
    432      @param event: The event will be available on the next call to L{event.get}.
    433                    An event pushed in the middle of a L{get} will not show until
    434                    the next time L{get} called preventing push related
    435                    infinite loops.
    436
    437                    This object should at least have a 'type' attribute.
    438      """
    439      _pushedEvents.append(event)
    440
    441 -def key_wait():
    442      """Waits until the user presses a key.
    443      Then returns a L{KeyDown} event.
    444
    445      Key events will repeat if held down.
    446
    447      A click to close the window will be converted into an Alt+F4 KeyDown event.
    448
    449      @rtype: L{KeyDown}
    450      """
    451      while 1:
    452          for event in get():
    453              if event.type == 'KEYDOWN':
    454                  return event
    455              if event.type == 'QUIT':
    456                  # convert QUIT into alt+F4
    457                  return KeyDown('F4', '', True, False, True, False, False)
    458          _time.sleep(.001)
    459
    460 -def set_key_repeat(delay=500, interval=0):
    461      """Change or disable key repeat.
    462
    463      @type delay: int
    464      @param delay: Milliseconds before a held key begins to repeat.
    465
    466                    Key repeat can be disabled entirely by setting a delay of zero.
    467
    468      @type interval: int
    469      @param interval: Milliseconds between key repeats.
    470
    471                       An interval of zero will repeat every frame.
    472      """
    473      _lib.TCOD_console_set_keyboard_repeat(delay, interval)
    474
    475 -def is_window_closed():
    476      """Returns True if the exit button on the window has been clicked and
    477      stays True afterwards.
    478
    479      @rtype: boolean
    480      """
    481      return _lib.TCOD_console_is_window_closed()
    482
    483  __all__ = [_var for _var in locals().keys() if _var[0] != '_']
    484
    485  App.runOnce = _style.backport(App.run_once)
    486  keyWait = _style.backport(key_wait)
    487  setKeyRepeat = _style.backport(set_key_repeat)
    488  isWindowClosed = _style.backport(is_window_closed)
    489