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.