kaa.canvas

Introduction

kaa.canvas is a second generation canvas API and deprecates kaa.mevas. kaa.canvas uses kaa.evas for most of the heavy lifting and builds a high-level API on top of it, providing support for layout and several other features. Canvas objects know their state, and are able to recreate themselves on any evas canvas, allowing objects to be moved between canvases, and, more importantly, for delayed instantiation, so that an object can be defined and modified before it is added to the canvas.

kaa.canvas provides basic objects like Image, Text, and Rectangle, as well as more advanced objects like TextBlock (for complex multi-line text layout) and Movie (for displaying video on the canvas using kaa.player). It also integrates into the kaa.notifier mainloop so that it is not typically necessary to explicitly render the canvas. Like mevas, kaa.canvas implements container objects, which can hold any kind of object (including other containers). Unlike mevas, however, kaa.canvas provides a basic layout model that borrows from html as well as gtk's box model, which allows you to implement aspect- and resolution-independent layouts. This is particularly important as interfaces should be built to work on both 4:3 and 16:9 displays at varying resolutions.

Each canvas object has a set of properties that are set at any point after the object's instatiation, and synchronized immediately prior to rendering. Properties set on containers apply to their children as well. For example, if the container's color property is set to (255, 255, 255, 128) (a 4-tuple representing red, green, blue, and alpha values) then all children of the container will have that color value multiplied with their own. So if a child of that container had its own color property also set to (255, 255, 255, 128), the final, rendered color value would be (255, 255, 255, 64).

Objects and Properties

kaa.canvas has the following object hierarchy:

The following table outlines the properties supported by each object and their purpose. Subclasses inherit all properties of their superclasses:

Object Property Description
Object name string or None: Identifier for the object that is unique to the canvas; objects may be retrieved by their id using Canvas.find_object. Default: None. Methods: set_name, get_name
visible boolean: True if the object is visible on the canvas, False if not. Default: True. Methods: set_visible, get_visible, hide, show
layer integer: layer or z-index of the object; the greater the number, the higher the object is rendered on the stacking order. Objects at the same layer value will be layered in the order in which they were added to their parents. Default: 0. Methods: set_layer, get_layer
color 4-tuple or string: A tuple representing the red, green, blue, alpha color of the object, or a string in HTML-style notation, such as #ff00ff80 (which represents the tuple (255, 0, 255, 128)). Default: (255, 255, 255, 255). Methods: set_color, get_color
expand boolean: Used for children of Box objects. If True, the object will occupy the maximum amount of space the box permits; if False, will occupy only as much space as necessary for the object. Default: False. Methods: set_expand, get_expand
width integer or string: A value representing the width of the object. Width can be a fixed (integer) size (in pixels), or a relative (percentage) size. Percentage values (e.g. "75%") are relative to the object's container. Width may also be auto, which means that the width is the object's natural width (such as, for example, an image's native width). e.g. 100, or "50%". Default: auto. Methods: set_width, get_width, resize.
height integer or string: Same as the width property, except specifies the height of the object. Default: auto. Methods: set_height, get_height, resize.
left integer or string: A value representing where the left edge of the object is to be positioned, relative to its container. May be a fixed position (integer value) in pixels, or a string representing the percentage of the object's container's width. Default: 0. Methods: set_left, get_left, move.
top integer or string: A value representing where the top edge of the object is to be positioned, relative to its container. May be a fixed position (integer value) in pixels, or a string representing the percentage of the object's container's height. Default: 0. Methods: set_height, get_height, move.
right integer or string: Same as the left property, but specifies the position of the object's right edge. Default: None (not specified; uses left property instead). Methods: set_right, get_right, move.
bottom integer or string: Same as the top property, but specifies the position of the object's bottom edge. Default: None (not specified; uses top property instead). Methods: set_bottom, get_bottom, move.
hcenter integer or string: Same as the left property, but specifies the position of the object's center point along its horizontal axis. Example: "50%" causes the object to be horizontally centered within its container. Default: None (not specified; uses left property instead). Methods: set_hcenter, get_hcenter, move.
vcenter integer or string: Same as the top property, but specifies the position of the object's center point along its vertical axis. Example: "50%" causes the object to be vertically centered within its container. Default: None (not specified; uses top property instead). Methods: set_vcenter, get_vcenter, move.
clip 2-tuple or "auto" or None: a value in the form ((x, y), (width, height)) that defines a rectangle to which the object will be clipped. All values may be integers in pixels, or, in the case of width and height, may be strings representing percentages of the object's actual width and height. Values may also be negative, in which case they behave as negative list indices in Python (that is, relative to the end). A value of auto is equivalent to ((0, 0), ("100%", "100%")) e.g. ((10, 10), (-10, -10)) crops 10 pixels from all sides of the object. Default: None. Methods: clip, unclip, set_clip, get_clip.
Image

(inherits
Object)
filename string: The path to a file from which to load the image. Default: None. Methods: set_image
image imlib2.Image object: Wraps an existing Imlib2 image object. Default: None. Methods: set_image
pixels 4-tuple: A tuple holding (data, width, height, format) where data is a buffer object or memory address, and format is PIXEL_FORMAT_YUV420P_601. Used to convert an image from YV12 colorspace. Default: None. Methods: import_pixels
data 2-tuple: A tuple holding (data, copy) where data is a buffer object or memory address, and copy is a boolean indicating whether the memory should be copied or used directly during rendering. Default: None. Methods: set_data
dirty boolean: Indicates whether the image set by set_data is dirty requires re-rendering. Default: False. Methods: set_dirty
has_alpha boolean: Indicates whether the image has a valid alpha channel. Default: True. Methods: set_has_alpha
border 4-tuple or None: A tuple of integers representing (left, right, top, bottom). This is used to provide a hint to the canvas when scaling an image. Each parameter indicates an offset from the corresponding edge, and pixels within the border will not be scaled but rather copied when scaling. This allows a button image, for example, to scale without distorting its edges. Default: None. Methods: set_border, get_border
aspect preserve, ignore or floating point value: Controls the behavior of resizing. If preserve, either the image's width or its height can be specified, and the other dimension will be automatically computed from the image's original aspect ratio. If ignore, the both the width and height properties are used in determining the image's size. If a floating point value is given, behaves as preserve except instead of using the image's native aspect ratio, it uses the one specified. Default: preserve. Methods: set_aspect, get_aspect
Movie

(inherits
Image)
detached boolean: If True, the movie is detached from the canvas and fills the canvas window. If False, the movie is attached to the canvas. Default: False. Methods: detach, attach, set_detached, get_detached
Text

(inherits
Object)
font 2-tuple: A tuple in the form (font, size) defining the font name and its size. e.g. ("tahoma", 16). Default: ("arial", 24). Methods: set_font, get_font
text string: A string containing the text to render. Default: None. Methods: set_text, get_text
clip "auto" or None: If set to auto, text will automatically be clipped to its container. Instead of being abruptly clipped, the text will fade to transparent. Default: auto. Methods: set_clip, get_clip, clip, unclip
TextBlock

(inherits
Object)
markup string: HTML-style markup containing the text to render. This text may contain template variables in the form @variable@ which can be defined by calling set_template_variable. Default: None. Methods: set_markup, get_markup, set_template_variable
Canvas

(inherits
Container)
fontpath list: A list of paths from which to load true-type fonts. Default: all directories within /usr/share/fonts. Methods: add_font_path, remove_font_path

The API

Here's the Hello World of kaa.canvas:

import kaa, kaa.canvas

# Create a 640x480 window into which the canvas will be added.
canvas = kaa.canvas.X11Canvas((640, 480))

# Create a Text object with the text "Hello world!"
hello = kaa.canvas.Text("Hello world!")
# Now add the new text object to the canvas.
canvas.add_child(hello)

# Enter the mainloop, which implicitly displays the window and renders the canvas.
kaa.main()

This code creates an X11 window that's 640x480 in size, and displays "Hello world!" at the default font (which is currently hardcoded as arial/24) at the top left corner of the window. X11Canvas subclasses Canvas which subclasses Container. add_child is a method of Container, and causes the child (in this case, the Text object) to be adopted by the container (in this case, the Canvas itself). The next example builds on the previous one by adding a background image to the canvas, and instead of displaying the text at the default position ((0, 0), or top left corner of the container), it will center the text:

import kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))

background = kaa.canvas.Image("background.jpg")
canvas.add_child(background)

hello = kaa.canvas.Text("Hello world!")
hello.move(hcenter = "50%", vcenter = "50%")
canvas.add_child(hello)

kaa.main()

Container.add_child allows most of the common properties (left, top, width, height, layer, color, name, clip, etc.) to be set by keyword arguments, so an object can easily be added and adjusted with minimal code. add_child will also return the object that was provided in the first argument. The next example shortens the above code to use the add_child kwargs, and also adds a new feature which will change the text after 2 seconds to say something else:

import kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.add_child( kaa.canvas.Image("background.jpg"))
text = canvas.add_child(kaa.canvas.Text("Hello world!"), vcenter="50%", hcenter="50%")

def change_text(text):
    text.set_text("This is a kaa.canvas example.")

kaa.notifier.OneShotTimer(change_text, text).start(2)
kaa.main()

When the text is changed (inside the change_text callback), the text object's size becomes wider (the string is longer than the previous one), so the object's position is recalculated so that the object remains centered.

Layout

kaa.canvas offers basic layout support, primarily in the form of relative values for position and size (for example, percentage values), as well as containers, and horizontal and vertical packing boxes. A Container object is simply a collection of objects that are logically grouped. When you move a container object, all children of the container move as well. When you hide a container, all children of the container become hidden. A container does not enforce a layout policy on its children, except inasmuch as the maximum size allowed for children is the container's own size. For example, if a container's size is fixed to 320x200 (that is, width of 320 pixels and a height of 200 pixels), and a child of that container specifies a width of 100%, the child's width will be computed as 320. Children of containers may overlap, and their stacking order is determined by the order they were added to the container, as well as their layer property.

Containers have a default width and height of auto, which means that the container "shrinkwraps" to fit its children. However, if a child requests a size of "100%" when its container's size is set to auto, the child is granted the full extent of its container's parent. For example, if a container of size auto x auto is added to a canvas of size 640x480, and an image of width "100%" is added to that container, the image will be 640 pixels wide, thus causing its parent's computed width to also be 640 pixels (assuming no other children of that container have a width greater than 640 pixels). Similarly, if the image is 50% wide, its computed width will be 320 pixels, and its container will be 320 pixels. This allows you to position containers relative to their size (such as centered, or bottom or right justified) and the container will be properly aligned as it grows and shrinks to fit its children.

Children are aligned relative to their parent container. So, if you have a container of size auto x auto that is parent to an image object and a text object with the properties hcenter="50%" and vcenter="50%", the text object will be centered on the image, assuming the image is larger than the text object. Here's an example that illustrates this:

import kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.add_child(kaa.canvas.Image("background.png"))

container = canvas.add_child(kaa.canvas.Container(), hcenter = "50%", vcenter = "50%")

frame = kaa.canvas.Image("frame.png")
frame.set_border(30, 30, 30, 30)
container.add_child(frame, width = "75%", height = "50%")

text = kaa.canvas.Text("Text inside a container.")
container.add_child(text, hcenter = "50%", vcenter = "50%")

kaa.main()

A Box is a subclass of Container and implements a layout policy for non-overlapping children. You'll never use Box directly, but rather one of VBox or HBox. VBox lays out its children in rows, whereas HBox lays out its children in columns. Because boxes are containers, they will also shrinkwrap their children unless a size is explicitly specified for the box. Children whose expand property is False will use only the minimum space in the box required for the child, whereas those with expand set to True will all available space in the Box. The expand property also modifies how relative sizes of children are interpreted. If a child specifies a width of 30% and has expand set to False, it will be given 30% of the box's total width. However, if expand is set to True, first all the space for other children that are not expanded is allocated, and the remaining space is distributed evenly for those children that are expanded. Thus, an expanded child that specifies a width of 30% will be given 30% of the available space, rather than of the box's total width.

For example, if an HBox has two children, the first child which requests a width of 30% and has expand set to False, and the second child which requests a width of 100% and has expand set to True, then this will result in a 2-column layout where the first column occupies 30% of the box, and the second column occupies 70% of the box. For such a layout, it would be preferable to explicitly specify 30% and 70% for non-expanded children, however the box layout semantics make the specifications equivalent. The following code illustrates this layout:

import kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))

box = canvas.add_child(kaa.canvas.HBox())
box.add_child(kaa.canvas.Rectangle(), width="30%", height="100%", color = "#ff0000")
box.add_child(kaa.canvas.Rectangle(), width="70%", height="100%", color = "#0000ff")

kaa.main()

Clipping

By default, objects are not clipped, and if they exceed the bounds of their container, the "spill-over" or overflow will still be visible. (A caveat to this is the Text object, which is clipped to its container by default.) For example, if you create a container with size 320x200 and then add an rectangle to that container that is 640x480, the whole rectangle will be visible, but if you center the container, it will be centered as if its size was 320x200. The best solution is to ensure that children of a container will not overflow its container's bounds by using relative sizes. However, if this is not possible, the object can be clipped by specifying its clip property.

Text objects are clipped by default. If clipping occurs, the last one or two characters of the text will be faded from opaque to transparent, rather than being abruptly clipped.

Objects can be clipped individually by specifying a clip region in the form (pos, size), or, in other words, ((left, top), (width, height)). The clip property for Container objects may also be auto, which is equivalent to ((0, 0), ("100%", "100%")), and will cause all children to be clipped to the container's bounds. Width and height may also be negative, in which case they are relative to the right or bottom edge of the object. The following example shows two fixed-width containers that are centered, one which is not clipped, and the other which is.

import kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.add_child(kaa.canvas.Image("background.png"))

c = canvas.add_container(width=100, height=100, hcenter="50%")
c.add_child(kaa.canvas.Rectangle(), width=200, height=200, color="#44ffff55")
c.add_child(kaa.canvas.Text("Text and rect will overflow"), clip = None)

c = canvas.add_container(width=100, height=100,  clip="auto", hcenter="50%", top=300)
c.add_child(kaa.canvas.Rectangle(), width=200, height=200, color="#ff44ff55")
c.add_child(kaa.canvas.Text("Text is clipped"))

kaa.main()

XML Syntax

Canvas objects can also be constructed using XML. Objects map to elements, and, for the most part, object properties map to element attributes. A canvas may be populated by calling the Canvas.from_xml method, or objects instantiated by calling kaa.canvas.xml.get_object_from_xml(). For example, the second Hello World example earlier can be rewritten in XML:

<canvas class="hello">
    <image file="background.png" />
    <text hcenter="50%" vcenter="50%">Hello world!</text>
</canvas>

And, if that XML were saved to a file called canvas.xml, this code would create a canvas object from the XML description:

import kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.from_xml("canvas.xml", classname = "hello")
kaa.main()

In the above example, the image and text elements refer to Image and Text objects respectively, and the hcenter, vcenter, and file properties all behave as outlined in the properties table. For properties where boolean values are required, the attribute value can be 0, false, or no to represent a False value, or 1, true, or yes to represent a True value. Colors are specified using the HTML-style notation #rrggbbaa (where 'aa' specifies alpha level and is optional). Otherwise, where tuples are expected, such as in the clip and border properties, a space-delimited sequence of values can be specified.

Canvas.from_xml and kaa.canvas.xml.get_object_from_xml both accept a classname parameter, and a path parameter which defines a list of paths on the filesystem. Where path names are specified in XML attributes (such as the file attribute for the image element), the current working directory is first checked, and then each path specified in the path list is checked for the filename specified.

Objects can be named using the name attribute, and subsequently retrieved using Canvas.find_object so that they may be modified programmatically. If an object's name begins with a dot (.), then its name is appended to its container's name. For example, if a vbox element has a name of box and a child has a name of .image, the image object can be gotten by calling canvas.find_object("box.image").

This example displays the time on the canvas and updates every second:

<canvas class="clock">
    <image file="background.png" />
    <text hcenter="50%" vcenter="50%" name="time">Current time goes here</text>
</canvas>
import time, kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.from_xml(xml, classname = "clock")

def update_clock(text):
    text.set_text(time.strftime("%I:%M:%S %p"))
kaa.notifier.Timer(update_clock, canvas.find_object("time")).start(1)

kaa.main()

The Movie Object

The canvas Movie object relies on kaa.player, and provides the same interface as any kaa.player object, except that the movie may be rendered as a canvas object. This state is referred to as attached. When the movie is detached, the video stops rendering to the canvas object and begins rendering to an external window. In the case of X11, this external window is a subwindow of the X11Canvas window and expands to fit this window. This allows seamless integration, switching between the canvas view and the movie view (using an Xv overlay, for example) in a single window.

The movie object may be manipulated like any other canvas object, including size, clip, and alpha level. This makes any manner of transition effects possible, for example, a movie that scales from a small, thumbnail view up to fill the whole canvas, just before it is detached.

This example creates a canvas and plays the movie specified on the command line, centered on the canvas and slightly transparent. The user can hit 'f' on the console to toggle between detached and attached mode.

import os, sys, kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.add_child(kaa.canvas.Image("background.png"))
canvas.add_child(kaa.canvas.Text("Now playing: " + os.path.split(sys.argv[1])[1]))
movie = kaa.canvas.Movie(sys.argv[1])
canvas.add_child(movie, width="70%", aspect="preserve", hcenter="50%", vcenter="50%")
movie.set_color(a = 100)
movie.play()

def handle_key(key, movie):
    if key == "q":
        raise SystemExit
    elif key == "f":
        movie.set_detached(not movie.get_detached())

kaa.signals["stdin_key_press_event"].connect(handle_key, movie)
kaa.main()

If the player supports it, Movie instances will have an osd member, which is a PlayerOSDCanvas instance. This behaves like any other canvas, and allows rendering an overlay to the video when it is in detached mode.

The TextBlock Object

canvas.TextBlock allows for complex, styled, multi-line layouts that can be specified using an XML syntax. Text may be given various effects (such as underlined, outline, shadow, glow), aligned, and adjusted via margins. The markup may also act as a template, where template variables are specified within the markup text in the form @variable. Variables may be defined by calling TextBlock.set_template_variable.

Here's an example:

import time, kaa, kaa.canvas

canvas = kaa.canvas.X11Canvas((640, 480))
canvas.add_child(kaa.canvas.Image("royale/background.png"))
text = canvas.add_child(kaa.canvas.TextBlock(), width = "50%", hcenter = "50%", 
                        vcenter = "50%)
text.set_markup("""
    <format align="right">
        <format style="far_shadow" shadow_color="#0004" 
                font="tahoma" font_size="90" color="#fff5">
            mebox
        </format><br />
        <format font="trebucbd" font_size="16">
            <format color="#8cbaf7">@date@</format><br />
            @time@
        </format>
    </format>
""")

text.set_template_variable(date = time.strftime("%A, %B %d"),
                           time = time.strftime("%I:%M:%S %p"))
kaa.main()

Powered by His Noodly Appendage.