10.2. Gtk2::Gdk - Bouncing Ball

Although there are dedicated toolkits for writing game interfaces, Gtk+ can also be used for it. Gtk+ allows you to pull a few tricks out the sleeve with regards to window shapes and movement. This lesson will utilize shape and movement to generate a simple bouncing ball animation.

Table 10-2. Gtk2 object classes used

Gtk2 Class Name
Gtk2::Window
Gtk2::EventBox
Gtk2::Image

Table 10-3. Gtk2::Gdk object classes used

Gtk2::Gdk Class Name
Gtk2::Gdk::Pixmap
Gtk2::Gdk::Window
Gtk2::Gdk::Screen
Gtk2::Gdk::Event

Table 10-4. Special Class used

Gtk2 Class Name
Glib::MainLoop

10.2.1. General discussion on Gtk2::Gdk::Pixmap.

Pixmaps are data structures that contain pictures. These pictures can be used in various places, but most commonly as icons on the X desktop, or as cursors.

Note

This is not the only use for Gtk2::Gdk::Pixmap. It is a subclass of Gtk2::Gdk::Drawable, which is like a primitive canvas, that can be used to draw lines, text, Images and more upon. The next program that we discuss, we will use a Gtk2::Gdk::Pixmap to draw text upon.

A pixmap which only has 2 colors is called a bitmap, and there are a few additional routines for handling this common special case.

To understand pixmaps, it would help to understand how the X window system works. Under X, applications do not need to be running on the same computer that is interacting with the user. Instead, the various applications, called "clients", all communicate with a program which displays the graphics and handles the keyboard and mouse. This program which interacts directly with the user is called a "display server" or "X server." Since the communication might take place over a network, it's important to keep some information with the X server. Pixmaps, for example, are stored in the memory of the X server. This means that once pixmap values are set, they don't need to keep getting transmitted over the network; instead a command is sent to "display pixmap number XYZ here." Even if you aren't using X with Gtk+ currently, using constructs such as Pixmaps will make your programs work acceptably under X.

Data to create the Gtk2::Gdk::Pixmap

Although there may be more fancy ways (including Gtk2::Gdk::Drawable's draw functions) to get something to display on the Gtk2::Gdk::Pixmap, the ones we will discuss here are using data in the XPM format.

XPM format is a readable pixmap representation for the X Window System. It is widely used and many different utilities are available for creating image files in this format. It has, just like GIF format, also a "Alpha Channel" that is used to create shaped images. Think about the shapes of cursors!

Tip

It is assumed the reader heard about "The GIMP" (The program for which this toolkit was written originally!). This program was used to create and save the ball images used in the sample program. Just take any normal image, and select "Filters->Map->Map Object".Now you can select to create a sphere from your image, and you can save it as a XPM file.

XPM data can be fetched from files, or it can be created from an inline data structure. To create it inline, you have to be familiar with the data structure of the XPM format. This is outside the scope of the book, and the reader is advised to look it up via any search engine on the Internet.

Tip

To learn more about the format of "inline" XPM data in Gtk2-Perl please visit here for more info.

To create a Gtk2::Gdk::Pixmap from inline XPM data, you use the following method:

($pixmap, $mask) = Gtk2::Gdk::Pixmap->create_from_xpm_d ($drawable, $transparent_color, @xpm_data)
	
You will notice that this method creates a pixmap AND a mask. The mask is a bitmap, that will be used when we need to display the pixmap image. It specifies which bits of the pixmap are opaque. The $drawable is any realized Gtk2::Gdk::Window. $transparent_color usually is undef, which will use the default transparent color. You then supply it the array of strings which is a presentation of the XPM image.

To create a Gtk2::Gdk::Pixmap from a file, you use the following method:

(pixmap, mask) = Gtk2::Gdk::Pixmap->create_from_xpm ($drawable, $transparent_color, $filename) 
	
You will notice that this method creates a pixmap AND a mask. The mask is a bitmap, that will be used when we need to display the pixmap image. It specifies which bits of pixmap are opaque. The $drawable is any realized Gtk2::Gdk::Window.$transparent_color usually is undef, which will use the default transparent color. You then supply it the name of the XPM file.

Caution

A Gtk2::Gdk::Pixmap is an off- screen drawable. To display a Gtk2::Gdk::Pixmap, you ask the server to copy the Gtk2::Gdk::Pixmap's pixels to a visible Gtk2::Gdk::Window. This will typically be the visible Gtk2::Gdk::Window of a Gtk2::Image or Gtk2::DrawingArea. When using Gtk2::Image, it does this for you behind the scenes. If you use Gtk2::DrawingArea, you have to do that by hand, usually in an expose-event handler.

Now we know more about Gtk2::Gdk::Pixmap, we can have a look at how we implement it along with other methods to create the bouncing ball animation.

10.2.2. Creating the animation.

The program will do the following:

10.2.3. The program's interface.

Figure 10-1. Bouncing Ball

10.2.4. The Code.

The program can be found here: 'Bouncing Ball'

	
  1 #! /usr/bin/perl -w
  2 use strict;
  3 use Gtk2 -init;
  4 use Glib qw/TRUE FALSE/;
  5 
  6 #array pointers to indicate the displayed file
  7 my $current_file = 0;
  8 my $current_bounce = 0;
  9 my $current_side_bounce = 0;
 10 #direction indicators for the stepping sub
 11 my $up_flag = FALSE;
 12 my $left_flag = FALSE;
 13 #indicate if it is currently bouncing up/down
 14 my $bounce_flag =FALSE;
 15 #indicate if it is currently bouncing sideways
 16 my $side_bounce_flag =FALSE;
 17 
 18 #Initial position horizontal and vertical
 19 my $x =5;
 20 my $y=5;
 21 #flag to indicate if the image changing and movement stepping
 22 #should be stopped
 23 my $freeze_flag = FALSE;
 24 #step size
 25 my $step_x = 10;
 26 my $step_y = 10;
 27 
 28 #Pre Widget prep START --------
 29 
 30 #list of files to show if not bouncing
 31 my @file_list = qw/t1.xpm t2.xpm t3.xpm t4.xpm t5.xpm t6.xpm t7.xpm t8.xpm t9.xpm/;
 32  my $list_count = scalar @file_list;
 33 
 34 #list of files to show for top and bottom bouncing
 35 my @bounce_top_bottom = qw/t1.xpm b1.xpm b2.xpm b3.xpm b2.xpm b1.xpm t1.xpm/;
 36  my $bounce_top_bottom_count = scalar @bounce_top_bottom;
 37  
 38 #list of files to show for side bouncing
 39 my @bounce_sides = qw/t3.xpm s1.xpm s2.xpm s3.xpm s2.xpm s1.xpm t3.xpm/;
 40  my $bounce_sides_count = scalar @bounce_sides;
 41 
 42 #Pre Widget prep END --------
 43 #timeout that will repeat.
 44 Glib::Timeout->add (200,sub{ &change() });
 45 
 46 #popup windows will create shaped windows.
 47  my $window = Gtk2::Window->new ('popup');
 48  $window->resize(4,4);
 49  
 50  	#create an event box that will house the pixmaps
 51   	my $eventbox = Gtk2::EventBox->new ();
 52   	#set the freeze flag when we enter or leave the eventbox
 53   	$eventbox->signal_connect('enter-notify-event' => sub { $freeze_flag = TRUE});
 54  	$eventbox->signal_connect('leave-notify-event' => sub { $freeze_flag = FALSE});
 55 
 56 		my $img = Gtk2::Image->new();
 57 
 58 	$eventbox->add($img);
 59 $window->add ($eventbox);
 60 
 61 #discover which size of screen we have
 62 my $screen = $window->get_screen();
 63 my $screen_width = $screen->get_width();
 64 my $screen_height = $screen->get_height();
 65 
 66 $window->show_all;
 67 
 68 	#first run
 69 	&change();
 70 Gtk2->main;
 71 
 72 sub change {
 73 
 74 	#IF the freese flag is set, put a message for the user, and return	
 75 	if ($freeze_flag) {
 76 		my($pxm_current, $msk_current) = Gtk2::Gdk::Pixmap->create_from_xpm ($window->window,undef, "./pix/stop.xpm");
 77 		$img->set_from_pixmap ($pxm_current, $msk_current);
 78 		$window->window->shape_combine_mask($msk_current, 0, 0);	
 79 	return TRUE;
 80 	
 81 	} 
 82 	#----------------------------------
 83 	
 84 	my ($w_w,$w_h) = $window->get_size;
 85 	
 86 	#we test for the following"
 87 	#	1.) if x values are to far, if YES, we bounce 
 88 	# 	sides and reverse the direction
 89 	#	2.) if y values are to far, if YES, we bounce
 90 	# 	top_bottom and reverse the direction 
 91 	
 92 	#test x:
 93 	my $max_x = $screen_width - $w_w;
 94 	if($x > $max_x){
 95 		$left_flag = TRUE;
 96 		$side_bounce_flag = TRUE;
 97 	}
 98 	
 99 	if ($x < 0){
100 		$left_flag = FALSE;
101 		$side_bounce_flag = TRUE;
102 	}		
103 	
104 	#test y:
105 	my $max_y = $screen_height - $w_h;
106 	if ($y > $max_y){
107 		$up_flag = TRUE;
108 		$bounce_flag = TRUE;
109 	}
110 	
111 	if ($y < 0){
112 		$up_flag = FALSE;
113 		$bounce_flag = TRUE;
114 	}
115 	
116 	#this will shrink the window prior to changing the image
117 	$window->resize(4,4);
118 	#move the window to the new position
119 	$window->move($x,$y);
120 	
121 	
122 	if ((!($bounce_flag))&&(!($side_bounce_flag))){
123 	
124 		&do_steps();
125 		&do_motions();
126 	}
127 	
128 	($side_bounce_flag)	&&(&bounce_side);
129 	($bounce_flag)		&&(&bounce_top_bottom);
130 	return TRUE;
131  
132 }
133 
134 sub bounce_top_bottom {
135 	#working through the top/bottom bounce list of images
136 	my $file_string = "./pix/".$bounce_top_bottom[$current_bounce];
137 	$current_bounce ++;
138 	
139 	my($pxm_current, $msk_current) = Gtk2::Gdk::Pixmap->create_from_xpm ($window->window,undef, "$file_string");
140 	$img->set_from_pixmap ($pxm_current, $msk_current);
141 	$window->window->shape_combine_mask($msk_current, 0, 0);
142 	
143 	if($current_bounce == $bounce_top_bottom_count -1){
144 	#if we got to the end of the list, we have to reset a few variables.
145 	#and do the step thing to continue moving
146 		$bounce_flag = FALSE;
147 		$current_bounce = FALSE;
148 		&do_steps();
149 		
150 	}	
151 		
152 }
153 
154 sub bounce_side {
155 	#working through the side bounce list of images
156 	my $file_string = "./pix/".$bounce_sides[$current_side_bounce];
157 	$current_side_bounce ++;
158 	
159 	my($pxm_current, $msk_current) = Gtk2::Gdk::Pixmap->create_from_xpm ($window->window,undef, "$file_string");
160 	$img->set_from_pixmap ($pxm_current, $msk_current);
161 	$window->window->shape_combine_mask($msk_current, 0, 0);
162 	
163 	if($current_side_bounce == $bounce_sides_count -1){
164 	#if we got to the end of the list, we have to reset a few variables.
165 	#and do the step thing to continue moving
166 		$side_bounce_flag = FALSE;
167 		$current_side_bounce = FALSE;
168 		&do_steps();
169 		
170 	}	
171 	
172 }
173 
174 sub do_steps {
175 
176 	#depending on the $up_flag we will step up or down
177 	if ($up_flag){
178 		$y = $y - $step_y;
179 	}else{
180 		$y = $y + $step_y;
181 	}
182 
183 	#depending on the $left_flag we will step to the left or right
184 	if ($left_flag){
185 		$x = $x - $step_x;
186 		
187 	}else{
188 		$x = $x + $step_x;
189 		
190 	}
191 
192 }
193 
194 sub do_motions {
195 	#when we are not bouncing, we are in motion, this gives the illusion
196 	my $file_string = "./pix/".$file_list[$current_file];
197 		$current_file ++;
198 		
199 	($current_file == $list_count)&&($current_file = 0);
200 		
201 	my($pxm_current, $msk_current) = Gtk2::Gdk::Pixmap->create_from_xpm ($window->window,undef, "$file_string");
202 	$img->set_from_pixmap ($pxm_current, $msk_current);
203 	$window->window->shape_combine_mask($msk_current, 0, 0);
204 
205 }

10.2.5. Discussing the code.

This section will discuss bits and pieces of the code above.

  1. To get the size of the screen where the program is displayed we get the Gtk2::Gdk::Screen of the Gtk2::Window. This will then be used to get the width and height of the screen.

    #discover which size of screen we have
    	my $screen = $window->get_screen();
    	my $screen_width = $screen->get_width();
    	my $screen_height = $screen->get_height();
    		

  2. To get the Gtk2::Gdk::Pixmap on the Gtk2::Image widget, we use the following method.

    $img->set_from_pixmap ($pxm_current, $msk_current);
    		

  3. To shape the window to the shape of the widget, we use the following method. (Please remember that the window itself has to be of type popup. This type of window does not have any borders etc.)

    $window->window->shape_combine_mask($msk_current, 0, 0);
    		
    This does not need to be just a window, but can be any widget that has a realized Gtk2::Gdk::Window. You can then "shrink" the window around the whole lot by using Gtk2::Gdk::Window's $window->set_child_shapes method.

  4. To set the position of the window, we use it's move method.

    $window->move($x,$y);
    		

  5. We resize the window to a size smaller than the size of the Gtk2::Image in order to get it to a minimum size as soon as we change the image. This will ensure the mask and the image are on the same spot on the screen.

    $window->resize(4,4);
    		

  6. To discover if the window is near the edge of the screen, we have to keep track of the size of the displayed window, and add it to the current position of the window. Once we have that values, we can do our tests to see if it reached the edge of the screen.

    my ($w_w,$w_h) = $window->get_size;
    		

  7. To react when the mouse enters the displayed area we use a Gtk2::EventBox. This is because Gtk2::Image (just like Gtk2::Label) do not have an associated Gtk2::Gdk::Window. We therefore put the Gtk2::Image inside the Gtk2::EventBox to catch signals on the Gtk2::Image.

    my $eventbox = Gtk2::EventBox->new ();
    #set the freeze flag when we enter or leave the eventbox
    $eventbox->signal_connect('enter-notify-event' => sub { $freeze_flag = TRUE});
    $eventbox->signal_connect('leave-notify-event' => sub { $freeze_flag = FALSE});
    	my $img = Gtk2::Image->new();
    $eventbox->add($img);
    		

The rest of the code is straight forward Perl and should be easy to follow, since it is heavily commented.