File: io.rst

package info (click to toggle)
python-asciimatics 1.15.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,488 kB
  • sloc: python: 15,713; sh: 8; makefile: 2
file content (307 lines) | stat: -rw-r--r-- 14,137 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
Basic Input/Output
==================

Creating a Screen
-----------------
The starting point for any asciimatics program is the :py:obj:`.Screen` object.  It can most easily
be obtained from the :py:meth:`.wrapper` static method.  This will handle all the necessary
initialization for your environment and pass the constructed Screen into the specified function.
For example:

.. code-block:: python

    from asciimatics.screen import Screen
    from time import sleep

    def demo(screen):
        screen.print_at('Hello world!', 0, 0)
        screen.refresh()
        sleep(10)

    Screen.wrapper(demo)

You can also use the :py:obj:`.ManagedScreen` class as a function decorator to achieve the same thing
as the above.  For example:

.. code-block:: python

    from asciimatics.screen import ManagedScreen
    from asciimatics.scene import Scene
    from asciimatics.effects import Cycle, Stars
    from asciimatics.renderers import FigletText

    @ManagedScreen
    def demo(screen=None):
        screen.print_at('Hello world!', 0, 0)
        screen.refresh()
        sleep(10)

    demo()

Or you can also use it as a context manager (i.e. using the `with` keyword).  For example:

.. code-block:: python

    from asciimatics.screen import ManagedScreen
    from asciimatics.scene import Scene
    from asciimatics.effects import Cycle, Stars
    from asciimatics.renderers import FigletText

    def demo():
        with ManagedScreen() as screen:
            screen.print_at('Hello world!', 0, 0)
            screen.refresh()
            sleep(10)

    demo()

If you need more control than this allows, you can fall back to using :py:meth:`.open`, but then
you have to call :py:meth:`.close` before exiting your application to restore the environment.

Output
------
Once you have a Screen, you probably want to ensure that it is clear before you do anything.  To
do this call :py:meth:`~.Screen.clear`.  Now that it's blank, the simplest way to output text is
using the :py:meth:`~.Screen.print_at` method.  This allows you to place a string at a desired
location in a specified colour.  The coordinates are zero-indexed starting at the top left of the
screen and move down and right, so the example above displays `Hello world!` at (0, 0) which is the
top left of the screen.

Colours
^^^^^^^
There is a long history to terminals and this is no more obvious than when it comes to colours.
Original terminals had limited colours, and so used attributes to change the format, using effects
like bold, underline and reverse video.  As time wore on, more colours were added and you can get
full 24 bit colour on some terminals.

For now, asciimatics limits itself to a maximum of the 256 colour palette.  You can find how many
colours your terminal supports by looking at the :py:obj:`~.Screen.colours` property.  These days
most terminals will support a minimum of 8 colours.  These are defined by the `COLOUR_xxx` constants
in the Screen class.  The full list is as follows:

.. code-block:: python

    COLOUR_BLACK = 0
    COLOUR_RED = 1
    COLOUR_GREEN = 2
    COLOUR_YELLOW = 3
    COLOUR_BLUE = 4
    COLOUR_MAGENTA = 5
    COLOUR_CYAN = 6
    COLOUR_WHITE = 7

These should always work for you as background and foreground colours (even on Windows).  For many
systems you can also use the attributes (see later) to double the number of foreground colours.

If you have a display capable of handling more than these (e.g. 256 colour xterm) you can use the
indexes of the colours for that display directly instead.  For a full list of the colour indices,
look `here <https://askubuntu.com/a/821163/1014276>`__.

When creating effects that use these extra colours, it is recommended that you also support a
reduced colour mode, using just the 8 common colours.  For an example of how to do this, see the
:py:obj:`.Rainbow` class.

Finally, some terminals have the concept of a default colour.  These can often have special attributes
that are otherwise impossible to set in a terminal - e.g. transparency.  If your terminal supports
these you can use the ``COLOUR_DEFAULT`` setting to use them.  If not supported, asciimatics will treat
them as a black background and white foreground.

Attributes
^^^^^^^^^^
Attributes are a way of modifying the displayed text in some basic ways that early hardware
terminals supported before they had colours.  Most systems don't use hardware terminals any more,
but the concept persists in all native console APIs and so is also used here.

Supported attributes are defined by the `A_xxx` constants in the Screen class.  The full list is as
follows:

.. code-block:: python

    A_BOLD = 1
    A_NORMAL = 2
    A_REVERSE = 3
    A_UNDERLINE = 4

Most systems will support bold (a.k.a bright), normal and reverse attributes.  Others are capable
of more, but you will have difficulties using them in a cross-platform manner and so they are
deprecated. The attribute is just another parameter to `print_at`.  For example:

.. code-block:: python

    # Bright green text
    screen.print_at('Hello world!', 0, 0, COLOUR_GREEN, A_BOLD)

Multicoloured strings
^^^^^^^^^^^^^^^^^^^^^
If you want to do something more complex, you can use the :py:meth:`~.Screen.paint` method to
specify a colour map for each character to be displayed.  This must be a list of colour/attribute
values (tuples or lists) that is at least as long as the text to be displayed.  This method is
typically used for displaying complex, multi-coloured text from a Renderer.  See
:ref:`animation-ref` for more details.

Unicode support
^^^^^^^^^^^^^^^
As of V1.7, asciimatics is officially misleadingly named!  It has support for unicode input and
output.  Just use a unicode literal where you would previously have used a string.  For example:

.. code-block:: python

    # Should have a telephone at the start...
    screen.print_at(u'☎ Call me!', 0, 0, COLOUR_GREEN, A_BOLD)

If your system is configured to support unicode, this should be output correctly.  However, not all
systems will work straight out of the box.  See :ref:`unicode-issues-ref` for more details on how
to fix this.

Clearing the Screen
^^^^^^^^^^^^^^^^^^^
Once you have started your application, you will likely want to clear parts, or all, of the Screen
at times.  The recommended way to do that is using :py:meth:`~.Screen.clear_buffer`.  This prevents
the flicker that you will see if you tried using the previously mentioned `clear` method instead.

Refreshing the Screen
---------------------
Just using the above methods to output to screen isn't quite enough.  The Screen maintains a buffer
of what is to be displayed and will only actually display it once the :py:meth:`~.Screen.refresh`
method is called.  This is done to reduce flicker on the display device as new content is created.

Applications are required to re-render everything that needs to be displayed and then call refresh
when all the new content is ready.  Note that the :py:meth:`.play` and :py:meth:`.draw_next_frame`
methods will do this for you automatically at the end of each frame, so you don't need to call it
again inside your animations.

Input
-----
To handle user input, use the :py:meth:`.get_event` method.  This instantly returns the latest
key-press or mouse event, without waiting for a new line and without echoing it to screen (for
keyboard events).  If there is no event available, it will return `None`.

The exact class returned depends on the event.  It will be either :py:obj:`.KeyboardEvent` or
:py:obj:`.MouseEvent`.  Handling of each is covered below.

If you wish to wait until some input is available, you can use the :py:meth:`.wait_for_input` method
to block execution and then call :py:meth:`.get_event` to retrieve the input.

KeyboardEvent
^^^^^^^^^^^^^
This event is triggered for any key-press, including auto repeat when keys are held down.
``key_code`` is the ordinal representation of the key (taking into account keyboard state - e.g.
caps lock) if possible, or an extended key code (the ``KEY_xxx`` constants in the Screen class)
where not.

For example, if you press 'a' normally :py:meth:`.get_event` will return a KeyboardEvent with
``key_code`` 97, which is ``ord('a')``.  If you press the same key with caps lock on, you will get
65, which is ``ord('A')``.  If you press 'F7' you will always get ``KEY_F7`` irrespective of the
caps lock.

The control key (CTRL) on a keyboard returns control codes (the first 31 codes in the ASCII table).
You can calculate the control code for any key using the :py:meth:`.ctrl` method.  Note that not
all systems will return control codes for all keys, so this function can return None if asciimatics
doesn't believe the key will work.  For best system compatibility, stick to the control codes for
alphabetical characters - i.e. "A" to "Z".

As of V1.7, you can also get keyboard events for Unicode characters outside the ASCII character
set.  These will also return the ordinal representation of the unicode character, just like the
previous support for ASCII characters.

If you are seeing random garbage instead, your system is probably not correctly configured for
unicode.  See :ref:`unicode-issues-ref` for how to fix this.

MouseEvent
^^^^^^^^^^
This event is triggered for any mouse movement or button click.  The current coordinates of the
mouse on the Screen are stored in the ``x`` and ``y`` properties.  If a button was clicked, this is
tracked by the ``buttons`` property.  Allowed values for the buttons are ``LEFT_CLICK``,
``RIGHT_CLICK`` and ``DOUBLE_CLICK``.

.. warning::

    In general, Windows will report all of these straight out of the box.  Linux will only report
    mouse events if you are using a terminal that supports mouse events (e.g. xterm) in the
    terminfo database.  Even then, not all terminals report all events.  For example, the standard
    xterm function is just to report button clicks.  If you need your application to handle mouse
    move events too, you will need to use a terminal that supports the additional extensions - e.g.
    the xterm-1003 terminal type.  See :ref:`mouse-issues-ref` for more details on how to fix this.

Screen Resizing
---------------
It is not possible to change the Screen size through your program.  However, the user may resize
their terminal or console while your program is running.  Asciimatics will continue to run as best
as it can within its original dimensions, or you can tell it to re-create the Screen to the new
size if desired.

In a little more detail, you can read the Screen size (at the time of creation) from the
:py:obj:`~.Screen.dimensions` property.  If the user changes the size at any point, you can detect
this by calling the :py:meth:`.has_resized` method.  In addition, you can tell the Screen to throw
an exception if this happens while you are playing a Scene by specifying ``stop_on_resize=True``.

Once you have detected that the screen size has changed using one of the options above, you can
either decide to carry on with the current Screen or throw it away and create a new one (by simply
creating a new Screen object). If you do the latter, you will typically need to recreate your
associated Scenes and Effects to run inside the new boundaries.  See the bars.py demo as a sample
of how to handle this.

Scraping Text
-------------
Sometimes it is useful to be able to read what is already displayed on the Screen at a given
location.  This is often referred to as screen scraping.  You can do this using the
:py:meth:`~.Screen.get_from` method.  It will return the displayed character and attributes (as a
4-tuple) for any single character location on the Screen.

.. code-block:: python

    # Check we've not already displayed something before updating.
    current_char, fg, attr, bg = screen.get_from(x, y)
    if current_char != 32:
        screen.print_at('X', x, y)

.. warning::

    Some languages use double-width glyphs.  When scraping text for such glyphs, you will find that
    ``get_from`` returns the character for both of the 2 locations containing the glyph.  For
    example, if you printed ``是`` at ``(0, 0)``, you would find that asciimatics returns this value
    for both ``(0, 0)`` and ``(0, 1)``.  For more details on which languages (and hence unicode
    characters) are affected by this see, `here
    <https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms>`__ and `here
    <http://denisbider.blogspot.co.uk/2015/09/when-monospace-fonts-arent-unicode.html>`__.

Drawing shapes
--------------
The Screen object also provides some anti-aliased line drawing facilities, using ASCII characters
to represent the line.  The :py:meth:`~.Screen.move` method will move the drawing cursor to the
specified coordinates and then the :py:meth:`~.Screen.draw` method will draw a straight line from
the current cursor location to the specified coordinates.

You can override the anti-aliasing with the ``char`` parameter.  This is most useful when trying to
clear what was already drawn.  For example:

.. code-block:: python

    # Draw a diagonal line from the top-left of the screen.
    screen.move(0, 0)
    screen.draw(10, 10)

    # Clear the line
    screen.move(0, 0)
    screen.draw(10, 10, char=' ')

If the resulting line is too thick, you can also pick a thinner pen by specifying ``thin=True``.
Examples of both styles can be found in the Clock sample code.

In addition, there is the :py:meth:`~.Screen.fill_polygon` method which will draw a filled
polygon in the specified colour using a set of points passed in to define the required shape.  This
uses the scan-line algorithm, so you can cut holes inside the shape by defining one polygon inside
another.  For example:

.. code-block:: python

    # Draw a large with a smaller rectangle hole in the middle.
    screen.fill_polygon([[(60, 0), (70, 0), (70, 10), (60, 10)],
                         [(63, 2), (67, 2), (67, 8), (63, 8)]])


Unicode drawing
---------------
The drawing methods covered above are unicode aware and will default to the correct character
set for your terminal, using unicode block characters where possible and falling back to pure
ASCII text if not.