_BaseConsole.draw_rect()   B
last analyzed

Complexity

Conditions 3

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
dl 0
loc 42
rs 8.872
c 1
b 0
f 0
1
"""
2
    Getting Started
3
    ===============
4
      Once the library is imported you can load the font you want to use with
5
      :any:`tdl.set_font`.
6
      This is optional and when skipped will use a decent default font.
7
8
      After that you call :any:`tdl.init` to set the size of the window and
9
      get the root console in return.
10
      This console is the canvas to what will appear on the screen.
11
12
    Indexing Consoles
13
    =================
14
      For most methods taking a position you can use Python-style negative
15
      indexes to refer to the opposite side of a console with (-1, -1)
16
      starting at the bottom right.
17
      You can also check if a point is part of a console using containment
18
      logic i.e. ((x, y) in console).
19
20
      You may also iterate over a console using a for statement.  This returns
21
      every x,y coordinate available to draw on but it will be extremely slow
22
      to actually operate on every coordinate individualy.
23
      Try to minimize draws by using an offscreen :any:`Console`, only drawing
24
      what needs to be updated, and using :any:`Console.blit`.
25
26
    Drawing and Colors
27
    ==================
28
29
      Once you have the root console from :any:`tdl.init` you can start drawing
30
      on it using a method such as :any:`Console.draw_char`.
31
      When using this method you can have the char parameter be an integer or a
32
      single character string.
33
34
      The fg and bg parameters expect a variety of types.
35
      The parameters default to Ellipsis which will tell the function to
36
      use the colors previously set by the :any:`Console.set_colors` method.
37
      The colors set by :any`Console.set_colors` are per each
38
      :any:`Console`/:any:`Window` and default to white on black.
39
      You can use a 3-item list/tuple of [red, green, blue] with integers in
40
      the 0-255 range with [0, 0, 0] being black and [255, 255, 255] being
41
      white.
42
      You can even use a single integer of 0xRRGGBB if you like.
43
44
      Using None in the place of any of the three parameters (char, fg, bg)
45
      will tell the function to not overwrite that color or character.
46
47
      After the drawing functions are called a call to :any:`tdl.flush` will
48
      update the screen.
49
"""
50
51
from __future__ import (absolute_import, division,
52
                        print_function, unicode_literals)
53
54
import sys as _sys
55
import os as _os
56
57
import array as _array
58
import weakref as _weakref
59
import itertools as _itertools
60
import textwrap as _textwrap
61
import struct as _struct
62
import re as _re
63
import warnings as _warnings
64
65
from tcod import ffi as _ffi
66
from tcod import lib as _lib
67
68
import tdl.event
69
import tdl.noise
70
import tdl.map
71
import tdl.style as _style
72
73
try:
74
    from tdl.version import __version__
75
except ImportError: # Gets imported without version.py by ReadTheDocs
76
    __version__ = ''
77
78
_IS_PYTHON3 = (_sys.version_info[0] == 3)
79
80
if _IS_PYTHON3: # some type lists to use with isinstance
81
    _INTTYPES = (int,)
82
    _NUMTYPES = (int, float)
83
    _STRTYPES = (str, bytes)
84
else:
85
    _INTTYPES = (int, long)
86
    _NUMTYPES = (int, long, float)
87
    _STRTYPES = (str, unicode)
88
89
def _encodeString(string): # still used for filepaths, and that's about it
90
    "changes string into bytes if running in python 3, for sending to ctypes"
91
    if isinstance(string, _STRTYPES):
92
        return string.encode()
93
    return string
94
95
def _format_char(char):
96
    """Prepares a single character for passing to ctypes calls, needs to return
97
    an integer but can also pass None which will keep the current character
98
    instead of overwriting it.
99
100
    This is called often and needs to be optimized whenever possible.
101
    """
102
    if char is None:
103
        return -1
104
    if isinstance(char, _STRTYPES) and len(char) == 1:
105
        return ord(char)
106
    try:
107
        return int(char) # allow all int-like objects
108
    except:
109
        raise TypeError('char single character string, integer, or None\nReceived: ' + repr(char))
110
111
_utf32_codec = {'little': 'utf-32le', 'big': 'utf-32le'}[_sys.byteorder]
112
113
def _format_str(string):
114
    """Attempt fast string handing by decoding directly into an array."""
115
    if isinstance(string, _STRTYPES):
116
        if _IS_PYTHON3:
117
            array = _array.array('I')
118
            array.frombytes(string.encode(_utf32_codec))
119
        else: # Python 2
120
            if isinstance(string, unicode):
121
                array = _array.array(b'I')
122
                array.fromstring(string.encode(_utf32_codec))
123
            else:
124
                array = _array.array(b'B')
125
                array.fromstring(string)
126
        return array
127
    return string
128
129
_fontinitialized = False
130
_rootinitialized = False
131
_rootConsoleRef = None
132
133
_put_char_ex = _lib.TDL_console_put_char_ex
134
135
# python 2 to 3 workaround
136
if _sys.version_info[0] == 2:
137
    int_types = (int, long)
138
else:
139
    int_types = int
140
141
142
def _format_color(color, default=Ellipsis):
143
        if color is Ellipsis:
144
            return default
145
        if color is None:
146
            return -1
147
        if isinstance(color, (tuple, list)) and len(color) == 3:
148
            return (color[0] << 16) + (color[1] << 8) + color[2]
149
        try:
150
            return int(color) # allow all int-like objects
151
        except:
152
            raise TypeError('fg and bg must be a 3 item tuple, integer, Ellipsis, or None\nReceived: ' + repr(color))
153
154
def _to_tcod_color(color):
155
    return _ffi.new('TCOD_color_t *', (color >> 16 & 0xff,
156
                                       color >> 8 & 0xff,
157
                                       color & 0xff))
158
159
def _getImageSize(filename):
160
    """Try to get the width and height of a bmp of png image file"""
161
    result = None
162
    file = open(filename, 'rb')
163
    if file.read(8) == b'\x89PNG\r\n\x1a\n': # PNG
164
        while 1:
165
            length, = _struct.unpack('>i', file.read(4))
166
            chunkID = file.read(4)
167
            if chunkID == '': # EOF
168
                break
169
            if chunkID == b'IHDR':
170
                # return width, height
171
                result = _struct.unpack('>ii', file.read(8))
172
                break
173
            file.seek(4 + length, 1)
174
        file.close()
175
        return result
176
    file.seek(0)
177
    if file.read(8) == b'BM': # Bitmap
178
        file.seek(18, 0) # skip to size data
179
        result = _struct.unpack('<ii', file.read(8))
180
    file.close()
181
    return result # (width, height), or None
182
183
class TDLError(Exception):
184
    """
185
    The catch all for most TDL specific errors.
186
    """
187
188
class _BaseConsole(object):
189
    """You will not use this class directly.  The methods in this class are
190
    inherited by the :any:`tdl.Console` and :any:`tdl.Window` subclasses.
191
    """
192
    __slots__ = ('width', 'height', 'console', '_cursor', '_fg',
193
                 '_bg', '_blend', '__weakref__', '__dict__')
194
195
    def __init__(self):
196
        self._cursor = (0, 0)
197
        self._scrollMode = 'error'
198
        self._fg = _format_color((255, 255, 255))
199
        self._bg = _format_color((0, 0, 0))
200
        self._blend = _lib.TCOD_BKGND_SET
201
202
    def _normalizePoint(self, x, y):
203
        """Check if a point is in bounds and make minor adjustments.
204
205
        Respects Pythons negative indexes.  -1 starts at the bottom right.
206
        Replaces the _drawable function
207
        """
208
        # cast to int, always faster than type checking
209
        x = int(x)
210
        y = int(y)
211
212
        assert (-self.width <= x < self.width) and \
213
               (-self.height <= y < self.height), \
214
               ('(%i, %i) is an invalid postition on %s' % (x, y, self))
215
216
        # handle negative indexes
217
        return (x % self.width, y % self.height)
218
219
    def _normalizeRect(self, x, y, width, height):
220
        """Check if the rectangle is in bounds and make minor adjustments.
221
        raise AssertionError's for any problems
222
        """
223
        x, y = self._normalizePoint(x, y) # inherit _normalizePoint logic
224
225
        assert width is None or isinstance(width, _INTTYPES), 'width must be an integer or None, got %s' % repr(width)
226
        assert height is None or isinstance(height, _INTTYPES), 'height must be an integer or None, got %s' % repr(height)
227
228
        # if width or height are None then extend them to the edge
229
        if width is None:
230
            width = self.width - x
231
        elif width < 0: # handle negative numbers
232
            width += self.width
233
            width = max(0, width) # a 'too big' negative is clamped zero
234
        if height is None:
235
            height = self.height - y
236
            height = max(0, height)
237
        elif height < 0:
238
            height += self.height
239
240
        # reduce rect size to bounds
241
        width = min(width, self.width - x)
242
        height = min(height, self.height - y)
243
244
        return x, y, width, height
245
246
    def _normalizeCursor(self, x, y):
247
        """return the normalized the cursor position."""
248
        width, height = self.get_size()
249
        assert width != 0 and height != 0, 'can not print on a console with a width or height of zero'
250
        while x >= width:
251
            x -= width
252
            y += 1
253
        while y >= height:
254
            if self._scrollMode == 'scroll':
255
                y -= 1
256
                self.scroll(0, -1)
257
            elif self._scrollMode == 'error':
258
                # reset the cursor on error
259
                self._cursor = (0, 0)
260
                raise TDLError('Cursor has reached the end of the console')
261
        return (x, y)
262
263
    def set_mode(self, mode):
264
        """Configure how this console will react to the cursor writing past the
265
        end if the console.
266
267
        This is for methods that use the virtual cursor, such as
268
        :any:`print_str`.
269
270
        Args:
271
            mode (Text): The mode to set.
272
273
            Possible settings are:
274
275
           - 'error' - A TDLError will be raised once the cursor
276
             reaches the end of the console.  Everything up until
277
             the error will still be drawn.
278
             This is the default setting.
279
           - 'scroll' - The console will scroll up as stuff is
280
             written to the end.
281
             You can restrict the region with :any:`tdl.Window` when
282
             doing this.
283
284
        ..seealso:: :any:`write`, :any:`print_str`
285
        """
286
        MODES = ['error', 'scroll']
287
        if mode.lower() not in MODES:
288
            raise TDLError('mode must be one of %s, got %s' % (MODES, repr(mode)))
289
        self._scrollMode = mode.lower()
290
291
    def set_colors(self, fg=None, bg=None):
292
        """Sets the colors to be used with the L{print_str} and draw_* methods.
293
294
        Values of None will only leave the current values unchanged.
295
296
        Args:
297
            fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
298
            bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
299
        .. seealso:: :any:`move`, :any:`print_str`
300
        """
301
        if fg is not None:
302
            self._fg = _format_color(fg, self._fg)
303
        if bg is not None:
304
            self._bg = _format_color(bg, self._bg)
305
306
    def print_str(self, string):
307
        """Print a string at the virtual cursor.
308
309
        Handles special characters such as '\\n' and '\\r'.
310
        Printing past the bottom of the console will scroll everything upwards
311
        if :any:`set_mode` is set to 'scroll'.
312
313
        Colors can be set with :any:`set_colors` and the virtual cursor can
314
        be moved with :any:`move`.
315
316
        Args:
317
            string (Text): The text to print.
318
319
        .. seealso:: :any:`draw_str`, :any:`move`, :any:`set_colors`,
320
                     :any:`set_mode`, :any:`write`, :any:`Window`
321
        """
322
        x, y = self._cursor
323
        for char in string:
324
            if char == '\n': # line break
325
                x = 0
326
                y += 1
327
                continue
328
            if char == '\r': # return
329
                x = 0
330
                continue
331
            x, y = self._normalizeCursor(x, y)
332
            self.draw_char(x, y, char, self._fg, self._bg)
333
            x += 1
334
        self._cursor = (x, y)
335
336
    def write(self, string):
337
        """This method mimics basic file-like behaviour.
338
339
        Because of this method you can replace sys.stdout or sys.stderr with
340
        a :any:`Console` or :any:`Window` instance.
341
342
        This is a convoluted process and behaviour seen now can be excepted to
343
        change on later versions.
344
345
        Args:
346
            string (Text): The text to write out.
347
348
        .. seealso:: :any:`set_colors`, :any:`set_mode`, :any:`Window`
349
        """
350
        # some 'basic' line buffer stuff.
351
        # there must be an easier way to do this.  The textwrap module didn't
352
        # help much.
353
        x, y = self._normalizeCursor(*self._cursor)
354
        width, height = self.get_size()
355
        wrapper = _textwrap.TextWrapper(initial_indent=(' '*x), width=width)
356
        writeLines = []
357
        for line in string.split('\n'):
358
            if line:
359
                writeLines += wrapper.wrap(line)
360
                wrapper.initial_indent = ''
361
            else:
362
                writeLines.append([])
363
364
        for line in writeLines:
365
            x, y = self._normalizeCursor(x, y)
366
            self.draw_str(x, y, line[x:], self._fg, self._bg)
367
            y += 1
368
            x = 0
369
        y -= 1
370
        self._cursor = (x, y)
371
372
    def draw_char(self, x, y, char, fg=Ellipsis, bg=Ellipsis):
373
        """Draws a single character.
374
375
        Args:
376
            x (int): x-coordinate to draw on.
377
            y (int): y-coordinate to draw on.
378
            char (Optional[Union[int, Text]]): An integer, single character
379
                string, or None.
380
381
                You can set the char parameter as None if you only want to change
382
                the colors of the tile.
383
            fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
384
            bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
385
386
        Raises: AssertionError: Having x or y values that can't be placed
387
            inside of the console will raise an AssertionError.
388
            You can use always use ((x, y) in console) to
389
            check if a tile is drawable.
390
391
        .. seealso:: :any:`get_char`
392
        """
393
        #x, y = self._normalizePoint(x, y)
394
        _put_char_ex(self.console_c, x, y, _format_char(char),
395
                     _format_color(fg, self._fg), _format_color(bg, self._bg), 1)
396
397
    def draw_str(self, x, y, string, fg=Ellipsis, bg=Ellipsis):
398
        """Draws a string starting at x and y.
399
400
        A string that goes past the right side will wrap around.  A string
401
        wrapping to below the console will raise :any:`tdl.TDLError` but will
402
        still be written out.
403
        This means you can safely ignore the errors with a
404
        try..except block if you're fine with partially written strings.
405
406
        \\r and \\n are drawn on the console as normal character tiles.  No
407
        special encoding is done and any string will translate to the character
408
        table as is.
409
410
        For a string drawing operation that respects special characters see
411
        :any:`print_str`.
412
413
        Args:
414
            x (int): x-coordinate to start at.
415
            y (int): y-coordinate to start at.
416
            string (Union[Text, Iterable[int]]): A string or an iterable of
417
                numbers.
418
419
                Special characters are ignored and rendered as any other
420
                character.
421
            fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
422
            bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
423
424
        Raises:
425
            AssertionError: Having x or y values that can't be placed inside
426
                            of the console will raise an AssertionError.
427
428
                You can use always use ``((x, y) in console)`` to check if
429
                a tile is drawable.
430
431
        .. seealso:: :any:`print_str`
432
        """
433
434
        x, y = self._normalizePoint(x, y)
435
        fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg)
436
        width, height = self.get_size()
437
        def _drawStrGen(x=x, y=y, string=string, width=width, height=height):
438
            """Generator for draw_str
439
440
            Iterates over ((x, y), ch) data for _set_batch, raising an
441
            error if the end of the console is reached.
442
            """
443
            for char in _format_str(string):
444
                if y == height:
445
                    raise TDLError('End of console reached.')
446
                yield((x, y), char)
447
                x += 1 # advance cursor
448
                if x == width: # line break
449
                    x = 0
450
                    y += 1
451
        self._set_batch(_drawStrGen(), fg, bg)
452
453
    def draw_rect(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
454
        """Draws a rectangle starting from x and y and extending to width and height.
455
456
        If width or height are None then it will extend to the edge of the console.
457
458
        Args:
459
            x (int): x-coordinate for the top side of the rect.
460
            y (int): y-coordinate for the left side of the rect.
461
            width (Optional[int]): The width of the rectangle.
462
463
                Can be None to extend to the bottom right of the
464
                console or can be a negative number to be sized reltive
465
                to the total size of the console.
466
            height (Optional[int]): The height of the rectangle.
467
            string (Optional[Union[Text, int]]): An integer, single character
468
                string, or None.
469
470
                You can set the string parameter as None if you only want
471
                to change the colors of an area.
472
            fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
473
            bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
474
475
        Raises:
476
            AssertionError: Having x or y values that can't be placed inside
477
                            of the console will raise an AssertionError.
478
479
            You can use always use ``((x, y) in console)`` to check if a tile
480
            is drawable.
481
        .. seealso:: :any:`clear`, :any:`draw_frame`
482
        """
483
        x, y, width, height = self._normalizeRect(x, y, width, height)
484
        fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg)
485
        char = _format_char(string)
486
        # use itertools to make an x,y grid
487
        # using ctypes here reduces type converstions later
488
        #grid = _itertools.product((_ctypes.c_int(x) for x in range(x, x + width)),
489
        #                          (_ctypes.c_int(y) for y in range(y, y + height)))
490
        grid = _itertools.product((x for x in range(x, x + width)),
491
                                  (y for y in range(y, y + height)))
492
        # zip the single character in a batch variable
493
        batch = zip(grid, _itertools.repeat(char, width * height))
494
        self._set_batch(batch, fg, bg, nullChar=(char is None))
495
496
    def draw_frame(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
497
        """Similar to L{draw_rect} but only draws the outline of the rectangle.
498
499
        `width or `height` can be None to extend to the bottom right of the
500
        console or can be a negative number to be sized reltive
501
        to the total size of the console.
502
503
        Args:
504
            x (int): The x-coordinate to start on.
505
            y (int): The y-coordinate to start on.
506
            width (Optional[int]): Width of the rectangle.
507
            height (Optional[int]): Height of the rectangle.
508
            string (Optional[Union[Text, int]]): An integer, single character
509
                string, or None.
510
511
                You can set this parameter as None if you only want
512
                to change the colors of an area.
513
            fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
514
            bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]])
515
516
        Raises:
517
            AssertionError: Having x or y values that can't be placed inside
518
                             of the console will raise an AssertionError.
519
520
             You can use always use ``((x, y) in console)`` to check if a tile
521
             is drawable.
522
        .. seealso:: :any:`draw_rect`, :any:`Window`
523
        """
524
        x, y, width, height = self._normalizeRect(x, y, width, height)
525
        fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg)
526
        char = _format_char(string)
527
        if width == 1 or height == 1: # it's just a single width line here
528
            return self.draw_rect(x, y, width, height, char, fg, bg)
529
530
        # draw sides of frame with draw_rect
531
        self.draw_rect(x, y, 1, height, char, fg, bg)
532
        self.draw_rect(x, y, width, 1, char, fg, bg)
533
        self.draw_rect(x + width - 1, y, 1, height, char, fg, bg)
534
        self.draw_rect(x, y + height - 1, width, 1, char, fg, bg)
535
536
    def blit(self, source, x=0, y=0, width=None, height=None, srcX=0, srcY=0,
537
             fg_alpha=1.0, bg_alpha=1.0):
538
        """Blit another console or Window onto the current console.
539
540
        By default it blits the entire source to the topleft corner.
541
542
        Args:
543
            source (Union[tdl.Console, tdl.Window]): The blitting source.
544
                A console can blit to itself without any problems.
545
            x (int): x-coordinate of this console to blit on.
546
            y (int): y-coordinate of this console to blit on.
547
            width (Optional[int]): Width of the rectangle.
548
549
                Can be None to extend as far as possible to the
550
                bottom right corner of the blit area or can be a negative
551
                number to be sized reltive to the total size of the
552
                B{destination} console.
553
            height (Optional[int]): Height of the rectangle.
554
            srcX (int):  x-coordinate of the source region to blit.
555
            srcY (int):  y-coordinate of the source region to blit.
556
            fg_alpha (float): The foreground alpha.
557
        """
558
        assert isinstance(source, (Console, Window)), "source muse be a Window or Console instance"
559
560
        # handle negative indexes and rects
561
        # negative width and height will be set realtive to the destination
562
        # and will also be clamped to the smallest Console
563
        x, y, width, height = self._normalizeRect(x, y, width, height)
564
        srcX, srcY, width, height = source._normalizeRect(srcX, srcY, width, height)
565
566
        # translate source and self if any of them are Window instances
567
        srcX, srcY = source._translate(srcX, srcY)
568
        source = source.console
569
        x, y = self._translate(x, y)
570
        self = self.console
571
572
        if self == source:
573
            # if we are the same console then we need a third console to hold
574
            # onto the data, otherwise it tries to copy into itself and
575
            # starts destroying everything
576
            tmp = Console(width, height)
577
            _lib.TCOD_console_blit(source.console_c,
578
                                   srcX, srcY, width, height,
579
                                   tmp.console_c, 0, 0, fg_alpha, bg_alpha)
580
            _lib.TCOD_console_blit(tmp.console_c, 0, 0, width, height,
581
                                   self.console_c, x, y, fg_alpha, bg_alpha)
582
        else:
583
            _lib.TCOD_console_blit(source.console_c,
584
                                   srcX, srcY, width, height,
585
                                   self.console_c, x, y, fg_alpha, bg_alpha)
586
587
    def get_cursor(self):
588
        """Return the virtual cursor position.
589
590
        The cursor can be moved with the :any:`move` method.
591
592
        Returns:
593
            Tuple[int, int]: The (x, y) coordinate of where :any:`print_str`
594
                will continue from.
595
596
        .. seealso:: :any:move`
597
        """
598
        x, y = self._cursor
599
        width, height = self.parent.get_size()
600
        while x >= width:
601
            x -= width
602
            y += 1
603
        if y >= height and self.scrollMode == 'scroll':
604
            y = height - 1
605
        return x, y
606
607
    def get_size(self):
608
        """Return the size of the console as (width, height)
609
610
        Returns:
611
            Tuple[int, int]: A (width, height) tuple.
612
        """
613
        return self.width, self.height
614
615
    def __iter__(self):
616
        """Return an iterator with every possible (x, y) value for this console.
617
618
        It goes without saying that working on the console this way is a
619
        slow process, especially for Python, and should be minimized.
620
621
        Returns:
622
            Iterator[Tuple[int, int]]: An ((x, y), ...) iterator.
623
        """
624
        return _itertools.product(range(self.width), range(self.height))
625
626
    def move(self, x, y):
627
        """Move the virtual cursor.
628
629
        Args:
630
            x (int): x-coordinate to place the cursor.
631
            y (int): y-coordinate to place the cursor.
632
633
        .. seealso:: :any:`get_cursor`, :any:`print_str`, :any:`write`
634
        """
635
        self._cursor = self._normalizePoint(x, y)
636
637
    def scroll(self, x, y):
638
        """Scroll the contents of the console in the direction of x,y.
639
640
        Uncovered areas will be cleared to the default background color.
641
        Does not move the virutal cursor.
642
643
        Args:
644
            x (int): Distance to scroll along the x-axis.
645
            y (int): Distance to scroll along the y-axis.
646
647
        Returns:
648
            Iterator[Tuple[int, int]]: An iterator over the (x, y) coordinates
649
            of any tile uncovered after scrolling.
650
651
        .. seealso:: :any:`set_colors`
652
        """
653
        assert isinstance(x, _INTTYPES), "x must be an integer, got %s" % repr(x)
654
        assert isinstance(y, _INTTYPES), "y must be an integer, got %s" % repr(x)
655
        def getSlide(x, length):
656
            """get the parameters needed to scroll the console in the given
657
            direction with x
658
            returns (x, length, srcx)
659
            """
660
            if x > 0:
661
                srcx = 0
662
                length -= x
663
            elif x < 0:
664
                srcx = abs(x)
665
                x = 0
666
                length -= srcx
667
            else:
668
                srcx = 0
669
            return x, length, srcx
670
        def getCover(x, length):
671
            """return the (x, width) ranges of what is covered and uncovered"""
672
            cover = (0, length) # everything covered
673
            uncover = None  # nothing uncovered
674
            if x > 0: # left side uncovered
675
                cover = (x, length - x)
676
                uncover = (0, x)
677
            elif x < 0: # right side uncovered
678
                x = abs(x)
679
                cover = (0, length - x)
680
                uncover = (length - x, x)
681
            return cover, uncover
682
683
        width, height = self.get_size()
684
        if abs(x) >= width or abs(y) >= height:
685
            return self.clear() # just clear the console normally
686
687
        # get the ranges of the areas that will be uncovered
688
        coverX, uncoverX = getCover(x, width)
689
        coverY, uncoverY = getCover(y, height)
690
        # so at this point we know that coverX and coverY makes a rect that
691
        # encases the area that we end up blitting to.  uncoverX/Y makes a
692
        # rect in the corner of the uncovered area.  So we need to combine
693
        # the uncoverX/Y with coverY/X to make what's left of the uncovered
694
        # area.  Explaining it makes it mush easier to do now.
695
696
        # But first we need to blit.
697
        x, width, srcx = getSlide(x, width)
698
        y, height, srcy = getSlide(y, height)
699
        self.blit(self, x, y, width, height, srcx, srcy)
700
        if uncoverX: # clear sides (0x20 is space)
701
            self.draw_rect(uncoverX[0], coverY[0], uncoverX[1], coverY[1],
702
                           0x20, self._fg, self._bg)
703
        if uncoverY: # clear top/bottom
704
            self.draw_rect(coverX[0], uncoverY[0], coverX[1], uncoverY[1],
705
                           0x20, self._fg, self._bg)
706
        if uncoverX and uncoverY: # clear corner
707
            self.draw_rect(uncoverX[0], uncoverY[0], uncoverX[1], uncoverY[1],
708
                           0x20, self._fg, self._bg)
709
710
    def clear(self, fg=Ellipsis, bg=Ellipsis):
711
        """Clears the entire L{Console}/L{Window}.
712
713
        Unlike other drawing functions, fg and bg can not be None.
714
715
        Args:
716
            fg (Union[Tuple[int, int, int], int, Ellipsis])
717
            bg (Union[Tuple[int, int, int], int, Ellipsis])
718
719
        .. seealso:: :any:`draw_rect`
720
        """
721
        raise NotImplementedError('this method is overwritten by subclasses')
722
723
    def get_char(self, x, y):
724
        """Return the character and colors of a tile as (ch, fg, bg)
725
726
        This method runs very slowly as is not recommended to be called
727
        frequently.
728
729
        Args:
730
            x (int): The x-coordinate to pick.
731
            y (int): The y-coordinate to pick.
732
733
        Returns:
734
            Tuple[int, Tuple[int, int, int], Tuple[int, int, int]]: A 3-item
735
                tuple: `(int, fg, bg)`
736
737
                The first item is an integer of the
738
                character at the position (x, y) the second and third are the
739
                foreground and background colors respectfully.
740
741
        .. seealso:: :any:`draw_char`
742
        """
743
        raise NotImplementedError('Method here only exists for the docstring')
744
745
    def __contains__(self, position):
746
        """Use ``((x, y) in console)`` to check if a position is drawable on
747
        this console.
748
        """
749
        x, y = position
750
        return (0 <= x < self.width) and (0 <= y < self.height)
751
752
class Console(_BaseConsole):
753
    """Contains character and color data and can be drawn to.
754
755
    The console created by the :any:`tdl.init` function is the root console
756
    and is the console that is rendered to the screen with :any:`flush`.
757
758
    Any console created from the Console class is an off-screen console that
759
    can be drawn on before being :any:`blit` to the root console.
760
761
    Args:
762
        width (int): Width of the new console in tiles
763
        height (int): Height of the new console in tiles
764
    """
765
766
    def __init__(self, width, height):
767
        _BaseConsole.__init__(self)
768
        self.console_c = _lib.TCOD_console_new(width, height)
769
        self.console = self
770
        self.width = width
771
        self.height = height
772
773
    @property
774
    def tcod_console(self):
775
        return self.console_c
776
    @tcod_console.setter
777
    def tcod_console(self, value):
778
        self.console_c = value
779
780
    @classmethod
781
    def _newConsole(cls, console):
782
        """Make a Console instance, from a console ctype"""
783
        self = cls.__new__(cls)
784
        _BaseConsole.__init__(self)
785
        self.console_c = console
786
        self.console = self
787
        self.width = _lib.TCOD_console_get_width(console)
788
        self.height = _lib.TCOD_console_get_height(console)
789
        return self
790
791
    def _root_unhook(self):
792
        """Change this root console into a normal Console object and
793
        delete the root console from TCOD
794
        """
795
        global _rootinitialized, _rootConsoleRef
796
        # do we recognise this as the root console?
797
        # if not then assume the console has already been taken care of
798
        if(_rootConsoleRef and _rootConsoleRef() is self):
799
            # turn this console into a regular console
800
            unhooked = _lib.TCOD_console_new(self.width, self.height)
801
            _lib.TCOD_console_blit(self.console_c,
802
                                   0, 0, self.width, self.height,
803
                                   unhooked, 0, 0, 1, 1)
804
            # delete root console from TDL and TCOD
805
            _rootinitialized = False
806
            _rootConsoleRef = None
807
            _lib.TCOD_console_delete(self.console_c)
808
            # this Console object is now a regular console
809
            self.console_c = unhooked
810
811
    def __del__(self):
812
        """
813
        If the main console is garbage collected then the window will be closed as well
814
        """
815
        if self.console_c is None:
816
            return # this console was already deleted
817
        if self.console_c is _ffi.NULL:
818
            # a pointer to the special root console
819
            self._root_unhook() # unhook the console and leave it to the GC
820
            return
821
        # this is a normal console pointer and can be safely deleted
822
        _lib.TCOD_console_delete(self.console_c)
823
        self.console_c = None
824
825
    def __copy__(self):
826
        # make a new class and blit
827
        clone = self.__class__(self.width, self.height)
828
        clone.blit(self)
829
        return clone
830
831
    def __getstate__(self):
832
        # save data from get_char
833
        data = [self.get_char(x, y) for x,y in
834
                _itertools.product(range(self.width), range(self.height))]
835
        return self.width, self.height, data
836
837
    def __setstate__(self, state):
838
        # make console from __init__ and unpack a get_char array
839
        width, height, data = state
840
        self.__init__(width, height)
841
        for (x, y), graphic in zip(_itertools.product(range(width),
842
                                                      range(height)), data):
843
            self.draw_char(x, y, *graphic)
844
845
    @staticmethod
846
    def _translate(x, y):
847
        """Convertion x and y to their position on the root Console for this Window
848
849
        Because this is a Console instead of a Window we return the paramaters
850
        untouched"""
851
        return x, y
852
853
    def clear(self, fg=Ellipsis, bg=Ellipsis):
854
        # inherit docstring
855
        assert fg is not None and bg is not None, 'Can not use None with clear'
856
        fg = _format_color(fg, self._fg)
857
        bg = _format_color(bg, self._bg)
858
        _lib.TCOD_console_set_default_foreground(self.console_c,
859
                                                 _to_tcod_color(fg)[0])
860
        _lib.TCOD_console_set_default_background(self.console_c,
861
                                                 _to_tcod_color(bg)[0])
862
        _lib.TCOD_console_clear(self.console_c)
863
864
865
    def _set_char(self, x, y, char, fg=None, bg=None,
866
                  bgblend=_lib.TCOD_BKGND_SET):
867
        """
868
        Sets a character.
869
        This is called often and is designed to be as fast as possible.
870
871
        Because of the need for speed this function will do NO TYPE CHECKING
872
        AT ALL, it's up to the drawing functions to use the functions:
873
        _format_char and _format_color before passing to this."""
874
        # values are already formatted, honestly this function is redundant
875
        return _put_char_ex(self.console_c, x, y, char, fg, bg, bgblend)
876
877
    def _set_batch(self, batch, fg, bg, bgblend=1, nullChar=False):
878
        """
879
        Try to perform a batch operation otherwise fall back to _set_char.
880
        If fg and bg are defined then this is faster but not by very
881
        much.
882
883
        if any character is None then nullChar is True
884
885
        batch is a iterable of [(x, y), ch] items
886
        """
887
        for (x, y), char in batch:
888
            self._set_char(x, y, char, fg, bg, bgblend)
889
890
    def get_char(self, x, y):
891
        # inherit docstring
892
        x, y = self._normalizePoint(x, y)
893
        char = _lib.TCOD_console_get_char(self.console_c, x, y)
894
        bg = _lib.TCOD_console_get_char_background(self.console_c, x, y)
895
        fg = _lib.TCOD_console_get_char_foreground(self.console_c, x, y)
896
        return char, (fg.r, fg.g, fg.b), (bg.r, bg.g, bg.b)
897
898
    # Copy docstrings for Sphinx
899
    clear.__doc__ = _BaseConsole.clear.__doc__
900
    get_char.__doc__ = _BaseConsole.get_char.__doc__
901
902
    def __repr__(self):
903
        return "<Console (Width=%i Height=%i)>" % (self.width, self.height)
904
905
906
class Window(_BaseConsole):
907
    """Isolate part of a :any:`Console` or :any:`Window` instance.
908
909
    This classes methods are the same as :any:`tdl.Console`
910
911
    Making a Window and setting its width or height to None will extend it to
912
    the edge of the console.
913
914
    This follows the normal rules for indexing so you can use a
915
    negative integer to place the Window relative to the bottom
916
    right of the parent Console instance.
917
918
    `width` or `height` can be set to None to extend as far as possible
919
    to the bottom right corner of the parent Console or can be a
920
    negative number to be sized reltive to the Consoles total size.
921
922
    Args:
923
        console (Union(tdl.Console, tdl.Window)): The parent object.
924
        x (int): x-coordinate to place the Window.
925
        y (int): y-coordinate to place the Window.
926
        width (Optional[int]): Width of the Window.
927
        height (Optional[int]): Height of the Window.
928
    """
929
930
    __slots__ = ('parent', 'x', 'y')
931
932
    def __init__(self, console, x, y, width, height):
933
        _BaseConsole.__init__(self)
934
        assert isinstance(console, (Console, Window)), 'console parameter must be a Console or Window instance, got %s' % repr(console)
935
        self.parent = console
936
        self.x, self.y, self.width, self.height = console._normalizeRect(x, y, width, height)
937
        if isinstance(console, Console):
938
            self.console = console
939
        else:
940
            self.console = self.parent.console
941
942
    def _translate(self, x, y):
943
        """Convertion x and y to their position on the root Console"""
944
        # we add our position relative to our parent and then call then next parent up
945
        return self.parent._translate((x + self.x), (y + self.y))
946
947
    def clear(self, fg=Ellipsis, bg=Ellipsis):
948
        # inherit docstring
949
        assert fg is not None and bg is not None, 'Can not use None with clear'
950
        if fg is Ellipsis:
951
            fg = self._fg
952
        if bg is Ellipsis:
953
            bg = self._bg
954
        self.draw_rect(0, 0, None, None, 0x20, fg, bg)
955
956
    def _set_char(self, x, y, char=None, fg=None, bg=None, bgblend=1):
957
        self.parent._set_char((x + self.x), (y + self.y), char, fg, bg, bgblend)
958
959
    def _set_batch(self, batch, *args, **kargs):
960
        # positional values will need to be translated to the parent console
961
        myX = self.x # remove dots for speed up
962
        myY = self.y
963
        self.parent._set_batch((((x + myX, y + myY), ch)
964
                                   for ((x, y), ch) in batch), *args, **kargs)
965
966
967
    def draw_char(self, x, y, char, fg=Ellipsis, bg=Ellipsis):
968
        # inherit docstring
969
        x, y = self._normalizePoint(x, y)
970
        if fg is Ellipsis:
971
            fg = self._fg
972
        if bg is Ellipsis:
973
            bg = self._bg
974
        self.parent.draw_char(x + self.x, y + self.y, char, fg, bg)
975
976
    def draw_rect(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
977
        # inherit docstring
978
        x, y, width, height = self._normalizeRect(x, y, width, height)
979
        if fg is Ellipsis:
980
            fg = self._fg
981
        if bg is Ellipsis:
982
            bg = self._bg
983
        self.parent.draw_rect(x + self.x, y + self.y, width, height,
984
                              string, fg, bg)
985
986
    def draw_frame(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis):
987
        # inherit docstring
988
        x, y, width, height = self._normalizeRect(x, y, width, height)
989
        if fg is Ellipsis:
990
            fg = self._fg
991
        if bg is Ellipsis:
992
            bg = self._bg
993
        self.parent.draw_frame(x + self.x, y + self.y, width, height,
994
                               string, fg, bg)
995
996
    def get_char(self, x, y):
997
        # inherit docstring
998
        x, y = self._normalizePoint(x, y)
999
        xtrans, ytrans = self._translate(x, y)
1000
        return self.console.get_char(xtrans, ytrans)
1001
1002
    def __repr__(self):
1003
        return "<Window(X=%i Y=%i Width=%i Height=%i)>" % (self.x, self.y,
1004
                                                          self.width,
1005
                                                          self.height)
1006
1007
1008
def init(width, height, title=None, fullscreen=False, renderer='SDL'):
1009
    """Start the main console with the given width and height and return the
1010
    root console.
1011
1012
    Call the consoles drawing functions.  Then remember to use L{tdl.flush} to
1013
    make what's drawn visible on the console.
1014
1015
    Args:
1016
        width (int): width of the root console (in tiles)
1017
        height (int): height of the root console (in tiles)
1018
        title (Optiona[Text]):
1019
            Text to display as the window title.
1020
1021
            If left None it defaults to the running scripts filename.
1022
        fullscreen (bool): Can be set to True to start in fullscreen mode.
1023
        renderer (Text): Can be one of 'GLSL', 'OPENGL', or 'SDL'.
1024
1025
        Due to way Python works you're unlikely to see much of an
1026
        improvement by using 'GLSL' over 'OPENGL' as most of the
1027
        time Python is slow interacting with the console and the
1028
        rendering itself is pretty fast even on 'SDL'.
1029
1030
    Returns:
1031
        tdl.Console: The root console.
1032
1033
            Only what is drawn on the root console is
1034
            what's visible after a call to :any:`tdl.flush`.
1035
            After the root console is garbage collected, the window made by
1036
            this function will close.
1037
1038
    .. seealso::
1039
        :any:`Console` :any:`set_font`
1040
    """
1041
    RENDERERS = {'GLSL': 0, 'OPENGL': 1, 'SDL': 2}
1042
    global _rootinitialized, _rootConsoleRef
1043
    if not _fontinitialized: # set the default font to the one that comes with tdl
1044
        set_font(_os.path.join(__path__[0], 'terminal8x8.png'),
1045
                 None, None, True, True)
1046
1047
    if renderer.upper() not in RENDERERS:
1048
        raise TDLError('No such render type "%s", expected one of "%s"' % (renderer, '", "'.join(RENDERERS)))
1049
    renderer = RENDERERS[renderer.upper()]
1050
1051
    # If a console already exists then make a clone to replace it
1052
    if _rootConsoleRef and _rootConsoleRef():
1053
        # unhook the root console, turning into a regular console and deleting
1054
        # the root console from libTCOD
1055
        _rootConsoleRef()._root_unhook()
1056
1057
    if title is None: # use a default title
1058
        if _sys.argv:
1059
            # Use the script filename as the title.
1060
            title = _os.path.basename(_sys.argv[0])
1061
        else:
1062
            title = 'python-tdl'
1063
1064
    _lib.TCOD_console_init_root(width, height, _encodeString(title), fullscreen, renderer)
1065
1066
    #event.get() # flush the libtcod event queue to fix some issues
1067
    # issues may be fixed already
1068
1069
    event._eventsflushed = False
1070
    _rootinitialized = True
1071
    rootconsole = Console._newConsole(_ffi.NULL)
1072
    _rootConsoleRef = _weakref.ref(rootconsole)
1073
1074
    return rootconsole
1075
1076
def flush():
1077
    """Make all changes visible and update the screen.
1078
1079
    Remember to call this function after drawing operations.
1080
    Calls to flush will enfore the frame rate limit set by :any:`tdl.set_fps`.
1081
1082
    This function can only be called after :any:`tdl.init`
1083
    """
1084
    if not _rootinitialized:
1085
        raise TDLError('Cannot flush without first initializing with tdl.init')
1086
    # flush the OS event queue, preventing lock-ups if not done manually
1087
    event.get()
1088
    _lib.TCOD_console_flush()
1089
1090
def set_font(path, columns=None, rows=None, columnFirst=False,
1091
             greyscale=False, altLayout=False):
1092
    """Changes the font to be used for this session.
1093
    This should be called before :any:`tdl.init`
1094
1095
    If the font specifies its size in its filename (i.e. font_NxN.png) then this
1096
    function can auto-detect the tileset formatting and the parameters columns
1097
    and rows can be left None.
1098
1099
    While it's possible you can change the font mid program it can sometimes
1100
    break in rare circumstances.  So use caution when doing this.
1101
1102
    Args:
1103
        path (Text): A file path to a `.bmp` or `.png` file.
1104
        columns (int):
1105
            Number of columns in the tileset.
1106
1107
            Can be left None for auto-detection.
1108
        rows (int):
1109
            Number of rows in the tileset.
1110
1111
            Can be left None for auto-detection.
1112
        columnFirst (bool):
1113
            Defines if the characer order goes along the rows or colomns.
1114
1115
            It should be True if the charater codes 0-15 are in the
1116
            first column, and should be False if the characters 0-15
1117
            are in the first row.
1118
        greyscale (bool):
1119
            Creates an anti-aliased font from a greyscale bitmap.
1120
            Otherwise it uses the alpha channel for anti-aliasing.
1121
1122
            Unless you actually need anti-aliasing from a font you
1123
            know uses a smooth greyscale channel you should leave
1124
            this on False.
1125
        altLayout (bool)
1126
            An alternative layout with space in the upper left corner.
1127
            The colomn parameter is ignored if this is True,
1128
            find examples of this layout in the `font/libtcod/`
1129
            directory included with the python-tdl source.
1130
1131
    Raises:
1132
        TDLError: Will be raised if no file is found at path or if auto-
1133
                  detection fails.
1134
    """
1135
    # put up some constants that are only used here
1136
    FONT_LAYOUT_ASCII_INCOL = 1
1137
    FONT_LAYOUT_ASCII_INROW = 2
1138
    FONT_TYPE_GREYSCALE = 4
1139
    FONT_LAYOUT_TCOD = 8
1140
    global _fontinitialized
1141
    _fontinitialized = True
1142
    flags = 0
1143
    if altLayout:
1144
        flags |= FONT_LAYOUT_TCOD
1145
    elif columnFirst:
1146
        flags |= FONT_LAYOUT_ASCII_INCOL
1147
    else:
1148
        flags |= FONT_LAYOUT_ASCII_INROW
1149
    if greyscale:
1150
        flags |= FONT_TYPE_GREYSCALE
1151
    if not _os.path.exists(path):
1152
        raise TDLError('Font %r not found.' % _os.path.abspath(path))
1153
    path = _os.path.abspath(path)
1154
1155
    # and the rest is the auto-detect script
1156
    imgSize = _getImageSize(path) # try to find image size
1157
    if imgSize:
1158
        fontWidth, fontHeight = None, None
1159
        imgWidth, imgHeight = imgSize
1160
        # try to get font size from filename
1161
        match = _re.match('.*?([0-9]+)[xX]([0-9]+)', _os.path.basename(path))
1162
        if match:
1163
            fontWidth, fontHeight = match.groups()
1164
            fontWidth, fontHeight = int(fontWidth), int(fontHeight)
1165
1166
            # estimate correct tileset size
1167
            estColumns, remC = divmod(imgWidth, fontWidth)
1168
            estRows, remR = divmod(imgHeight, fontHeight)
1169
            if remC or remR:
1170
                _warnings.warn("Font may be incorrectly formatted.")
1171
1172
            if not columns:
1173
                columns = estColumns
1174
            if not rows:
1175
                rows = estRows
1176
        else:
1177
            # filename doesn't contain NxN, but we can still estimate the fontWidth
1178
            # and fontHeight given number of columns and rows.
1179
            if columns and rows:
1180
                fontWidth, remC = divmod(imgWidth, columns)
1181
                fontHeight, remR = divmod(imgHeight, rows)
1182
                if remC or remR:
1183
                    _warnings.warn("Font may be incorrectly formatted.")
1184
1185
            # the font name excluded the fonts size
1186
            if not (columns and rows):
1187
                # no matched font size and no tileset is given
1188
                raise TDLError('%s has no font size in filename' % _os.path.basename(path))
1189
1190
        if columns and rows:
1191
            # confirm user set options
1192
            if (fontWidth * columns != imgWidth or
1193
                fontHeight * rows != imgHeight):
1194
                _warnings.warn("set_font parameters are set as if the image size is (%d, %d) when the detected size is actually (%i, %i)"
1195
                             % (fontWidth * columns, fontHeight * rows,
1196
                                imgWidth, imgHeight))
1197
    else:
1198
        _warnings.warn("%s is probably not an image." % _os.path.basename(path))
1199
1200
    if not (columns and rows):
1201
        # didn't auto-detect
1202
        raise TDLError('Can not auto-detect the tileset of %s' % _os.path.basename(path))
1203
1204
    _lib.TCOD_console_set_custom_font(_encodeString(path), flags, columns, rows)
1205
1206
def get_fullscreen():
1207
    """Returns True if program is fullscreen.
1208
1209
    Returns:
1210
        bool:  Returns True if the application is in full-screen mode.
1211
               Otherwise returns False.
1212
    """
1213
    if not _rootinitialized:
1214
        raise TDLError('Initialize first with tdl.init')
1215
    return _lib.TCOD_console_is_fullscreen()
1216
1217
def set_fullscreen(fullscreen):
1218
    """Changes the fullscreen state.
1219
1220
    Args:
1221
        fullscreen (bool): True for full-screen, False for windowed mode.
1222
    """
1223
    if not _rootinitialized:
1224
        raise TDLError('Initialize first with tdl.init')
1225
    _lib.TCOD_console_set_fullscreen(fullscreen)
1226
1227
def set_title(title):
1228
    """Change the window title.
1229
1230
    Args:
1231
        title (Text): The new title text.
1232
    """
1233
    if not _rootinitialized:
1234
        raise TDLError('Not initilized.  Set title with tdl.init')
1235
    _lib.TCOD_console_set_window_title(_encodeString(title))
1236
1237
def screenshot(path=None):
1238
    """Capture the screen and save it as a png file.
1239
1240
    If path is None then the image will be placed in the current
1241
    folder with the names:
1242
    ``screenshot001.png, screenshot002.png, ...``
1243
1244
    Args:
1245
        path (Optional[Text]): The file path to save the screenshot.
1246
    """
1247
    if not _rootinitialized:
1248
        raise TDLError('Initialize first with tdl.init')
1249
    if isinstance(path, str):
1250
        _lib.TCOD_sys_save_screenshot(_encodeString(path))
1251
    elif path is None: # save to screenshot001.png, screenshot002.png, ...
1252
        filelist = _os.listdir('.')
1253
        n = 1
1254
        filename = 'screenshot%.3i.png' % n
1255
        while filename in filelist:
1256
            n += 1
1257
            filename = 'screenshot%.3i.png' % n
1258
        _lib.TCOD_sys_save_screenshot(_encodeString(filename))
1259
    else: # assume file like obj
1260
        #save to temp file and copy to file-like obj
1261
        tmpname = _os.tempnam()
1262
        _lib.TCOD_sys_save_screenshot(_encodeString(tmpname))
1263
        with tmpname as tmpfile:
1264
            path.write(tmpfile.read())
1265
        _os.remove(tmpname)
1266
    #else:
1267
    #    raise TypeError('path is an invalid type: %s' % type(path))
1268
1269
def set_fps(fps):
1270
    """Set the maximum frame rate.
1271
1272
    Further calls to :any:`tdl.flush` will limit the speed of
1273
    the program to run at `fps` frames per second. This can
1274
    also be set to None to remove the frame rate limit.
1275
1276
    Args:
1277
        fps (optional[int]): The frames per second limit, or None.
1278
    """
1279
    _lib.TCOD_sys_set_fps(fps or 0)
1280
1281
def get_fps():
1282
    """Return the current frames per second of the running program set by
1283
    :any:`set_fps`
1284
1285
    Returns:
1286
        int: The frame rate set by :any:`set_fps`.
1287
             If there is no current limit, this will return 0.
1288
    """
1289
    return _lib.TCOD_sys_get_fps()
1290
1291
def force_resolution(width, height):
1292
    """Change the fullscreen resoulution.
1293
1294
    Args:
1295
        width (int): Width in pixels.
1296
        height (int): Height in pixels.
1297
    """
1298
    _lib.TCOD_sys_force_fullscreen_resolution(width, height)
1299
1300
1301
__all__ = [_var for _var in locals().keys() if _var[0] != '_'] # remove modules from __all__
1302
__all__ += ['_BaseConsole'] # keep this object public to show the documentation in epydoc
1303
__all__.remove('absolute_import')
1304
__all__.remove('division')
1305
__all__.remove('print_function')
1306
__all__.remove('unicode_literals')
1307
1308
# backported function names
1309
_BaseConsole.setMode = _style.backport(_BaseConsole.set_mode)
1310
_BaseConsole.setColors = _style.backport(_BaseConsole.set_colors)
1311
_BaseConsole.printStr = _style.backport(_BaseConsole.print_str)
1312
_BaseConsole.drawChar = _style.backport(_BaseConsole.draw_char)
1313
_BaseConsole.drawStr = _style.backport(_BaseConsole.draw_str)
1314
_BaseConsole.drawRect = _style.backport(_BaseConsole.draw_rect)
1315
_BaseConsole.drawFrame = _style.backport(_BaseConsole.draw_frame)
1316
_BaseConsole.getCursor = _style.backport(_BaseConsole.get_cursor)
1317
_BaseConsole.getSize = _style.backport(_BaseConsole.get_size)
1318
_BaseConsole.getChar = _style.backport(_BaseConsole.get_char)
1319
1320
Console.getChar = _style.backport(Console.get_char)
1321
1322
Window.drawChar = _style.backport(Window.draw_char)
1323
Window.drawRect = _style.backport(Window.draw_rect)
1324
Window.drawFrame = _style.backport(Window.draw_frame)
1325
Window.getChar = _style.backport(Window.get_char)
1326
1327
setFont = _style.backport(set_font)
1328
getFullscreen = _style.backport(get_fullscreen)
1329
setFullscreen = _style.backport(set_fullscreen)
1330
setTitle = _style.backport(set_title)
1331
setFPS = _style.backport(set_fps)
1332
getFPS = _style.backport(get_fps)
1333
forceResolution = _style.backport(force_resolution)
1334