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 as _time
     34
     35  from .__tcod import _lib, _Mouse, _Key
     36  from . import __tcod as _tcod
     37  from . import __style as _style
     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
    229 -    def ev_QUIT(self, event):
    230          """Unless overridden this method raises a SystemExit exception closing
    231          the program."""
    232          raise SystemExit()
    233
    234 -    def ev_KEYDOWN(self, event):
    235          """Override this method to handle a L{KeyDown} event."""
    236
    237 -    def ev_KEYUP(self, event):
    238          """Override this method to handle a L{KeyUp} event."""
    239
    240 -    def ev_MOUSEDOWN(self, event):
    241          """Override this method to handle a L{MouseDown} event."""
    242
    243 -    def ev_MOUSEUP(self, event):
    244          """Override this method to handle a L{MouseUp} event."""
    245
    246 -    def ev_MOUSEMOTION(self, event):
    247          """Override this method to handle a L{MouseMotion} event."""
    248
    249 -    def update(self, deltaTime):
    250          """Override this method to handle per frame logic and drawing.
    251
    252          @type deltaTime: float
    253          @param deltaTime: This parameter tells the amount of time passed since
    254                            the last call measured in seconds as a floating point
    255                            number.
    256
    257                            You can use this variable to make your program
    258                            frame rate independent.
    259                            Use this parameter to adjust the speed of motion,
    260                            timers, and other game logic.
    261          """
    262          pass
    263
    264 -    def suspend(self):
    265          """When called the App will begin to return control to where
    266          L{App.run} was called.
    267
    268          Some further events are processed and the L{App.update} method will be
    269          called one last time before exiting
    270          (unless suspended during a call to L{App.update}.)
    271          """
    272          self.__running = False
    273
    274 -    def run(self):
    275          """Delegate control over to this App instance.  This function will
    276          process all events and send them to the special methods ev_* and key_*.
    277
    278          A call to L{App.suspend} will return the control flow back to where
    279          this function is called.  And then the App can be run again.
    280          But a single App instance can not be run multiple times simultaneously.
    281          """
    282          if getattr(self, '_App__running', False):
    283              raise _tdl.TDLError('An App can not be run multiple times simultaneously')
    284          self.__running = True
    285          while self.__running:
    286              self.runOnce()
    287
    288 -    def run_once(self):
    289          """Pump events to this App instance and then return.
    290
    291          This works in the way described in L{App.run} except it immediately
    292          returns after the first L{update} call.
    293
    294          Having multiple L{App} instances and selectively calling runOnce on
    295          them is a decent way to create a state machine.
    296          """
    297          if not hasattr(self, '_App__prevTime'):
    298              self.__prevTime = _time.clock() # initiate __prevTime
    299          for event in get():
    300              if event.type: # exclude custom events with a blank type variable
    301                  # call the ev_* methods
    302                  method = 'ev_%s' % event.type # ev_TYPE
    303                  getattr(self, method)(event)
    304              if event.type == 'KEYDOWN':
    305                  # call the key_* methods
    306                  method = 'key_%s' % event.key # key_KEYNAME
    307                  if hasattr(self, method): # silently exclude undefined methods
    308                      getattr(self, method)(event)
    309          newTime = _time.clock()
    310          self.update(newTime - self.__prevTime)
    311          self.__prevTime = newTime
    312          #_tdl.flush()
    313
    314 -def _processEvents():
    315      """Flushes the event queue from libtcod into the global list _eventQueue"""
    316      global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
    317      _eventsflushed = True
    318      events = _pushedEvents # get events from event.push
    319      _pushedEvents = [] # then clear the pushed events queue
    320
    321      mouse = _Mouse()
    322      libkey = _Key()
    323      while 1:
    324          libevent = _lib.TCOD_sys_check_for_event(_tcod.TCOD_EVENT_ANY, libkey, mouse)
    325          if not libevent: # no more events from libtcod
    326              break
    327
    328          #if mouse.dx or mouse.dy:
    329          if libevent & _tcod.TCOD_EVENT_MOUSE_MOVE:
    330              events.append(MouseMotion(*mouse.motion))
    331
    332          mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
    333
    334          for oldstate, newstate, released, button in zip((_mousel, _mousem, _mouser),
    335                                      mouse.button, mouse.button_pressed, (1, 2, 3)):
    336              if released:
    337                  if not oldstate:
    338                      events.append(MouseDown(button, *mousepos))
    339                  events.append(MouseUp(button, *mousepos))
    340                  if newstate:
    341                      events.append(MouseDown(button, *mousepos))
    342              elif newstate and not oldstate:
    343                  events.append(MouseDown(button, *mousepos))
    344
    345          if mouse.wheel_up:
    346              events.append(MouseDown(4, *mousepos))
    347          if mouse.wheel_down:
    348              events.append(MouseDown(5, *mousepos))
    349
    350          _mousel = mouse.lbutton
    351          _mousem = mouse.mbutton
    352          _mouser = mouse.rbutton
    353
    354          if libkey.vk == _tcod.K_NONE:
    355              break
    356          if libkey.pressed:
    357              keyevent = KeyDown
    358          else:
    359              keyevent = KeyUp
    360          events.append(keyevent(*tuple(libkey)))
    361
    362      if _lib.TCOD_console_is_window_closed():
    363          events.append(Quit())
    364
    365      _eventQueue.extend(events)
    366
    367 -def get():
    368      """Flushes the event queue and returns the list of events.
    369
    370      This function returns L{Event} objects that can be indentified by their
    371      type attribute or their class.
    372
    373      @rtype: iterator
    374      @return: Returns an iterable of objects derived from L{Event} or anything
    375               put in a L{push} call.  If the iterator is deleted or otherwise
    376               interrupted before finishing the excess items are preserved for the
    377               next call.
    378      """
    379      def eventGenerator():
    380          while _eventQueue:
    381              # if there is an interruption the rest of the events stay untouched
    382              # this means you can break out of a event.get loop without losing
    383              # the leftover events
    384              yield(_eventQueue.pop(0))
    385          raise StopIteration()
    386      _processEvents()
    387      return eventGenerator()
    388
    389 -def push(event):
    390      """Push an event into the event buffer.
    391
    392      @type event: L{Event}-like object
    393      @param event: The event will be available on the next call to L{event.get}.
    394                    An event pushed in the middle of a L{get} will not show until
    395                    the next time L{get} called preventing push related
    396                    infinite loops.
    397      """
    398      _pushedEvents.append(event)
    399
    400 -def key_wait():
    401      """Waits until the user presses a key.
    402      Then returns a L{KeyDown} event.
    403
    404      Key events will repeat if held down.
    405
    406      A click to close the window will be converted into an Alt+F4 KeyDown event.
    407
    408      @rtype: L{KeyDown}
    409      """
    410      while 1:
    411          for event in get():
    412              if event.type == 'KEYDOWN':
    413                  return event
    414              if event.type == 'QUIT':
    415                  # convert QUIT into alt+F4
    416                  return KeyDown('F4', '', True, False, True, False, False)
    417          _time.sleep(.001)
    418
    419 -def set_key_repeat(delay=500, interval=0):
    420      """Change or disable key repeat.
    421
    422      @type delay: int
    423      @param delay: Milliseconds before a held key begins to repeat.
    424
    425                    Key repeat can be disabled entirely by setting a delay of zero.
    426
    427      @type interval: int
    428      @param interval: Milliseconds between key repeats.
    429
    430                       An interval of zero will repeat every frame.
    431      """
    432      _lib.TCOD_console_set_keyboard_repeat(delay, interval)
    433
    434 -def is_window_closed():
    435      """Returns True if the exit button on the window has been clicked and
    436      stays True afterwards.
    437
    438      @rtype: boolean
    439      """
    440      return _lib.TCOD_console_is_window_closed()
    441
    442  __all__ = [_var for _var in locals().keys() if _var[0] != '_']
    443
    444  App.runOnce = _style.backport(App.run_once)
    445  keyWait = _style.backport(key_wait)
    446  setKeyRepeat = _style.backport(set_key_repeat)
    447  isWindowClosed = _style.backport(is_window_closed)
    448