diff --git a/docs/gtk5.tex b/docs/gtk5.tex index cf628653d3..9fdf756204 100644 --- a/docs/gtk5.tex +++ b/docs/gtk5.tex @@ -171,29 +171,36 @@ functions for points, lines, segments, rectangles, polygons, circles, text and bitmaps. All functions accept as the first two parameters a \var{PGDKdrawable}, which -can be a \var{TGDKWindow} or \var{TGDkPixmap}, and a \var{PGdkGC}, a pointer -to a graphics context. These parameters are omitted from the following -declarations: +can be a pointer to a \var{TGDKWindow} or a \var{TGDkPixmap}, and a +\var{PGdkGC}, a pointer to a graphics context. + +These parameters are omitted from the following declarations: \begin{verbatim} procedure gdk_draw_point(x,y:gint); procedure gdk_draw_line(x1,y1,x2,y2:gint); procedure gdk_draw_rectangle(filled,x,y,width,height:gint); \end{verbatim} +The above functions draw respectively a dot, a line and a rectangle. The meaning of the parameters for these functions is obvious. -For the rectangle, care must be taken. If \var{Filled} is false (-1) then -the drawn rectangle is actually \var{Width+1}, \var{Height+1}. If it is -filled, then the width are height are as specified. +For the rectangle, care must be taken. If the parameter \var{Filled} is +False (-1) then the drawn rectangle has actually a width and height of +\var{Width+1}, \var{Height+1}. If it is filled, then the width and +height are as specified in the call to \var{gdk\_draw\_rectangle}. The following functions can be used to draw a series of lines: \begin{verbatim} procedure gdk_draw_polygon(filled:gint;points:PGdkPoint; npoints:gint); -procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint); procedure gdk_draw_lines(points:PGdkPoint; npoints:gint); +procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint); \end{verbatim} The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects -them using lines, optionally filling them. A \var{TGDKPoint} record contains - 2 fields \var{X,Y} which specify the location of a point. If needed, the -first and last points are also connected using a line. +them using lines, optionally filling them. The points are specified by a +pointer to an array of \var{TGDKPoint} records (there should be \var{npoint} +such records in the array). +A \var{TGDKPoint} record contains 2 fields: \var{X,Y} which specify the +location of a point. +If needed, the first and last points are also connected using a line. + The \var{gdk\_draw\_lines} does the same, only it cannot be filled, and it will not connect the first and last points. The \var{gdk\_draw\_segments} requires a series of \var{TGDKSegment} @@ -225,6 +232,13 @@ procedure gdk_draw_string(drawable:PGdkDrawable; font:PGdkFont; gc:PGdkGC; \end{verbatim} The meaning of the parameters for this functions should be obvious. +The font for the \var{gdk\_draw\_string} can be obtained using the +\var{gdk\_font\_load} function: +\begin{verbatim} + function gdk_font_load(font_name:Pgchar):PGdkFont; +\end{verbatim} +The font name should be specified as an X font path. + All this is demonstrated in the following program: \begin{lstlisting}{} program graphics; @@ -352,17 +366,343 @@ Begin gtk_main(); end. \end{lstlisting} -The main program startsby creating a main window, +The main program starts by creating a main window, and adding a \var{TGTKDrawingArea} to it. It then connects 2 event handlers, one to stop the application if the window is closed (\var{CloseApp}), the other to draw the \var{TGTKDrawingArea} when it is exposed (\var{Exposed}). This latter contains the actual drawing routines, and is -pretty self-explaining. +pretty self-explaining. It simply demonstrates the use of the drawing +primitives explained above. Note that the allocated colors are not freed again, so this program does contain a memory leak. \section{Animation} +The GDK drawing functions can be used to draw directly on a window visible +on the screen. This is OK for normal applications, but applications that +have a lot of (changing) graphics will soon see a flickering screen. +Luckily, GDK provides a means to cope with this: Instead of drawing directly +on the screen, one can draw on a bitmap which exists in memory, and copy +parts of the bitmap to the screen on an as-need basis. + +This is the reason why the GDK drawing functions generally accept a +\var{PGDKdrawable} parameter: This can be of the type \var{PgdkWindow} or +\var{PGDKPixmap}: The \var{TGDKPixmap} can be used to do the drawing in the +background, and then copy the pixmap to the actual window. + +This technique, known as double buffering, will be demonstrated in a small +arcade game: BreakOut. The game is quite simple: at the top of the screen, +there are a series of bricks. At the bottom of the screen is a small pad, +which can be move left or right using the cursor keys. A ball bounces on the +screen. When the ball hits a brick, the brick dissappears. When the ball +hits the bottom of the window, the ball is lost. The pad can be used to +prevent the ball from hitting the bottom window. + +When the pad hits the ball, the ball is accellerated in the direction the +pad was moving at the moment of impact. Also, an idea of 'slope' is +introduced: If the ball hits the pad at some distance from the pad's center, +the ball's trajectory is slightly disturbed, as if the pad has a slope. + +After 5 balls were lost, the game is over. If all bricks have been +destroyed, a next level is started. + +As stated above, the game will be implemented using double buffering. +The ball and pad themselves will be implemented as pixmaps; the bricks +will be drawn as simple rectangles. + +These three objects will be implemented using a series of classes: +\var{TGraphicalObject}, which introduces a position and size. This class +will have 2 descendents: \var{TBlock}, which will draw a block on the +screen and \var{TSprite}, which contains all functionality to draw a moving +pixmap on the screen. From \var{TSprite}, \var{TBall} and \var{TPad} will be +derived. These two objects introduce the behaviour specific to the ball and +pad in the game. + +The blocks will be managed by a \var{TBlockList} class, which is a +descendent of the standard \var{TList} class. + +All these objects are managed by a \var{TBreakOut} class, which contains the +game logic. The class structure could be improved a bit, but the idea is +more to separate the logic of the different objects. + +The \var{TGraphicalObject} class is a simple object which introduces some +easy access properties to get the position and size of the object: +\begin{verbatim} +TGraphicalObject = Class(TObject) + FRect : TGdkRectangle; +Public + Function Contains(X,Y : Integer) : Boolean; + Property Left : SmallInt Read FRect.x Write Frect.x; + Property Top : SmallInt Read FRect.y Write Frect.y; + Property Width : Word Read Frect.Width Write Frect.Width; + Property Height : Word Read Frect.Height Write Frect.Height; +end; +\end{verbatim} + +The \var{TBlock} object is a simple descendent of the var{TGraphicalObject} +class: +\begin{verbatim} +TBlock = Class(TGraphicalObject) +Private + FMaxHits : Integer; + FBlockList : TBlockList; + FGC : PGDKGC; + FColor : PGDKColor; + FNeedRedraw : Boolean; + Procedure CreateGC; + Function DrawingArea : PGtkWidget; + Function PixMap : PgdkPixMap; +Public + Procedure Draw; + Function Hit : Boolean; + Constructor Create (ABlockList : TBlockList); + Property Color : PGDKColor Read FColor Write FColor; +end; +\end{verbatim} +The \var{FMaxHits} field determines how many times the ball must hit the +brick before it dissappears. With each hit, the field is decremented by 1. + +The \var{FBlockList} refers to the blocklist object that will manage the +block. The needed drawing widget and the pixmap on which the block must be +drawn are obtained from the blockmanager using the \var{DrawingArea} and +\var{Pixmap} functions. +The \var{Draw} procedure will draw the block at it's position on the pixmap. +The \var{Color} property determines the color in which the block will be +drawn. + +The implementation of the \var{TBlock} methods are quite simple. The first +methods don't need any explanation. +\begin{verbatim} +Constructor TBlock.Create (ABlockList : TBlockList); + +begin + Inherited Create; + FBlockList:=ABlockList; + FMaxHits:=1; +end; + +Function TBlock.DrawingArea : PGtkWidget; + +begin + Result:=FBlockList.FBreakout.FDrawingArea; +end; + +Function TBlock.PixMap : PgdkPixMap; + +begin + Result:=FBlockList.PixMap; +end; +\end{verbatim} +The first interesting method is the \var{CreateGC} method: +\begin{verbatim} +Procedure TBlock.CreateGC; + +begin + FGC:=gdk_gc_new(DrawingArea^.Window); + gdk_gc_set_foreground(FGC,FColor); + gdk_gc_set_fill(FGC,GDK_SOLID); + FNeedRedraw:=True; +end; +\end{verbatim} +The method is called the first time the block must be drawn. It allocates a +new graphics context using the \var{gdk\_gc\_new} function. This function +accepts a pointer to a \var{TGTKWidget} as a parameter and returns a new +graphics context. After the graphics context is created, the foreground +color and fill style are set. (it is assumed that \var{FColor} points +to a valid color) + +The \var{Draw} procedure actually draws the block on the pixmap, using +the graphics context created in the previous method: +\begin{verbatim} +Procedure TBlock.Draw; + +begin + if FGC=Nil then + CreateGC; + if FNeedRedraw Then + begin + gdk_draw_rectangle(PGDKDrawable(Pixmap),FGC,-1,Left,Top,Width,Height); + FNeedRedraw:=False; + end; +end; +\end{verbatim} +The \var{FNeedRedraw} procedure is used for optimization. + +Finally, the \var{Hit} method is called when the block is hit by the ball. +It will decrease the \var{FMaxHits} field, and if it reaches zero, the +place occupied by the block is redrawn in the background color. After that, +it removes itself from the blocklist and frees itself. +\begin{verbatim} +Function TBlock.Hit : Boolean; + +begin + Dec(FMaxHits); + Result:=FMaxHits=0; + If Result then + begin + FBlockList.FBreakOut.DrawBackground(FRect); + FBlockList.Remove(Self); + Free; + end; +end; +\end{verbatim} + +The \var{TSprite} object is a little more involved. The declaration is +as follows: +\begin{verbatim} +TSprite = Class(TGraphicalObject) + FPreviousTop, + FPreviousLeft : Integer; + FDrawingArea : PGtkWidget; + FDrawPixMap : PgdkPixmap; + FPixMap : PgdkPixMap; + FBitMap : PGdkBitMap; +Protected + Procedure CreateSpriteFromData(SpriteData : PPGchar); + Procedure CreatePixMap; Virtual; Abstract; + Procedure SavePosition; +Public + Constructor Create(DrawingArea: PGtkWidget); + Procedure Draw; + Function GetChangeRect (Var Rect : TGDkRectAngle) : Boolean; + Property PixMap : PgdkPixMap Read FPixMap; + Property BitMap : PGdkBitMap Read FBitMap; +end; +\end{verbatim} +The important property is the \var{PixMap} property; this contains the +pixmap with the sprite's image. The \var{BitMap} property contains the +bitmap associated with the pixmap. The second important method is the +\var{GetChangeRect} method; it returns the rectangle occupied by the +sprite at its previous position. This will be used to 'move' the sprite: +When moving the sprite, the current position is saved (using +\var{SavePosition}), and the new position is set. After that, the old +position is cleared, and the sprite is drawn at the new position. + +All this drawing is done on the background pixmap, to avoid flickering +when drawing: The result of the two drawing steps is shown at once. + +The implementation of the \var{Draw} method is quite straightforward: +\begin{verbatim} +Procedure TSprite.Draw; + +Var + gc : PGDKGc; + +begin + if FPixMap=Nil then + CreatePixMap; + gc:=gtk_widget_get_style(FDrawingArea)^.fg_gc[GTK_STATE_NORMAL]; + gdk_gc_set_clip_origin(gc,Left,Top); + gdk_gc_set_clip_mask(gc,FBitmap); + gdk_draw_pixmap(FDrawPixMap,gc,FPixMap,0,0,Left,Top,Width,Height) + gdk_gc_set_clip_mask(gc,Nil); +end; +\end{verbatim} +After the pixmap has been created (a method which must be implemented by +descendent objects), the graphics context of the drawing area is retrieved +to do the drawing. + +The bitmap is drawn using the clipping functionality of the GDK toolkit: +To this end, the clip origin is set to the position of the sprite, and +the clip bitmask is set from the \var{FBitmap}, which is created when the +sprite's pixmap is created. When drawing the pixmap, only the bits in the +bitmap will be drawn, other bits are left untouched. + +The pixmap is drawn using the \var{gdk\_draw\_pixmap} function. This +function copies a region from one \var{TGDKDrawable} to another. +It is defined as follows: +\begin{verbatim} +procedure gdk_draw_pixmap(drawable:PGdkDrawable; gc:PGdkGC; + src:PGdkDrawable; + xsrc,ysrc,xdest,ydest,width,height:gint); +\end{verbatim} +The function, as all GDK drawing functions, takes a \var{PGDKDrawable} +pointer and a graphics contexts as its first two arguments. The third +argument is the \var{TGDKDrawable} which should be copied. The +\var{xsrc,ysrc} parameters indicate the position of the region that should +be copied in the source \var{TGDKDrawable}; the \var{xdest,ydest} indicate +the position in the target \var{TGDKDrawable} where the bitmap should be +drawn. + +In the case of \var{TSprite}, the function is used to copy the sprite's +bitmap onto the memory pixmap with the game image. After the bitmap was +copied, the clip mask is removed again. + +The creation of the pixmap happens when the sprite is drawn for the first +time; The \var{CreateSpriteFromData} method accepts a pointer to an XPM +pixmap, and uses the \var{gdk\_pixmap\_create\_from\_xpm\_d} function +(explained in the previous article) to create the actual pixmap and the +corresponding bitmap. +\begin{verbatim} +Procedure TSprite.CreateSpriteFromData(SpriteData : PPGChar); + +begin + FPixMap:=gdk_pixmap_create_from_xpm_d(FDrawingArea^.Window, + @FBitmap, + Nil, + SpriteData); +end; +\end{verbatim} +This method can be used by the descendent object's \var{CreatePixmap} +procedure. + +The \var{SavePosition} and \var{GetChangeRect} methods are very +straightforward: +\begin{verbatim} +Function TSprite.GetChangeRect (Var Rect : TGDkRectAngle) : Boolean; + +begin + Result:=(FPreviousLeft<>Left) or (FPreviousTop<>Top); + If Result then + With Rect do + begin + x:=FPreviousLeft; + y:=FPreviousTop; + Width:=Abs(Left-FPreviousLeft)+self.Width; + height:=Abs(Top-FPreviousTop)+self.Height; + end; +end; + +Procedure TSprite.SavePosition; + +begin + FPreviousLeft:=Left; + FPreviousTop:=Top; +end; +\end{verbatim} +Note that the \var{GetChangeRect} procedure returns false if the position +of the sprite didn't change. This is used for optimization purposes. + +The pad is the simplest of the two \var{TSprite} descendents. It only adds a +horizontal movement to the sprite: +\begin{verbatim} +TPad = Class (TSprite) +Private + FSlope, + FSpeed,FCurrentSpeed : Integer; +Protected + Procedure CreatePixMap; override; + Procedure InitialPosition; +Public + Constructor Create(DrawingArea: PGtkWidget); + Procedure Step; + Procedure GoLeft; + Procedure GoRight; + Procedure Stop; + Property CurrentSpeed : Integer Read FCurrentSpeed; + Property Speed : Integer Read FSpeed Write FSpeed; + Property Slope : Integer Read FSlope Write FSlope; +end; +\end{verbatim} +The procedures \var{GoLeft}, \var{GoRight} and \var{Stop} can be used to +control the movement of the pad. The method \var{Step} will be called at +regular intervals to actually move the pad. The \var{InitialPosition} +sets the pad at its initial position on the screen. The \var{Speed} and +\var{Slope} properties can be used to set the speed and slope of the pad. + +The implementation is quite straightforward: +\begin{verbatim} +\end{verbatim} \end{document} \ No newline at end of file