untdl.event

Package untdl :: Module event |
[frames] | no frames]

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