A ``Hello world!'' program.

One of the simplest PRIMA programs looks like this:

     1  #! /usr/bin/perl -w
     2
     3  use Prima qw(Buttons);
     4  use Prima::Application name => 'Hello1';
     5
     6  my $w = Prima::Window-> create(
           text => 'Hello, world!',
     7     onClose => sub { $application-> destroy },
     8  );
     9
    10  $w-> insert( Button => 
    11     text => 'E~xit',
    12     onClick => sub {
    13        $w-> close;
    14     },
    15     growMode => gm::Center,
    16  );
    17
    18  run Prima;

While running, it creates a top-level window of an unspecified size, carrying ``Hello, world!'' on its title bar. In the center of the window there is a button titled ``Exit'' which does exactly that when pushed. The window can be resized, if your WM supports this. The button still remains in the window center.

The program is mostly self-explanatory. The statement in line 3 is mandatory: PRIMA core initializes itself during ``use Prima''. The basic classes are always loaded by this statement. One should ask Prima module to load the rest, like buttons support in this example. This is just a convenient shortcut to the explicit ``use Prima::Buttons;'' statement.

The next statement creates the parent object of all PRIMA widgets, and assigns a name 'Hello1' to it. The lowercased version of this name is used as an instance name for the X resource manager. The application instance itself is stored in the global variable $::application. This is the only global variable used by PRIMA core.

The lines from 6 to 8 create a top-level window. The constructor method for all PRIMA objects is called create(). It takes a list of property name/property value pairs as its parameters. In this case only two properties are set. The second one is an event handler for ``close'' notification, which is sent to the top-level window by a WM when the ``close'' button on the window frame is pushed.

There are more than 90 properties which can be set for a generic widget, and every class derived from a Prima::Widget adds more properties of its own. Normally it is not necessary to specify values for most of them, since they all have acceptable defaults.

The lines 10-16 add one button to the window. The insert() method is merely a wrapper for one or more create() constructors. In this case it is roughly equivalent to

        Prima::Button-> create( 
           owner => $w, 
           text => 'E~xit', ...
        );

Not only a top-level window can be an owner (parent) of a widget. In fact, any widget can own almost any other widget, allowing an arbitrary nesting level.

We'd like to draw your attention to the property growMode. Its concept was borrowed from Inprise's (former Borland) text-mode toolkit TurboVision, and roughly corresponds to X's gravity. Here the value gm::Center tells PRIMA to maintain the button centered relatively to its owner. gm::Center is a constant in a ``gm'' namespace. There are lots of similarly named constants in PRIMA, for example cl::Red and kb::Escape. Such naming convention contradicts perlstyle(1) on two points, but nevertheless is very convenient.

The class method Prima::run enters an event loop.

So far, so good. Let's extend our example with some nifty graphics:

     1  #! /usr/bin/perl -w
     2  
     3  use Prima qw(Buttons);
     4  use constant PI => 4.0 * atan2 1, 1;
     5  use constant D2R => PI / 180;
     6  use constant Cos_120 => cos(D2R*(-120));
     7  use constant Sin_120 => sin(D2R*(-120));
     8   
     9  use Prima::Application name => 'Hello2';
    10  
    11  my $w = Prima::Window-> create(
           text => 'Hello, world!',
    12     onClose => sub { $application-> destroy },
    13     onPaint => \&painter,
    14     lineWidth => 3,
    15     color => cl::Red,
    16  );
    17  
    18  $w-> insert( Button => 
    19     text => 'E~xit',
    20     onClick => sub {
    21        $w-> close;
    22     },
    23     growMode => gm::Center,
    24  );
    25  
    26  run Prima;
    27  
    28  sub painter
    29  {
    30     my ($me,$canvas) = @_;
    31     $me-> {angle} = 0 
              unless exists $me-> {angle};
    32     my @l = ();
    33     my ($w, $h) = $canvas-> size;
    34     my $sz = ($w > $h ? $h : $w) * 0.8 * 0.5;
    35     my ($x, $y) = (25*$sz/34,$sz);
    36     my ($fx, $fy) = ($x,$y);
    37     ($w,$h) = ($w/2,$h/2);
    38     my $angle = $me-> {angle};
    39     for (1..3)
    40     {
    41        my $cos = cos(D2R*$angle);
    42        my $sin = sin(D2R*$angle);
    43        my @lines;
    44        ($x,$y) = ($fx, $fy);
    45        push @lines, $cos*$x-$sin*$y+$w, 
                           $sin*$x+$cos*$y+$h;
    46        $y = -$y;
    47        push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    48        $x = Cos_120*$fx - Sin_120*$fy;
    49        $y = Sin_120*$fx + Cos_120*$fy;
    50        push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    51        $y = -$y/2;
    52        push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    53        ($x, $y) = ($x-sqrt(3)*$y,0);
    54        push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    55        push @l, \@lines;
    56        $angle += 120;
    57     }
    58     my $fore = $me-> color;
    59     $canvas-> color( $me-> backColor);
    60     $canvas-> bar( 0, 0, $me-> get_size);
    61     $canvas-> color( $fore);
    62     $canvas-> polyline( shift @l);
    63     $canvas-> polyline( shift @l);
    64     $canvas-> polyline( shift @l);
    65  }

This program draws the famous M. C. Escher's impossible triangle. Comparing to the ``Hello, world!'' program, the lines 4-8, 13-15 and 27-65 were added, and the line 9 was modified. There are few things to note in this program:

All right. Let's make our little program a bit more dynamic.

     1	#! /usr/bin/perl -w
     2	
     3	use Prima qw(Buttons);
     4	use constant PI => 4.0 * atan2 1, 1;
     5	use constant D2R => PI / 180;
     6	use constant Cos_120 => cos(D2R*(-120));
     7	use constant Sin_120 => sin(D2R*(-120));
     8	 
     9 	use Prima::Application name => 'Hello3';
    10	
    11	my $w = Prima::Window-> create(
           text => 'Hello, world!',
    12	   onClose => sub { $application-> destroy },
    13	   onPaint => \&painter,
    14	   lineWidth => 3,
    15	   color => cl::Red,
    16  );
    17	
    18	$w-> insert( Button => 
    19     text => 'Exit',
    20	   onClick => sub {
    21	      $w-> close;
    22	   },
    23	   growMode => gm::Center,
    24	);
    25 	
    26 	$w-> {angle} = 0;
    27 	
    28 	$w-> insert( Timer => timeout => 50,
    29 	             onTick => sub {
    30 	                          $w-> {angle} -= 5;
    31 	                          $w-> repaint;
    32 	                       },
    33 	           )-> start;
    34	
    35	run Prima;
    36	
    37	sub painter
    38	{
    39	   my ($me,$canvas) = @_;
    40	   $me-> {angle} = 0
              unless exists $me-> {angle};
    41	   my @l = ();
    42	   my ($w, $h) = $canvas-> size;
    43	   my $sz = ($w > $h ? $h : $w) * 0.8 * 0.5;
    44	   my ($x, $y) = (25*$sz/34,$sz);
    45	   my ($fx, $fy) = ($x,$y);
    46	   ($w,$h) = ($w/2,$h/2);
    47	   my $angle = $me-> {angle};
    48	   for (1..3)
    49	   {
    50	      my $cos = cos(D2R*$angle);
    51	      my $sin = sin(D2R*$angle);
    52	      my @lines;
    53	      ($x,$y) = ($fx, $fy);
    54	      push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    55	      $y = -$y;
    56	      push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    57	      $x = Cos_120*$fx - Sin_120*$fy;
    58	      $y = Sin_120*$fx + Cos_120*$fy;
    59	      push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    60	      $y = -$y/2;
    61	      push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    62	      ($x, $y) = ($x-sqrt(3)*$y,0);
    63	      push @lines, $cos*$x-$sin*$y+$w,
                           $sin*$x+$cos*$y+$h;
    64	      push @l, \@lines;
    65	      $angle += 120;
    66	   }
    67	   my $fore = $me-> color;
    68	   $canvas-> color( $me-> backColor);
    69	   $canvas-> bar( 0, 0, $me-> get_size);
    70	   $canvas-> color( $fore);
    71	   $canvas-> polyline( shift @l);
    72	   $canvas-> polyline( shift @l);
    73	   $canvas-> polyline( shift @l);
    74	}

Comparing with the previous variant of the program, the line 9 was modified, and lines 25-33 were added.

This example shows that PRIMA has more than pure interface classes. We use here an instance of a Prima::Timer class to animate Escher's triangle. The timeout property specifies the timeout in milliseconds. The insert() method returns the reference to an object created, so we use that to start the timer. The Prima::Widget's repaint() method forces a widget to update itself. One of the stages in triangle's rotation is shown in fig. 1.


  
Figure 1: Screenshot of rotating Escher's triangle.
\includegraphics{escher.ps}