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