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