MouseButtonUp   A
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 2
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 2
rs 10
wmc 0
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