2D on a screen: GUI canvases#

Everything so far drew to a file. This chapter draws to a window: an interactive canvas that repaints, responds to the mouse and keyboard, and can animate. Every GUI toolkit works the same way in outline: you create a window, you give it a paint handler that draws its contents, and the toolkit calls that handler whenever the window needs redrawing. Drawing inside the handler uses primitives that are, by now, familiar: lines, ellipses, polygons, fills.

The Perl GUI landscape has a clear best option for graphics and several others with tradeoffs. This chapter leads with Prima, because it is actively maintained, self-contained, and has a real graphics core; then covers Tk and Gtk3 for when you have a reason to prefer them; and is honest about Wx, which is no longer a healthy choice.

Prima: the self-contained option#

Prima is a GUI toolkit written for Perl with its own graphics engine. It does not wrap an external toolkit you have to install and match versions with; it draws natively (on X11 on Linux) and ships its own primitives, image handling, and fonts. It is maintained, and it is the recommendation for new graphics-heavy GUI work in Perl.

A minimal window with a paint handler:

use Prima qw(Application);

my $win = Prima::MainWindow->new(
    text    => 'Prima demo',
    size    => [400, 300],
    onPaint => sub {
        my ($self, $canvas) = @_;        # the handler gets a canvas
        $canvas->color(cl::White);
        $canvas->bar(0, 0, $canvas->size);   # bar is a filled rectangle
        $canvas->color(cl::Red);
        $canvas->fill_ellipse(200, 150, 120, 90);
        $canvas->color(cl::Black);
        $canvas->line(0, 0, 400, 300);
    },
);

Prima->run;

Two naming notes that catch newcomers. Prima’s filled rectangle is bar; plain rectangle draws the outline. The polygon fill is fillpoly (one word), taking an array reference of coordinates. Colors come from the cl:: constants (cl::Red, cl::White, and so on) or a packed RGB integer, and are set as the drawing state with color before you draw, rather than passed to each primitive.

The Prima::Drawable primitives are the full set you expect: line, ellipse and fill_ellipse, arc, sector and fill_sector, polyline, fillpoly, bar, rectangle, flood_fill, and put_image for blitting a bitmap. Drawing state includes color, backColor, lineWidth, and fill patterns.

Offscreen drawing and saving#

The same primitives work on a Prima::Image, an offscreen surface, so you can draw without a window and save the result. This is how a Prima program exports what it drew:

my $img = Prima::Image->new(width => 200, height => 200, type => im::RGB);
$img->begin_paint;
$img->color(cl::Blue);
$img->fill_ellipse(100, 100, 80, 80);
$img->end_paint;
$img->save('out.png');

begin_paint and end_paint bracket drawing onto the offscreen image, and save writes it in a format inferred from the extension. This dual nature (same drawing code for the screen and for a file) is why the capstone chapters build on Prima.

Tk: the classic Canvas#

Tk (Perl/Tk) is the long-standing option, and its Canvas widget has a different model worth knowing: it is retained-mode. Instead of a paint handler that redraws everything, you create canvas items (a line, an oval, a polygon) that the widget remembers and redraws for you, and you move or reconfigure them by their id. This suits diagrams and editors where objects persist and get manipulated.

use Tk;

my $mw = MainWindow->new;
my $canvas = $mw->Canvas(-width => 400, -height => 300)->pack;

$canvas->createLine(0, 0, 400, 300, -fill => 'blue', -width => 2);
my $oval = $canvas->createOval(160, 100, 240, 180, -fill => 'red');
$canvas->createPolygon(50, 10, 150, 10, 100, 90, -fill => 'green');

# move the remembered oval later, by id
$canvas->move($oval, 20, 0);

MainLoop;

Tk is stable and works, though it is in maintenance rather than active development and carries a few inherited CVEs in its bundled Tcl/libpng. For a persistent-object diagram or a quick classic GUI it is a reasonable pick; for a redraw-every-frame animation the immediate-mode Prima model fits better.

Gtk3: drawing through Cairo#

Gtk3 binds GTK 3 through GObject introspection. Its drawing story is that a Gtk3::DrawingArea widget emits a draw signal handed a Cairo context, so you draw with the Cairo API from the vector chapter inside the handler. If your application is already a GTK application, this is the natural way to add custom graphics; if it is not, pulling in the whole GTK stack (the GTK 3 development libraries and GObject introspection data) is a large dependency for graphics alone.

use Gtk3 -init;

my $window = Gtk3::Window->new('toplevel');
my $area   = Gtk3::DrawingArea->new;
$window->add($area);
$window->set_default_size(400, 300);

$area->signal_connect(draw => sub {
    my ($widget, $cr) = @_;              # $cr is a Cairo context
    $cr->set_source_rgb(0.2, 0.4, 0.9);
    $cr->arc(200, 150, 80, 0, 6.28318);
    $cr->fill;
    return 0;
});

$window->show_all;
Gtk3->main;

Everything you know about Cairo applies unchanged inside that handler. Gtk3 is usable but in slow maintenance, and the dependency weight means it earns its place mainly when GTK is already in the picture.

Wx: mention, not recommendation#

You will find Wx (wxPerl) referenced as the way to build native-looking cross-platform GUIs in Perl, and historically it was. It is included here so its current state is clear: Wx and its Alien::wxWidgets build dependency have not had a release in years, and a large fraction of automated build reports fail against current wxWidgets. It is not a choice for new work. If you need a GUI with custom drawing today, Prima is the maintained self-contained option and Tk the maintained classic; reach for either of those instead.

Choosing#

You want …

Reach for

Maintained, self-contained, graphics-heavy new GUI

Prima

A retained-mode canvas of persistent objects

Tk

Custom drawing inside an existing GTK application

Gtk3

Per-frame animation and a realtime loop

Prima

With a window and a paint handler in hand, the toolkit arc is complete. The next arc goes further than any single CPAN module: it uses pperl’s native FFI to reach C graphics libraries directly, and then spends everything in the guide on two projects you can run, both built on the Prima foundation from this chapter.