1
|
|
|
#!/usr/bin/env python |
2
|
|
|
""" |
3
|
|
|
An alternative, more direct implementation of event handling using cffi |
4
|
|
|
calls to SDL functions. The current code is incomplete, but can be |
5
|
|
|
extended easily by following the official SDL documentation. |
6
|
|
|
|
7
|
|
|
This module be run directly like any other event get example, but is meant |
8
|
|
|
to be copied into your code base. Then you can use the sdlevent.get and |
9
|
|
|
sdlevent.wait functions in your code. |
10
|
|
|
|
11
|
|
|
Printing any event will tell you its attributes in a human readable format. |
12
|
|
|
An events type attr is just the classes name with all letters upper-case. |
13
|
|
|
|
14
|
|
|
Like in tdl, events use a type attribute to tell events apart. Unlike tdl |
15
|
|
|
and tcod the names and values used are directly derived from SDL. |
16
|
|
|
|
17
|
|
|
As a general guideline for turn-based rouge-likes, you should use |
18
|
|
|
KeyDown.sym for commands, and TextInput.text for name entry. |
19
|
|
|
|
20
|
|
|
This module may be included as a tcod.event module once it is more mature. |
21
|
|
|
|
22
|
|
|
An absolute minimal example: |
23
|
|
|
|
24
|
|
|
import tcod |
25
|
|
|
import sdlevent |
26
|
|
|
|
27
|
|
|
with tcod.console_init_root(80, 60, 'title') as console: |
28
|
|
|
while True: |
29
|
|
|
for event in sdlevent.wait(): |
30
|
|
|
print(event) |
31
|
|
|
if event.type == 'QUIT': |
32
|
|
|
raise SystemExit() |
33
|
|
|
tcod.console_flush() |
34
|
|
|
""" |
35
|
|
|
# CC0 License: https://creativecommons.org/publicdomain/zero/1.0/ |
36
|
|
|
# To the extent possible under law, Kyle Stewart has waived all copyright and |
37
|
|
|
# related or neighboring rights to this sdlevent.py module. |
38
|
|
|
|
39
|
|
|
import tcod |
40
|
|
|
|
41
|
|
|
def _copy_attrs(prefix): |
42
|
|
|
"""Copy names and values from tcod.lib into this modules top-level scope. |
43
|
|
|
|
44
|
|
|
This is a private function, used internally. |
45
|
|
|
|
46
|
|
|
Args: |
47
|
|
|
prefix (str): Used to filter out which names to copy. |
48
|
|
|
|
49
|
|
|
Returns: |
50
|
|
|
Dict[Any,str]: A reverse lookup table used in Event repr functions. |
51
|
|
|
""" |
52
|
|
|
g = globals() # dynamically add names to the global state |
53
|
|
|
# removes the SDL prefix, this module already has SDL in the name |
54
|
|
|
if prefix.startswith('SDL_'): |
55
|
|
|
name_starts_at = 4 |
56
|
|
|
elif prefix.startswith('SDL'): |
57
|
|
|
name_starts_at = 3 |
58
|
|
|
else: |
59
|
|
|
name_starts_at = 0 |
60
|
|
|
revere_table = {} |
61
|
|
|
for attr in dir(tcod.lib): |
62
|
|
|
if attr.startswith(prefix): |
63
|
|
|
name = attr[name_starts_at:] |
64
|
|
|
value = getattr(tcod.lib, attr) |
65
|
|
|
revere_table[value] = 'sdlevent.' + name |
66
|
|
|
g[name] = value |
67
|
|
|
|
68
|
|
|
return revere_table |
69
|
|
|
|
70
|
|
|
def _describe_bitmask(bits, table, default='0'): |
71
|
|
|
"""Returns a bitmask in human readable form. |
72
|
|
|
|
73
|
|
|
This is a private function, used internally. |
74
|
|
|
|
75
|
|
|
Args: |
76
|
|
|
bits (int): The bitmask to be represented. |
77
|
|
|
table (Dict[Any,str]): A reverse lookup table. |
78
|
|
|
default (Any): A default return value when bits is 0. |
79
|
|
|
|
80
|
|
|
Returns: str: A printable version of the bits variable. |
81
|
|
|
""" |
82
|
|
|
result = [] |
83
|
|
|
for bit, name in table.items(): |
84
|
|
|
if bit & bits: |
85
|
|
|
result.append(name) |
86
|
|
|
if not result: |
87
|
|
|
return default |
88
|
|
|
return '|'.join(result) |
89
|
|
|
|
90
|
|
|
|
91
|
|
|
_REVSRSE_SCANCODE_TABLE = _copy_attrs('SDL_SCANCODE') |
92
|
|
|
_REVSRSE_SYM_TABLE = _copy_attrs('SDLK') |
93
|
|
|
_REVSRSE_MOD_TABLE = _copy_attrs('KMOD') |
94
|
|
|
_REVSRSE_WHEEL_TABLE = _copy_attrs('SDL_MOUSEWHEEL') |
95
|
|
|
|
96
|
|
|
# manually define names for SDL macros |
97
|
|
|
BUTTON_LEFT = 1 |
98
|
|
|
BUTTON_MIDDLE = 2 |
99
|
|
|
BUTTON_RIGHT = 3 |
100
|
|
|
BUTTON_X1 = 4 |
101
|
|
|
BUTTON_X2 = 5 |
102
|
|
|
BUTTON_LMASK = 0x01 |
103
|
|
|
BUTTON_MMASK = 0x02 |
104
|
|
|
BUTTON_RMASK = 0x04 |
105
|
|
|
BUTTON_X1MASK = 0x08 |
106
|
|
|
BUTTON_X2MASK = 0x10 |
107
|
|
|
|
108
|
|
|
_REVSRSE_BUTTON_TABLE = { |
109
|
|
|
BUTTON_LEFT: 'sdlevent.BUTTON_LEFT', |
110
|
|
|
BUTTON_MIDDLE: 'sdlevent.BUTTON_MIDDLE', |
111
|
|
|
BUTTON_RIGHT: 'sdlevent.BUTTON_RIGHT', |
112
|
|
|
BUTTON_X1: 'sdlevent.BUTTON_X1', |
113
|
|
|
BUTTON_X2: 'sdlevent.BUTTON_X2', |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
_REVSRSE_BUTTON_MASK_TABLE = { |
117
|
|
|
BUTTON_LMASK: 'sdlevent.BUTTON_LMASK', |
118
|
|
|
BUTTON_MMASK: 'sdlevent.BUTTON_MMASK', |
119
|
|
|
BUTTON_RMASK: 'sdlevent.BUTTON_RMASK', |
120
|
|
|
BUTTON_X1MASK: 'sdlevent.BUTTON_X1MASK', |
121
|
|
|
BUTTON_X2MASK: 'sdlevent.BUTTON_X2MASK', |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
class Event(object): |
125
|
|
|
"""The base event class.""" |
126
|
|
|
@classmethod |
127
|
|
|
def from_sdl_event(cls, sdl_event): |
128
|
|
|
"""Return a class instance from a cffi SDL_Event pointer.""" |
129
|
|
|
raise NotImplementedError() |
130
|
|
|
|
131
|
|
|
@property |
132
|
|
|
def type(self): |
133
|
|
|
"""All event types are just the class name, but all upper-case.""" |
134
|
|
|
return self.__class__.__name__.upper() |
135
|
|
|
|
136
|
|
|
|
137
|
|
|
class Quit(Event): |
138
|
|
|
"""An application quit request event. |
139
|
|
|
|
140
|
|
|
For more info on when this event is triggered see: |
141
|
|
|
https://wiki.libsdl.org/SDL_EventType#SDL_QUIT |
142
|
|
|
""" |
143
|
|
|
@classmethod |
144
|
|
|
def from_sdl_event(cls, sdl_event): |
145
|
|
|
return cls() |
146
|
|
|
|
147
|
|
|
def __repr__(self): |
148
|
|
|
return 'sdlevent.%s()' % self.__class__.__name__ |
149
|
|
|
|
150
|
|
|
|
151
|
|
|
class KeyboardEvent(Event): |
152
|
|
|
|
153
|
|
|
def __init__(self, scancode, sym, mod): |
154
|
|
|
self.scancode = scancode |
155
|
|
|
self.sym = sym |
156
|
|
|
self.mod = mod |
157
|
|
|
|
158
|
|
|
@classmethod |
159
|
|
|
def from_sdl_event(cls, sdl_event): |
160
|
|
|
keysym = sdl_event.key.keysym |
161
|
|
|
return cls(keysym.scancode, keysym.sym, keysym.mod) |
162
|
|
|
|
163
|
|
|
def __repr__(self): |
164
|
|
|
return ('sdlevent.%s(scancode=%s, sym=%s, mod=%s)' % |
165
|
|
|
(self.__class__.__name__, |
166
|
|
|
_REVSRSE_SCANCODE_TABLE[self.scancode], |
167
|
|
|
_REVSRSE_SYM_TABLE[self.sym], |
168
|
|
|
_describe_bitmask(self.mod, _REVSRSE_MOD_TABLE), |
169
|
|
|
) |
170
|
|
|
) |
171
|
|
|
|
172
|
|
|
|
173
|
|
|
class KeyDown(KeyboardEvent): |
174
|
|
|
pass |
175
|
|
|
|
176
|
|
|
|
177
|
|
|
class KeyUp(KeyboardEvent): |
178
|
|
|
pass |
179
|
|
|
|
180
|
|
|
|
181
|
|
|
class MouseMotion(Event): |
182
|
|
|
|
183
|
|
|
def __init__(self, x, y, xrel, yrel, state): |
184
|
|
|
self.x = x |
185
|
|
|
self.y = y |
186
|
|
|
self.xrel = xrel |
187
|
|
|
self.yrel = yrel |
188
|
|
|
self.state = state |
189
|
|
|
|
190
|
|
|
@classmethod |
191
|
|
|
def from_sdl_event(cls, sdl_event): |
192
|
|
|
motion = sdl_event.motion |
193
|
|
|
return cls(motion.x, motion.y, motion.xrel, motion.yrel, motion.state) |
194
|
|
|
|
195
|
|
|
def __repr__(self): |
196
|
|
|
return ('sdlevent.%s(x=%i, y=%i, xrel=%i, yrel=%i, state=%s)' % |
197
|
|
|
(self.__class__.__name__, |
198
|
|
|
self.x, self.y, |
199
|
|
|
self.xrel, self.yrel, |
200
|
|
|
_describe_bitmask(self.state, _REVSRSE_BUTTON_MASK_TABLE), |
201
|
|
|
) |
202
|
|
|
) |
203
|
|
|
|
204
|
|
|
|
205
|
|
|
class MouseButtonEvent(Event): |
206
|
|
|
|
207
|
|
|
def __init__(self, x, y, button): |
208
|
|
|
self.x = x |
209
|
|
|
self.y = y |
210
|
|
|
self.button = button |
211
|
|
|
|
212
|
|
|
@classmethod |
213
|
|
|
def from_sdl_event(cls, sdl_event): |
214
|
|
|
button = sdl_event.button |
215
|
|
|
return cls(button.x, button.y, button.button) |
216
|
|
|
|
217
|
|
|
def __repr__(self): |
218
|
|
|
return ('sdlevent.%s(x=%i, y=%i, button=%s)' % |
219
|
|
|
(self.__class__.__name__, |
220
|
|
|
self.x, self.y, _REVSRSE_BUTTON_TABLE[self.button], |
221
|
|
|
) |
222
|
|
|
) |
223
|
|
|
|
224
|
|
|
|
225
|
|
|
class MouseButtonDown(MouseButtonEvent): |
226
|
|
|
pass |
227
|
|
|
|
228
|
|
|
|
229
|
|
|
class MouseButtonUp(MouseButtonEvent): |
230
|
|
|
pass |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
class MouseWheel(Event): |
234
|
|
|
|
235
|
|
|
def __init__(self, x, y, direction): |
236
|
|
|
self.x = x |
237
|
|
|
self.y = y |
238
|
|
|
self.direction = direction |
239
|
|
|
|
240
|
|
|
@classmethod |
241
|
|
|
def from_sdl_event(cls, sdl_event): |
242
|
|
|
wheel = sdl_event.wheel |
243
|
|
|
return cls(wheel.x, wheel.y, wheel.direction) |
244
|
|
|
|
245
|
|
|
def __repr__(self): |
246
|
|
|
return ('sdlevent.%s(x=%i, y=%i, direction=%s)' % |
247
|
|
|
(self.__class__.__name__, |
248
|
|
|
self.x, self.y, |
249
|
|
|
_REVSRSE_WHEEL_TABLE[self.direction], |
250
|
|
|
) |
251
|
|
|
) |
252
|
|
|
|
253
|
|
|
|
254
|
|
|
class TextInput(Event): |
255
|
|
|
|
256
|
|
|
def __init__(self, text): |
257
|
|
|
self.text = text |
258
|
|
|
|
259
|
|
|
@classmethod |
260
|
|
|
def from_sdl_event(cls, sdl_event): |
261
|
|
|
return cls(tcod.ffi.string(sdl_event.text.text, 32).decode('utf8')) |
262
|
|
|
|
263
|
|
|
def __repr__(self): |
264
|
|
|
return ('sdlevent.%s(text=%r)' % (self.__class__.__name__, self.text)) |
265
|
|
|
|
266
|
|
|
|
267
|
|
|
_SDL_TO_CLASS_TABLE = { |
268
|
|
|
tcod.lib.SDL_QUIT: Quit, |
269
|
|
|
tcod.lib.SDL_KEYDOWN: KeyDown, |
270
|
|
|
tcod.lib.SDL_KEYUP: KeyUp, |
271
|
|
|
tcod.lib.SDL_MOUSEMOTION: MouseMotion, |
272
|
|
|
tcod.lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown, |
273
|
|
|
tcod.lib.SDL_MOUSEBUTTONUP: MouseButtonUp, |
274
|
|
|
tcod.lib.SDL_MOUSEWHEEL: MouseWheel, |
275
|
|
|
tcod.lib.SDL_TEXTINPUT: TextInput, |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
def get(): |
279
|
|
|
"""Iterate over all pending events. |
280
|
|
|
|
281
|
|
|
Returns: |
282
|
|
|
Iterator[sdlevent.Event]: |
283
|
|
|
An iterator of Event subclasses. |
284
|
|
|
""" |
285
|
|
|
sdl_event = tcod.ffi.new('SDL_Event*') |
286
|
|
|
while tcod.lib.SDL_PollEvent(sdl_event): |
287
|
|
|
if sdl_event.type in _SDL_TO_CLASS_TABLE: |
288
|
|
|
yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) |
289
|
|
|
|
290
|
|
|
def wait(timeout=None): |
291
|
|
|
"""Block until an event exists, then iterate over all events. |
292
|
|
|
|
293
|
|
|
Keep in mind that this function will wake even for events not handled by |
294
|
|
|
this module. |
295
|
|
|
|
296
|
|
|
Args: |
297
|
|
|
timeout (Optional[int]): |
298
|
|
|
Maximum number of milliseconds to wait, or None to wait forever. |
299
|
|
|
|
300
|
|
|
Returns: |
301
|
|
|
Iterator[sdlevent.Event]: Same iterator as a call to sdlevent.get |
302
|
|
|
""" |
303
|
|
|
if timeout is not None: |
304
|
|
|
tcod.lib.SDL_WaitEventTimeout(tcod.ffi.NULL, timeout) |
305
|
|
|
else: |
306
|
|
|
tcod.lib.SDL_WaitEvent(tcod.ffi.NULL) |
307
|
|
|
return get() |
308
|
|
|
|
309
|
|
|
def _main(): |
310
|
|
|
"""An example program for when this module is run directly.""" |
311
|
|
|
WIDTH, HEIGHT = 120, 60 |
312
|
|
|
TITLE = 'sdlevent.py engine' |
313
|
|
|
|
314
|
|
|
with tcod.console_init_root(WIDTH, HEIGHT, TITLE) as console: |
315
|
|
|
tcod.sys_set_fps(24) |
316
|
|
|
while True: |
317
|
|
|
for event in wait(): |
318
|
|
|
print(event) |
319
|
|
|
if event.type == 'QUIT': |
320
|
|
|
raise SystemExit() |
321
|
|
|
elif event.type == 'MOUSEMOTION': |
322
|
|
|
console.rect(0, HEIGHT - 1, WIDTH, 1, True) |
323
|
|
|
console.print_(0, HEIGHT - 1, repr(event)) |
324
|
|
|
else: |
325
|
|
|
console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) |
326
|
|
|
console.print_(0, HEIGHT - 3, repr(event)) |
327
|
|
|
tcod.console_flush() |
328
|
|
|
|
329
|
|
|
if __name__ == '__main__': |
330
|
|
|
_main() |
331
|
|
|
|