gtk5.tex 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. \documentclass[10pt]{article}
  2. \usepackage{a4}
  3. \usepackage{epsfig}
  4. \usepackage{listings}
  5. \usepackage{tabularx}
  6. \lstset{language=Delphi}%
  7. \lstset{basicstyle=\sffamily\small}%
  8. \lstset{commentstyle=\itshape}%
  9. \lstset{keywordstyle=\bfseries}%
  10. %\lstset{blankstring=true}%
  11. \newcommand{\file}[1]{\textsf{#1}}
  12. \newcommand{\var}[1]{\texttt{#1}}
  13. \usepackage[pdftex]{hyperref}
  14. \newif\ifpdf
  15. \ifx\pdfoutput\undefined
  16. \pdffalse
  17. \else
  18. \pdfoutput=1
  19. \pdftrue
  20. \fi
  21. \begin{document}
  22. \title{Programming GTK in Free Pascal: Using GDK}
  23. \author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
  24. \date{July 2001}
  25. \maketitle
  26. \section{Introduction}
  27. In this article, some of the graphics primitives from the gdk toolkit will
  28. be demonstrated in a small game - breakout.
  29. The GTK toolkit widgets are built upon the GDK: Graphics Drawing Kit.
  30. The GDK does not know anything about buttons, menus checkboxes and so on.
  31. Instead, it knows how to create windows, draw on them, handle mouse clicks
  32. and keypresses. This functionality is used by the GTK widget set to create
  33. usable widgets.
  34. Sometimes, the widgets offered by GTK are not enough, and one has to fall
  35. back on the graphics functionality of the GDK to be able to do what is
  36. needed for a program.
  37. Fortunately, it is not necessary to create a GTK window and handle all
  38. GDK events to be able to use the GDK functions. The GTK widget set has a
  39. special widget, which can be used to draw upon. This widget is the
  40. \var{TGtkDrawingArea} widget. The use of the \var{TGtkDrawingArea} is what
  41. will be explained below.
  42. The GDK graphics functions will be explained using a simple arcade game,
  43. to demonstrate that the speed of the GDK is sufficient for the creation of
  44. simple games. The breakout game is chosen because it is conceptually simple,
  45. requires moving graphics and can be extended in many ways.
  46. \section{The drawing area widget}
  47. The drawing area widget (\var{TGTKDrawingArea}) is a simple widget which
  48. just provides a drawing window. It responds to all widget events, and adds
  49. additionally the 'configure\_event', which is called when the widget is
  50. realized (i.e. when the window handle is created.)
  51. The widget has only 1 method: \var{gtk\_drawing\_area\_size}, which sets
  52. the size of the drawing area. It is defined as follows:
  53. \begin{verbatim}
  54. procedure gtk_drawing_area_size(Area:PGtkDrawingArea; width:gint;height:gint)
  55. \end{verbatim}
  56. The arguments to this function are self-explaining.
  57. To use the drawing area widget, one should respond to the 'expose\_event'.
  58. This event is triggered whenever a part of the window that was invisible,
  59. becomes visible. The event handler gets an \var{PGDKEventExpose} parameter,
  60. which describes which area was exposed. This can be used for optimization
  61. purposes.
  62. To draw in the drawing area widget, the \var{Window} field of the
  63. \var{TGTKWidget} parent can be used. This is of type \var{TGDKWindow}.
  64. All drawing functions require a parameter of type \var{TGdkDrawable}
  65. which can be one of the \var{TGdkWindow} or \var{TGdkPixMap} types.
  66. \section{Graphics contexts}
  67. Most drawing functions do not only require a drawable to draw on, they also
  68. require a {\em Graphics Context}. A graphics context is a series of
  69. parameters that determine how lines are drawn, what colors and font are
  70. used etc.
  71. The Graphics Context is an opaque record, and its members cannot be
  72. accessed. The relevant parameters are set in a \var{TGdkGCValues} record,
  73. which is defined as follows:
  74. \begin{verbatim}
  75. foreground : TGdkColor;
  76. background : TGdkColor;
  77. font : PGdkFont;
  78. thefunction : TGdkfunction;
  79. fill : TGdkFill;
  80. tile : PGdkPixmap;
  81. stipple : PGdkPixmap;
  82. clip_mask : PGdkPixmap;
  83. subwindow_mode : TGdkSubwindowMode;
  84. ts_x_origin : gint;
  85. ts_y_origin : gint;
  86. clip_x_origin : gint;
  87. clip_y_origin : gint;
  88. graphics_exposures : gint;
  89. line_width : gint;
  90. line_style : TGdkLineStyle;
  91. cap_style : TGdkCapStyle;
  92. join_style : TGdkJoinStyle;
  93. \end{verbatim}
  94. The \var{ForeGround} and \var{Background} parameters determine the foreground
  95. and background colors. \var{Font} is the default font. The \var{Fill} field
  96. describes how areas are filled. It can be one of the following:
  97. \begin{description}
  98. \item[GDK\_SOLID] fill with the foreground color.
  99. \item[GDK\_TILED] Use the pixmap specified in \var{Tile} to fill the area.
  100. \item[GDK\_STIPPLED] Use the pixmap specified in \var{Stipple} to draw
  101. pixels that are in the bitmap in the foreground color. Other bits are not
  102. drawn.
  103. \item[GDK\_OPAQUE\_STIPPLED] Same as \var{GDK\_STIPPLED} except that bits
  104. not in the pixmap will be drawn in the background color.
  105. \end{description}
  106. The \var{clip\_bitmap} is used to define a clip area. The
  107. \var{ts\_x\_origin} and \var{ts\_y\_origin} define the stipple or tile
  108. origin. The \var{clip\_x\_origin} and \var{clip\_y\_origin} fields define
  109. the origin of the clipping region.
  110. \var{LineWidth} is the linewidth used when drawing lines. \var{Line\_Style}
  111. determines how dashed lines are drawn. It can have one of the following
  112. values:
  113. \begin{description}
  114. \item[GDK\_LINE\_SOLID] Lines are drawn solid.
  115. \item[GDK\_LINE\_ON\_OFF\_DASH] Even segments are drawn, odd segments are
  116. not.
  117. \item[GDK\_LINE\_DOUBLE\_DASH] Even segments are drawn, Odd segments are
  118. drawn in the background color if the fill style is \var{GDK\_SOLID}.
  119. \end{description}
  120. \var{cap\_style} determines how line ends are drawn. The following values are
  121. defined:
  122. \begin{description}
  123. \item[GDK\_CAP\_BUTT] The lines are drawn with square ends.
  124. \item[GDK\_CAP\_NOT\_LAST] Idem as \var{GDK\_CAP\_BUTT}, only for zero-width
  125. lines, the last dot is not drawn.
  126. \item[GDK\_CAP\_ROUND] The end of the line is a semicircle. The circle has
  127. diameter equal to the linewidth, and the center is the endpoint of the line.
  128. \item[GDK\_CAP\_PROJECTING] Idem as [GDK\_CAP\_BUTT], only the line extends
  129. half the linewidth outside the endpoint.
  130. \end{description}
  131. The effect of these elements will be shown in the next section.
  132. To set a color, a \var{TGDkColor} record must be allocated. Colors are
  133. specified using a RGB value. Unfortunately, not all graphics cards can
  134. show all colors. In order to find out which screen color corresponds
  135. to the RGB-specified color, the GDK uses a colormap, and allocates a
  136. color that matches the closest to the specified color values.
  137. When allocating a new color, the colormap should be specified.
  138. A colormap can be obtained from a \var{TGTKWidget} object using the
  139. \var{gtk\_widget\_get\_colormap} function; A color can then be allocated
  140. using the \var{gdk\_colormap\_alloc\_color} function:
  141. \begin{verbatim}
  142. function gdk_colormap_alloc_color(colormap:PGdkColormap;
  143. color:PGdkColor;
  144. writeable:gboolean;
  145. best_match:gboolean):gboolean;
  146. \end{verbatim}
  147. The \var{writeable} parameter specifies whether changes in the
  148. \var{color} using \var{gdk\_color\_change} are allowed.
  149. \var{best\_match} specifies whether a best match should be attempted
  150. on existing colors or an exact value is required.
  151. The function returns \var{True} if the allocation succeeded,
  152. \var{False} otherwise.
  153. \section{Drawing primitives}
  154. Using the properties introduced in the previous section, drawing can be
  155. attempted using the drawing primitives offered by GDK. GDK offers drawing
  156. functions for points, lines, segments, rectangles, polygons, circles, text
  157. and bitmaps.
  158. All functions accept as the first two parameters a \var{PGDKdrawable}, which
  159. can be a pointer to a \var{TGDKWindow} or a \var{TGDkPixmap}, and a
  160. \var{PGdkGC}, a pointer to a graphics context.
  161. These parameters are omitted from the following declarations:
  162. \begin{verbatim}
  163. procedure gdk_draw_point(x,y:gint);
  164. procedure gdk_draw_line(x1,y1,x2,y2:gint);
  165. procedure gdk_draw_rectangle(filled,x,y,width,height:gint);
  166. \end{verbatim}
  167. The above functions draw respectively a dot, a line and a rectangle.
  168. The meaning of the parameters for these functions is obvious.
  169. For the rectangle, care must be taken. If the parameter \var{Filled} is
  170. False (-1) then the drawn rectangle has actually a width and height of
  171. \var{Width+1}, \var{Height+1}. If it is filled, then the width and
  172. height are as specified in the call to \var{gdk\_draw\_rectangle}.
  173. The following functions can be used to draw a series of lines:
  174. \begin{verbatim}
  175. procedure gdk_draw_polygon(filled:gint;points:PGdkPoint; npoints:gint);
  176. procedure gdk_draw_lines(points:PGdkPoint; npoints:gint);
  177. procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint);
  178. \end{verbatim}
  179. The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects
  180. them using lines, optionally filling them. The points are specified by a
  181. pointer to an array of \var{TGDKPoint} records (there should be \var{npoint}
  182. such records in the array).
  183. A \var{TGDKPoint} record contains 2 fields: \var{X,Y} which specify the
  184. location of a point.
  185. If needed, the first and last points are also connected using a line.
  186. The \var{gdk\_draw\_lines} does the same, only it cannot be filled, and it
  187. will not connect the first and last points.
  188. The \var{gdk\_draw\_segments} requires a series of \var{TGDKSegment}
  189. records. These consist of 4 fields: \var{x1,y1,x2,y2}, each describing
  190. the start and end point of a line segment. The segments will not be
  191. connected.
  192. The \var{gdk\_draw\_arc} can be used to draw a circle or a segment of
  193. the circle, or an ellipse.
  194. \begin{verbatim}
  195. procedure gdk_draw_arc(filled,x,y,width,height,angle1,angle2 : gint);
  196. \end{verbatim}
  197. The \var{x,y, width} and \var{height} parameters describe a bounding
  198. rectangle for the circle. The angles describe the start and extending
  199. angle of the segment to be drawn: The circle segment starts at angle
  200. \var{angle1} and ends at \var{angle1+angle2}. These angles are specified
  201. in 1/64ths of a degree and are measured counterclockwise, starting at
  202. the 3 o'clock direction. A circle segment drawn from 90 to 270 degrees
  203. should therefore have as angles 90*64=5760 and 270*64=17280.
  204. If filled is \var{True} (-1), then the segment will be connected to
  205. the circle centre, and filled, in effect drawing a pie-slice.
  206. Finally, for the \var{gdk\_draw\_string} function, the graphics context comes
  207. before the graphics context:
  208. \begin{verbatim}
  209. procedure gdk_draw_string(drawable:PGdkDrawable; font:PGdkFont; gc:PGdkGC;
  210. x:gint; y:gint; thestring:Pgchar);
  211. \end{verbatim}
  212. The meaning of the parameters for this functions should be obvious.
  213. The font for the \var{gdk\_draw\_string} can be obtained using the
  214. \var{gdk\_font\_load} function:
  215. \begin{verbatim}
  216. function gdk_font_load(font_name:Pgchar):PGdkFont;
  217. \end{verbatim}
  218. The font name should be specified as an X font path.
  219. All this is demonstrated in the following program:
  220. \begin{lstlisting}{}
  221. program graphics;
  222. {$mode objfpc}
  223. {$h+}
  224. uses glib,gdk,gtk,sysutils;
  225. var
  226. window,
  227. area : PGtkWidget;
  228. Function CloseApp(widget : PGtkWidget ;
  229. event : PGdkEvent;
  230. data : gpointer) : boolean; cdecl;
  231. Begin
  232. gtk_main_quit();
  233. close_application := false;
  234. End;
  235. Function AllocateColor(R,G,B : Integer;
  236. Widget : PGtkWidget) : PGdkColor;
  237. begin
  238. Result:=New(PgdkColor);
  239. With Result^ do
  240. begin
  241. Pixel:=0;
  242. Red:=R;
  243. Blue:=B;
  244. Green:=G;
  245. end;
  246. gdk_colormap_alloc_color(gtk_widget_get_colormap(Widget),
  247. Result,true,False);
  248. end;
  249. function Exposed(Widget: PGtkWidget;
  250. event : PGdkEventExpose;
  251. Data : gpointer) : Integer; cdecl;
  252. Const
  253. Triangle : Array[1..4] of TgdkPoint =
  254. ((X:10;Y:195),
  255. (X:110;Y:195),
  256. (X:55;Y:145),
  257. (X:10;Y:195));
  258. LineStyles : Array[1..5] of TgdkLineStyle =
  259. (GDK_LINE_SOLID, GDK_LINE_ON_OFF_DASH,
  260. GDK_LINE_DOUBLE_DASH, GDK_LINE_ON_OFF_DASH,
  261. GDK_LINE_SOLID);
  262. capstyles : Array[1..5] of TgdkCapStyle =
  263. (GDK_CAP_ROUND,GDK_CAP_NOT_LAST, GDK_CAP_BUTT,
  264. GDK_CAP_PROJECTING, GDK_CAP_NOT_LAST);
  265. Var
  266. SegTriangle : Array[1..3] of TgdkSegment;
  267. Win : pgdkWindow;
  268. gc : PgdkGC;
  269. i,seg : Integer;
  270. font : PgdkFont;
  271. Angle1,Angle2 : Longint;
  272. begin
  273. gc:=gdk_gc_new(widget^.Window);
  274. Win:=widget^.window;
  275. With Event^.area do
  276. gdk_window_clear_area (win,x,y,width,height);
  277. gdk_gc_set_foreground(gc,allocatecolor(0,0,0,Widget));
  278. gdk_draw_rectangle(win,gc,0,5,5,590,390);
  279. gdk_gc_set_foreground(gc,allocatecolor(0,0,$ffff,Widget));
  280. for I:=10 to 50 do
  281. gdk_draw_point(win,gc,I*10,100);
  282. gdk_gc_set_foreground(gc,allocatecolor($ffff,0,0,Widget));
  283. for I:=10 to 50 do
  284. begin
  285. gdk_gc_set_line_attributes(gc,6,LineStyles[i div 10],CapStyles[i div 10],GDK_JOIN_MITER);
  286. gdk_draw_line(win,gc,I*10,20,I*10,90)
  287. end;
  288. gdk_gc_set_line_attributes(gc,1,GDK_LINE_SOLID,GDK_CAP_BUTT,GDK_JOIN_MITER);
  289. gdk_gc_set_foreground(gc,allocatecolor($ffff,0,$ffff,Widget));
  290. seg:=(360 div 20) * 64;
  291. For I:=1 to 20 do
  292. gdk_draw_arc(win,gc,0,220-I*4,200-i*4,8*i,8*i,i*seg,seg*19);
  293. For I:=1 to 20 do
  294. gdk_draw_arc(win,gc,-1,380-I*4,200-i*4,8*i,8*i,(i-1)*seg,seg);
  295. gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,$ffff,Widget));
  296. gdk_draw_polygon(win,gc,0,@triangle[1],4);
  297. For I:=1 to 4 do
  298. Triangle[i].Y:=400-Triangle[i].y;
  299. gdk_draw_polygon(win,gc,-1,@triangle[1],4);
  300. gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,0,Widget));
  301. For I:=1 to 4 do
  302. Triangle[i].X:=600-Triangle[i].x;
  303. gdk_draw_lines(win,gc,@triangle[1],4);
  304. For I:=1 to 3 do
  305. begin
  306. SegTriangle[i].X1:=Triangle[i].X;
  307. SegTriangle[i].Y1:=400-Triangle[i].Y;
  308. SegTriangle[i].X2:=Triangle[i+1].X;
  309. SegTriangle[i].Y2:=400-Triangle[i+1].Y;
  310. end;
  311. gdk_draw_segments(win,gc,@segtriangle[1],3);
  312. font:=gdk_font_load('-*-helvetica-bold-r-normal--*-120-*-*-*-*-iso8859-1');
  313. gdk_gc_set_foreground(gc,allocatecolor($ffff,$ffff,0,Widget));
  314. For I:=1 to 4 do
  315. gdk_draw_string(win,font,gc,I*100,300,Pchar(format('String %d',[i])));
  316. result:=0;
  317. end;
  318. Begin
  319. // Initialize GTK and create the main window
  320. gtk_init( @argc, @argv );
  321. window := gtk_window_new( GTK_WINDOW_TOPLEVEL );
  322. gtk_window_set_policy(PgtkWindow(Window),0,0,1);
  323. gtk_signal_connect (GTK_OBJECT (window), 'delete_event',
  324. GTK_SIGNAL_FUNC( @CloseApp ), NIL);
  325. gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  326. area := gtk_drawing_area_new();
  327. gtk_container_add( GTK_CONTAINER(window), Area);
  328. gtk_signal_connect (GTK_OBJECT (area),'expose_event',
  329. GTK_SIGNAL_FUNC(@Exposed),Nil);
  330. gtk_drawing_area_size (PGTKDRAWINGAREA(area),600,400);
  331. gtk_widget_show_all( window );
  332. gtk_main();
  333. end.
  334. \end{lstlisting}
  335. The main program starts by creating a main window,
  336. and adding a \var{TGTKDrawingArea} to it. It then connects 2 event handlers,
  337. one to stop the application if the window is closed (\var{CloseApp}),
  338. the other to draw the \var{TGTKDrawingArea} when it is exposed
  339. (\var{Exposed}). This latter contains the actual drawing routines, and is
  340. pretty self-explaining. It simply demonstrates the use of the drawing
  341. primitives explained above.
  342. Note that the allocated colors are not freed again, so this program does
  343. contain a memory leak.
  344. \section{Animation}
  345. The GDK drawing functions can be used to draw directly on a window visible
  346. on the screen. This is OK for normal applications, but applications that
  347. have a lot of (changing) graphics will soon see a flickering screen.
  348. Luckily, GDK provides a means to cope with this: Instead of drawing directly
  349. on the screen, one can draw on a bitmap which exists in memory, and copy
  350. parts of the bitmap to the screen on an as-need basis.
  351. This is the reason why the GDK drawing functions generally accept a
  352. \var{PGDKdrawable} parameter: This can be of the type \var{PgdkWindow} or
  353. \var{PGDKPixmap}: The \var{TGDKPixmap} can be used to do the drawing in the
  354. background, and then copy the pixmap to the actual window.
  355. This technique, known as double buffering, will be demonstrated in a small
  356. arcade game: BreakOut. The game is quite simple: at the top of the screen,
  357. there are a series of bricks. At the bottom of the screen is a small pad,
  358. which can be move left or right using the cursor keys. A ball bounces on the
  359. screen. When the ball hits a brick, the brick dissappears. When the ball
  360. hits the bottom of the window, the ball is lost. The pad can be used to
  361. prevent the ball from hitting the bottom window.
  362. When the pad hits the ball, the ball is accellerated in the direction the
  363. pad was moving at the moment of impact. Also, an idea of 'slope' is
  364. introduced: If the ball hits the pad at some distance from the pad's center,
  365. the ball's trajectory is slightly disturbed, as if the pad has a slope.
  366. After 5 balls were lost, the game is over. If all bricks have been
  367. destroyed, a next level is started.
  368. As stated above, the game will be implemented using double buffering.
  369. The ball and pad themselves will be implemented as pixmaps; the bricks
  370. will be drawn as simple rectangles.
  371. These three objects will be implemented using a series of classes:
  372. \var{TGraphicalObject}, which introduces a position and size. This class
  373. will have 2 descendents: \var{TBlock}, which will draw a block on the
  374. screen and \var{TSprite}, which contains all functionality to draw a moving
  375. pixmap on the screen. From \var{TSprite}, \var{TBall} and \var{TPad} will be
  376. derived. These two objects introduce the behaviour specific to the ball and
  377. pad in the game.
  378. The blocks will be managed by a \var{TBlockList} class, which is a
  379. descendent of the standard \var{TList} class.
  380. All these objects are managed by a \var{TBreakOut} class, which contains the
  381. game logic. The class structure could be improved a bit, but the idea is
  382. more to separate the logic of the different objects.
  383. The \var{TGraphicalObject} class is a simple object which introduces some
  384. easy access properties to get the position and size of the object:
  385. \begin{verbatim}
  386. TGraphicalObject = Class(TObject)
  387. FRect : TGdkRectangle;
  388. Public
  389. Function Contains(X,Y : Integer) : Boolean;
  390. Property Left : SmallInt Read FRect.x Write Frect.x;
  391. Property Top : SmallInt Read FRect.y Write Frect.y;
  392. Property Width : Word Read Frect.Width Write Frect.Width;
  393. Property Height : Word Read Frect.Height Write Frect.Height;
  394. end;
  395. \end{verbatim}
  396. The \var{TBlock} object is a simple descendent of the var{TGraphicalObject}
  397. class:
  398. \begin{verbatim}
  399. TBlock = Class(TGraphicalObject)
  400. Private
  401. FMaxHits : Integer;
  402. FBlockList : TBlockList;
  403. FGC : PGDKGC;
  404. FColor : PGDKColor;
  405. FNeedRedraw : Boolean;
  406. Procedure CreateGC;
  407. Function DrawingArea : PGtkWidget;
  408. Function PixMap : PgdkPixMap;
  409. Public
  410. Procedure Draw;
  411. Function Hit : Boolean;
  412. Constructor Create (ABlockList : TBlockList);
  413. Property Color : PGDKColor Read FColor Write FColor;
  414. end;
  415. \end{verbatim}
  416. The \var{FMaxHits} field determines how many times the ball must hit the
  417. brick before it dissappears. With each hit, the field is decremented by 1.
  418. The \var{FBlockList} refers to the blocklist object that will manage the
  419. block. The needed drawing widget and the pixmap on which the block must be
  420. drawn are obtained from the blockmanager using the \var{DrawingArea} and
  421. \var{Pixmap} functions.
  422. The \var{Draw} procedure will draw the block at it's position on the pixmap.
  423. The \var{Color} property determines the color in which the block will be
  424. drawn.
  425. The implementation of the \var{TBlock} methods are quite simple. The first
  426. methods don't need any explanation.
  427. \begin{verbatim}
  428. Constructor TBlock.Create (ABlockList : TBlockList);
  429. begin
  430. Inherited Create;
  431. FBlockList:=ABlockList;
  432. FMaxHits:=1;
  433. end;
  434. Function TBlock.DrawingArea : PGtkWidget;
  435. begin
  436. Result:=FBlockList.FBreakout.FDrawingArea;
  437. end;
  438. Function TBlock.PixMap : PgdkPixMap;
  439. begin
  440. Result:=FBlockList.PixMap;
  441. end;
  442. \end{verbatim}
  443. The first interesting method is the \var{CreateGC} method:
  444. \begin{verbatim}
  445. Procedure TBlock.CreateGC;
  446. begin
  447. FGC:=gdk_gc_new(DrawingArea^.Window);
  448. gdk_gc_set_foreground(FGC,FColor);
  449. gdk_gc_set_fill(FGC,GDK_SOLID);
  450. FNeedRedraw:=True;
  451. end;
  452. \end{verbatim}
  453. The method is called the first time the block must be drawn. It allocates a
  454. new graphics context using the \var{gdk\_gc\_new} function. This function
  455. accepts a pointer to a \var{TGTKWidget} as a parameter and returns a new
  456. graphics context. After the graphics context is created, the foreground
  457. color and fill style are set. (it is assumed that \var{FColor} points
  458. to a valid color)
  459. The \var{Draw} procedure actually draws the block on the pixmap, using
  460. the graphics context created in the previous method:
  461. \begin{verbatim}
  462. Procedure TBlock.Draw;
  463. begin
  464. if FGC=Nil then
  465. CreateGC;
  466. if FNeedRedraw Then
  467. begin
  468. gdk_draw_rectangle(PGDKDrawable(Pixmap),FGC,-1,Left,Top,Width,Height);
  469. FNeedRedraw:=False;
  470. end;
  471. end;
  472. \end{verbatim}
  473. The \var{FNeedRedraw} procedure is used for optimization.
  474. Finally, the \var{Hit} method is called when the block is hit by the ball.
  475. It will decrease the \var{FMaxHits} field, and if it reaches zero, the
  476. place occupied by the block is redrawn in the background color. After that,
  477. it removes itself from the blocklist and frees itself.
  478. \begin{verbatim}
  479. Function TBlock.Hit : Boolean;
  480. begin
  481. Dec(FMaxHits);
  482. Result:=FMaxHits=0;
  483. If Result then
  484. begin
  485. FBlockList.FBreakOut.DrawBackground(FRect);
  486. FBlockList.Remove(Self);
  487. Free;
  488. end;
  489. end;
  490. \end{verbatim}
  491. The \var{TSprite} object is a little more involved. The declaration is
  492. as follows:
  493. \begin{verbatim}
  494. TSprite = Class(TGraphicalObject)
  495. FPreviousTop,
  496. FPreviousLeft : Integer;
  497. FDrawingArea : PGtkWidget;
  498. FDrawPixMap : PgdkPixmap;
  499. FPixMap : PgdkPixMap;
  500. FBitMap : PGdkBitMap;
  501. Protected
  502. Procedure CreateSpriteFromData(SpriteData : PPGchar);
  503. Procedure CreatePixMap; Virtual; Abstract;
  504. Procedure SavePosition;
  505. Public
  506. Constructor Create(DrawingArea: PGtkWidget);
  507. Procedure Draw;
  508. Function GetChangeRect (Var Rect : TGDkRectAngle) : Boolean;
  509. Property PixMap : PgdkPixMap Read FPixMap;
  510. Property BitMap : PGdkBitMap Read FBitMap;
  511. end;
  512. \end{verbatim}
  513. The important property is the \var{PixMap} property; this contains the
  514. pixmap with the sprite's image. The \var{BitMap} property contains the
  515. bitmap associated with the pixmap. The second important method is the
  516. \var{GetChangeRect} method; it returns the rectangle occupied by the
  517. sprite at its previous position. This will be used to 'move' the sprite:
  518. When moving the sprite, the current position is saved (using
  519. \var{SavePosition}), and the new position is set. After that, the old
  520. position is cleared, and the sprite is drawn at the new position.
  521. All this drawing is done on the background pixmap, to avoid flickering
  522. when drawing: The result of the two drawing steps is shown at once.
  523. The implementation of the \var{Draw} method is quite straightforward:
  524. \begin{verbatim}
  525. Procedure TSprite.Draw;
  526. Var
  527. gc : PGDKGc;
  528. begin
  529. if FPixMap=Nil then
  530. CreatePixMap;
  531. gc:=gtk_widget_get_style(FDrawingArea)^.fg_gc[GTK_STATE_NORMAL];
  532. gdk_gc_set_clip_origin(gc,Left,Top);
  533. gdk_gc_set_clip_mask(gc,FBitmap);
  534. gdk_draw_pixmap(FDrawPixMap,gc,FPixMap,0,0,Left,Top,Width,Height)
  535. gdk_gc_set_clip_mask(gc,Nil);
  536. end;
  537. \end{verbatim}
  538. After the pixmap has been created (a method which must be implemented by
  539. descendent objects), the graphics context of the drawing area is retrieved
  540. to do the drawing.
  541. The bitmap is drawn using the clipping functionality of the GDK toolkit:
  542. To this end, the clip origin is set to the position of the sprite, and
  543. the clip bitmask is set from the \var{FBitmap}, which is created when the
  544. sprite's pixmap is created. When drawing the pixmap, only the bits in the
  545. bitmap will be drawn, other bits are left untouched.
  546. The pixmap is drawn using the \var{gdk\_draw\_pixmap} function. This
  547. function copies a region from one \var{TGDKDrawable} to another.
  548. It is defined as follows:
  549. \begin{verbatim}
  550. procedure gdk_draw_pixmap(drawable:PGdkDrawable; gc:PGdkGC;
  551. src:PGdkDrawable;
  552. xsrc,ysrc,xdest,ydest,width,height:gint);
  553. \end{verbatim}
  554. The function, as all GDK drawing functions, takes a \var{PGDKDrawable}
  555. pointer and a graphics contexts as its first two arguments. The third
  556. argument is the \var{TGDKDrawable} which should be copied. The
  557. \var{xsrc,ysrc} parameters indicate the position of the region that should
  558. be copied in the source \var{TGDKDrawable}; the \var{xdest,ydest} indicate
  559. the position in the target \var{TGDKDrawable} where the bitmap should be
  560. drawn.
  561. In the case of \var{TSprite}, the function is used to copy the sprite's
  562. bitmap onto the memory pixmap with the game image. After the bitmap was
  563. copied, the clip mask is removed again.
  564. The creation of the pixmap happens when the sprite is drawn for the first
  565. time; The \var{CreateSpriteFromData} method accepts a pointer to an XPM
  566. pixmap, and uses the \var{gdk\_pixmap\_create\_from\_xpm\_d} function
  567. (explained in the previous article) to create the actual pixmap and the
  568. corresponding bitmap.
  569. \begin{verbatim}
  570. Procedure TSprite.CreateSpriteFromData(SpriteData : PPGChar);
  571. begin
  572. FPixMap:=gdk_pixmap_create_from_xpm_d(FDrawingArea^.Window,
  573. @FBitmap,
  574. Nil,
  575. SpriteData);
  576. end;
  577. \end{verbatim}
  578. This method can be used by the descendent object's \var{CreatePixmap}
  579. procedure.
  580. The \var{SavePosition} and \var{GetChangeRect} methods are very
  581. straightforward:
  582. \begin{verbatim}
  583. Function TSprite.GetChangeRect (Var Rect : TGDkRectAngle) : Boolean;
  584. begin
  585. Result:=(FPreviousLeft<>Left) or (FPreviousTop<>Top);
  586. If Result then
  587. With Rect do
  588. begin
  589. x:=FPreviousLeft;
  590. y:=FPreviousTop;
  591. Width:=Abs(Left-FPreviousLeft)+self.Width;
  592. height:=Abs(Top-FPreviousTop)+self.Height;
  593. end;
  594. end;
  595. Procedure TSprite.SavePosition;
  596. begin
  597. FPreviousLeft:=Left;
  598. FPreviousTop:=Top;
  599. end;
  600. \end{verbatim}
  601. Note that the \var{GetChangeRect} procedure returns false if the position
  602. of the sprite didn't change. This is used for optimization purposes.
  603. The pad is the simplest of the two \var{TSprite} descendents. It only adds a
  604. horizontal movement to the sprite:
  605. \begin{verbatim}
  606. TPad = Class (TSprite)
  607. Private
  608. FSlope,
  609. FSpeed,FCurrentSpeed : Integer;
  610. Protected
  611. Procedure CreatePixMap; override;
  612. Procedure InitialPosition;
  613. Public
  614. Constructor Create(DrawingArea: PGtkWidget);
  615. Procedure Step;
  616. Procedure GoLeft;
  617. Procedure GoRight;
  618. Procedure Stop;
  619. Property CurrentSpeed : Integer Read FCurrentSpeed;
  620. Property Speed : Integer Read FSpeed Write FSpeed;
  621. Property Slope : Integer Read FSlope Write FSlope;
  622. end;
  623. \end{verbatim}
  624. The procedures \var{GoLeft}, \var{GoRight} and \var{Stop} can be used to
  625. control the movement of the pad. The method \var{Step} will be called at
  626. regular intervals to actually move the pad. The \var{InitialPosition}
  627. sets the pad at its initial position on the screen. The \var{Speed} and
  628. \var{Slope} properties can be used to set the speed and slope of the pad.
  629. The implementation is quite straightforward:
  630. \begin{verbatim}
  631. \end{verbatim}
  632. \end{document}