Basic Input/Output

Creating a Screen

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

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 ManagedScreen class as a function decorator to achieve the same thing as the above. For example:

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:

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 open(), but then you have to call 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 clear(). Now that it’s blank, the simplest way to output text is using the 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 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:

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.

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

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:

# 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 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 Animation 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:

# 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 Unicode characters are not working 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 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 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 play() and 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 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 KeyboardEvent or MouseEvent. Handling of each is covered below.

If you wish to wait until some input is available, you can use the wait_for_input() method to block execution and then call 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 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 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 Unicode characters are not working 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 Mouse support not working 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 dimensions property. If the user changes the size at any point, you can detect this by calling the 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 get_from() method. It will return the displayed character and attributes (as a 4-tuple) for any single character location on the Screen.

# 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 and here.

Drawing shapes

The Screen object also provides some anti-aliased line drawing facilities, using ASCII characters to represent the line. The move() method will move the drawing cursor to the specified coordinates and then the 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:

# 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 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:

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