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