Completed
Pull Request — master (#28)
by
unknown
01:14
created

_BaseConsole._drawStrGen()   A

Complexity

Conditions 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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