|
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
|
|
|
|