Gtk2::Gdk
- Bouncing BallAlthough 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-3. Gtk2::Gdk
object classes used
Gtk2::Gdk Class Name |
---|
Gtk2::Gdk::Pixmap |
Gtk2::Gdk::Window |
Gtk2::Gdk::Screen |
Gtk2::Gdk::Event |
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.
This is not the only use for |
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.
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!
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. |
To learn more about the format of "inline" XPM data in |
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.
A |
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.
The program will do the following:
Move in steps (horizontal and vertical) across the screen.
With every step, change the image displayed, to give the impression of motion.
As soon as it reached the end of the screen, be it the top,bottom or sides, it must "bounce" and change direction after the bounce.
Whenever the mouse enters the displayed window, it should change to a predefined image, and stop any stepping and image changing.
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 }
This section will discuss bits and pieces of the code above.
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();
To get the Gtk2::Gdk::Pixmap
on the Gtk2::Image
widget, we use the following method.
$img->set_from_pixmap ($pxm_current, $msk_current);
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.
To set the position of the window, we use it's move
method.
$window->move($x,$y);
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);
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;
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);