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
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):
57 if attr[:2] == 'K_':
58 _keyNames[getattr(_tcod, attr)] = attr[2:]
59 return _keyNames
60
61 _keyNames = _parseKeyNames(_tcod)
62
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
79 """List an events public attributes in print calls.
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
89 """Fired when the window is closed by the user.
90 """
91 __slots__ = ()
92 type = 'QUIT'
93
95 __slots__ = ('key', 'char', 'shift', 'alt', 'control',
96 'leftAlt', 'leftCtrl', 'rightAlt', 'rightCtrl')
97
98 - def __init__(self, key, char, lalt, lctrl, ralt, rctrl, shift):
99 self.key = _keyNames[key]
100 """Human readable name of the key pressed.
101
102 Can be one of
103 'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL',
104 'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP',
105 'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN',
106 'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
107 'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9',
108 'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2',
109 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
110 'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR'
111 @type: string"""
112 char = char if isinstance(char, str) else char.decode()
113 self.char = char.replace('\x00', '')
114 """A single character string of the letter or symbol pressed.
115
116 Special characters like delete and return are not cross-platform.
117 L{key} should be used instead for special keys.
118 @type: string"""
119 self.leftAlt = bool(lalt)
120 """@type: boolean"""
121 self.rightAlt = bool(ralt)
122 """@type: boolean"""
123 self.leftCtrl = bool(lctrl)
124 """@type: boolean"""
125 self.rightCtrl = bool(rctrl)
126 """@type: boolean"""
127 self.shift = bool(shift)
128 """True if shift was held down during this event.
129 @type: boolean"""
130 self.alt = bool(lalt or ralt)
131 """True if alt was held down during this event.
132 @type: boolean"""
133 self.control = bool(lctrl or rctrl)
134 """True if control was held down during this event.
135 @type: boolean"""
136
138 """Fired when the user presses a key on the keyboard or a key repeats.
139 """
140 __slots__ = ()
141 type = 'KEYDOWN'
142
144 """Fired when the user releases a key on the keyboard.
145 """
146 __slots__ = ()
147 type = 'KEYUP'
148
149 _mouseNames = {1: 'LEFT', 2: 'MIDDLE', 3: 'RIGHT', 4: 'SCROLLUP', 5: 'SCROLLDOWN'}
164
166 """Fired when a mouse button is pressed."""
167 __slots__ = ()
168 type = 'MOUSEDOWN'
169
171 """Fired when a mouse button is released."""
172 __slots__ = ()
173 type = 'MOUSEUP'
174
176 """Fired when the mouse is moved."""
177 __slots__ = ('pos', 'motion', 'cell', 'cellmotion')
178 type = 'MOUSEMOTION'
179
180 - def __init__(self, pos, cell, motion, cellmotion):
181 self.pos = pos
182 """(x, y) position of the mouse on the screen.
183 type: (int, int)"""
184 self.cell = cell
185 """(x, y) position of the mouse snapped to a cell on the root console.
186 type: (int, int)"""
187 self.motion = motion
188 """(x, y) motion of the mouse on the screen.
189 type: (int, int)"""
190 self.cellmotion = cellmotion
191 """(x, y) mostion of the mouse moving over cells on the root console.
192 type: (int, int)"""
193
195 """
196 Application framework.
197
198 - ev_*: Events are passed to methods based on their L{Event.type} attribute.
199 If an event type is 'KEYDOWN' the ev_KEYDOWN method will be called
200 with the event instance as a parameter.
201
202 - key_*: When a key is pressed another method will be called based on the
203 L{KeyEvent.key} attribute. For example the 'ENTER' key will call key_ENTER
204 with the associated L{KeyDown} event as its parameter.
205
206 - L{update}: This method is called every loop. It is passed a single
207 parameter detailing the time in seconds since the last update
208 (often known as deltaTime.)
209
210 You may want to call drawing routines in this method followed by
211 L{tdl.flush}.
212 """
213 __slots__ = ('__running', '__prevTime')
214 __running = False
215 __prevTime = None
216
218 """Unless overridden this method raises a SystemExit exception closing
219 the program."""
220 raise SystemExit()
221
223 """Override this method to handle a L{KeyDown} event."""
224
226 """Override this method to handle a L{KeyUp} event."""
227
229 """Override this method to handle a L{MouseDown} event."""
230
232 """Override this method to handle a L{MouseUp} event."""
233
235 """Override this method to handle a L{MouseMotion} event."""
236
238 """Override this method to handle per frame logic and drawing.
239
240 @type deltaTime: float
241 @param deltaTime: This parameter tells the amount of time passed since
242 the last call measured in seconds as a floating point
243 number.
244
245 You can use this variable to make your program
246 frame rate independent.
247 Use this parameter to adjust the speed of motion,
248 timers, and other game logic.
249 """
250 pass
251
253 """When called the App will begin to return control to where
254 L{App.run} was called.
255
256 Some further events are processed and the L{App.update} method will be
257 called one last time before exiting
258 (unless suspended during a call to L{App.update}.)
259 """
260 self.__running = False
261
263 """Delegate control over to this App instance. This function will
264 process all events and send them to the special methods ev_* and key_*.
265
266 A call to L{App.suspend} will return the control flow back to where
267 this function is called. And then the App can be run again.
268 But a single App instance can not be run multiple times simultaneously.
269 """
270 if self.__running:
271 raise _tdl.TDLError('An App can not be run multiple times simultaneously')
272 self.__running = True
273 while self.__running:
274 self.runOnce()
275
277 """Pump events to this App instance and then return.
278
279 This works in the way described in L{App.run} except it immediately
280 returns after the first L{update} call.
281
282 Having multiple L{App} instances and selectively calling runOnce on
283 them is a decent way to create a state machine.
284 """
285 if self.__prevTime is None:
286 self.__prevTime = time.clock()
287 for event in get():
288 if event.type:
289
290 method = 'ev_%s' % event.type
291 getattr(self, method)(event)
292 if event.type == 'KEYDOWN':
293
294 method = 'key_%s' % event.key
295 if hasattr(self, method):
296 getattr(self, method)(event)
297 newTime = time.clock()
298 self.update(newTime - self.__prevTime)
299 self.__prevTime = newTime
300
301
303 """Flushes the event queue from libtcod into the global list _eventQueue"""
304 global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents
305 _eventsflushed = True
306 events = _pushedEvents
307 _pushedEvents = []
308
309 mouse = _Mouse()
310 libkey = _Key()
311 while 1:
312 libevent = _lib.TCOD_sys_check_for_event(_tcod.TCOD_EVENT_ANY, libkey, mouse)
313 if not libevent:
314 break
315
316
317 if libevent & _tcod.TCOD_EVENT_MOUSE_MOVE:
318 events.append(MouseMotion(*mouse.motion))
319
320 mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy))
321
322 for oldstate, newstate, released, button in zip((_mousel, _mousem, _mouser),
323 mouse.button, mouse.button_pressed, (1, 2, 3)):
324 if released:
325 if not oldstate:
326 events.append(MouseDown(button, *mousepos))
327 events.append(MouseUp(button, *mousepos))
328 if newstate:
329 events.append(MouseDown(button, *mousepos))
330 elif newstate and not oldstate:
331 events.append(MouseDown(button, *mousepos))
332
333 if mouse.wheel_up:
334 events.append(MouseDown(4, *mousepos))
335 if mouse.wheel_down:
336 events.append(MouseDown(5, *mousepos))
337
338 _mousel = mouse.lbutton
339 _mousem = mouse.mbutton
340 _mouser = mouse.rbutton
341
342 if libkey.vk == _tcod.K_NONE:
343 break
344 if libkey.pressed:
345 keyevent = KeyDown
346 else:
347 keyevent = KeyUp
348 events.append(keyevent(*tuple(libkey)))
349
350 if _lib.TCOD_console_is_window_closed():
351 events.append(Quit())
352
353 _eventQueue.extend(events)
354
356 """Flushes the event queue and returns the list of events.
357
358 This function returns L{Event} objects that can be indentified by their
359 type attribute or their class.
360
361 @rtype: iterator
362 @return: Returns an iterable of objects derived from L{Event} or anything
363 put in a L{push} call. If the iterator is deleted or otherwise
364 interrupted before finishing the excess items are preserved for the
365 next call.
366 """
367 _processEvents()
368 while _eventQueue:
369
370
371
372 yield(_eventQueue.pop(0))
373 raise StopIteration()
374
376 """Push an event into the event buffer.
377
378 @type event: L{Event}-like object
379 @param event: The event will be available on the next call to L{event.get}.
380 An event pushed in the middle of a L{get} will not show until
381 the next time L{get} called preventing push related
382 infinite loops.
383 """
384 _pushedEvents.append(event)
385
387 """Waits until the user presses a key. Then returns a L{KeyDown} event.
388
389 @rtype: L{KeyDown}
390 """
391 global _eventsflushed
392 _eventsflushed = True
393 flush = False
394 libkey = _Key()
395 _lib.TCOD_console_wait_for_keypress_wrapper(libkey, flush)
396 return KeyDown(*libkey)
397
399 """Returns True if the exit button on the window has been clicked and
400 stays True afterwards.
401
402 @rtype: boolean
403 """
404 return _lib.TCOD_console_is_window_closed()
405
406 __all__ = [_var for _var in locals().keys() if _var[0] != '_' and _var not in ['time', 'ctypes']]
407