mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-13 13:59:07 +02:00
+ First part of GTK article
This commit is contained in:
parent
3a1f29ce31
commit
ecd4fbace8
368
docs/gtk5.tex
Normal file
368
docs/gtk5.tex
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
\documentclass[10pt]{article}
|
||||||
|
\usepackage{a4}
|
||||||
|
\usepackage{epsfig}
|
||||||
|
\usepackage{listings}
|
||||||
|
\usepackage{tabularx}
|
||||||
|
\lstset{language=Delphi}%
|
||||||
|
\lstset{basicstyle=\sffamily\small}%
|
||||||
|
\lstset{commentstyle=\itshape}%
|
||||||
|
\lstset{keywordstyle=\bfseries}%
|
||||||
|
%\lstset{blankstring=true}%
|
||||||
|
\newcommand{\file}[1]{\textsf{#1}}
|
||||||
|
\newcommand{\var}[1]{\texttt{#1}}
|
||||||
|
\usepackage[pdftex]{hyperref}
|
||||||
|
\newif\ifpdf
|
||||||
|
\ifx\pdfoutput\undefined
|
||||||
|
\pdffalse
|
||||||
|
\else
|
||||||
|
\pdfoutput=1
|
||||||
|
\pdftrue
|
||||||
|
\fi
|
||||||
|
\begin{document}
|
||||||
|
\title{Programming GTK in Free Pascal: Using GDK}
|
||||||
|
\author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
|
||||||
|
\date{July 2001}
|
||||||
|
\maketitle
|
||||||
|
\section{Introduction}
|
||||||
|
In this article, some of the graphics primitives from the gdk toolkit will
|
||||||
|
be demonstrated in a small game - breakout.
|
||||||
|
|
||||||
|
The GTK toolkit widgets are built upon the GDK: Graphics Drawing Kit.
|
||||||
|
The GDK does not know anything about buttons, menus checkboxes and so on.
|
||||||
|
Instead, it knows how to create windows, draw on them, handle mouse clicks
|
||||||
|
and keypresses. This functionality is used by the GTK widget set to create
|
||||||
|
usable widgets.
|
||||||
|
|
||||||
|
Sometimes, the widgets offered by GTK are not enough, and one has to fall
|
||||||
|
back on the graphics functionality of the GDK to be able to do what is
|
||||||
|
needed for a program.
|
||||||
|
|
||||||
|
Fortunately, it is not necessary to create a GTK window and handle all
|
||||||
|
GDK events to be able to use the GDK functions. The GTK widget set has a
|
||||||
|
special widget, which can be used to draw upon. This widget is the
|
||||||
|
\var{TGtkDrawingArea} widget. The use of the \var{TGtkDrawingArea} is what
|
||||||
|
will be explained below.
|
||||||
|
|
||||||
|
The GDK graphics functions will be explained using a simple arcade game,
|
||||||
|
to demonstrate that the speed of the GDK is sufficient for the creation of
|
||||||
|
simple games. The breakout game is chosen because it is conceptually simple,
|
||||||
|
requires moving graphics and can be extended in many ways.
|
||||||
|
|
||||||
|
\section{The drawing area widget}
|
||||||
|
The drawing area widget (\var{TGTKDrawingArea}) is a simple widget which
|
||||||
|
just provides a drawing window. It responds to all widget events, and adds
|
||||||
|
additionally the 'configure\_event', which is called when the widget is
|
||||||
|
realized (i.e. when the window handle is created.)
|
||||||
|
|
||||||
|
The widget has only 1 method: \var{gtk\_drawing\_area\_size}, which sets
|
||||||
|
the size of the drawing area. It is defined as follows:
|
||||||
|
\begin{verbatim}
|
||||||
|
procedure gtk_drawing_area_size(Area:PGtkDrawingArea; width:gint;height:gint)
|
||||||
|
\end{verbatim}
|
||||||
|
The arguments to this function are self-explaining.
|
||||||
|
|
||||||
|
To use the drawing area widget, one should respond to the 'expose\_event'.
|
||||||
|
This event is triggered whenever a part of the window that was invisible,
|
||||||
|
becomes visible. The event handler gets an \var{PGDKEventExpose} parameter,
|
||||||
|
which describes which area was exposed. This can be used for optimization
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
To draw in the drawing area widget, the \var{Window} field of the
|
||||||
|
\var{TGTKWidget} parent can be used. This is of type \var{TGDKWindow}.
|
||||||
|
All drawing functions require a parameter of type \var{TGdkDrawable}
|
||||||
|
which can be one of the \var{TGdkWindow} or \var{TGdkPixMap} types.
|
||||||
|
|
||||||
|
\section{Graphics contexts}
|
||||||
|
Most drawing functions do not only require a drawable to draw on, they also
|
||||||
|
require a {\em Graphics Context}. A graphics context is a series of
|
||||||
|
parameters that determine how lines are drawn, what colors and font are
|
||||||
|
used etc.
|
||||||
|
|
||||||
|
The Graphics Context is an opaque record, and its members cannot be
|
||||||
|
accessed. The relevant parameters are set in a \var{TGdkGCValues} record,
|
||||||
|
which is defined as follows:
|
||||||
|
\begin{verbatim}
|
||||||
|
foreground : TGdkColor;
|
||||||
|
background : TGdkColor;
|
||||||
|
font : PGdkFont;
|
||||||
|
thefunction : TGdkfunction;
|
||||||
|
fill : TGdkFill;
|
||||||
|
tile : PGdkPixmap;
|
||||||
|
stipple : PGdkPixmap;
|
||||||
|
clip_mask : PGdkPixmap;
|
||||||
|
subwindow_mode : TGdkSubwindowMode;
|
||||||
|
ts_x_origin : gint;
|
||||||
|
ts_y_origin : gint;
|
||||||
|
clip_x_origin : gint;
|
||||||
|
clip_y_origin : gint;
|
||||||
|
graphics_exposures : gint;
|
||||||
|
line_width : gint;
|
||||||
|
line_style : TGdkLineStyle;
|
||||||
|
cap_style : TGdkCapStyle;
|
||||||
|
join_style : TGdkJoinStyle;
|
||||||
|
\end{verbatim}
|
||||||
|
The \var{ForeGround} and \var{Background} parameters determine the foreground
|
||||||
|
and background colors. \var{Font} is the default font. The \var{Fill} field
|
||||||
|
describes how areas are filled. It can be one of the following:
|
||||||
|
\begin{description}
|
||||||
|
\item[GDK\_SOLID] fill with the foreground color.
|
||||||
|
\item[GDK\_TILED] Use the pixmap specified in \var{Tile} to fill the area.
|
||||||
|
\item[GDK\_STIPPLED] Use the pixmap specified in \var{Stipple} to draw
|
||||||
|
pixels that are in the bitmap in the foreground color. Other bits are not
|
||||||
|
drawn.
|
||||||
|
\item[GDK\_OPAQUE\_STIPPLED] Same as \var{GDK\_STIPPLED} except that bits
|
||||||
|
not in the pixmap will be drawn in the background color.
|
||||||
|
\end{description}
|
||||||
|
The \var{clip\_bitmap} is used to define a clip area. The
|
||||||
|
\var{ts\_x\_origin} and \var{ts\_y\_origin} define the stipple or tile
|
||||||
|
origin. The \var{clip\_x\_origin} and \var{clip\_y\_origin} fields define
|
||||||
|
the origin of the clipping region.
|
||||||
|
\var{LineWidth} is the linewidth used when drawing lines. \var{Line\_Style}
|
||||||
|
determines how dashed lines are drawn. It can have one of the following
|
||||||
|
values:
|
||||||
|
\begin{description}
|
||||||
|
\item[GDK\_LINE\_SOLID] Lines are drawn solid.
|
||||||
|
\item[GDK\_LINE\_ON\_OFF\_DASH] Even segments are drawn, odd segments are
|
||||||
|
not.
|
||||||
|
\item[GDK\_LINE\_DOUBLE\_DASH] Even segments are drawn, Odd segments are
|
||||||
|
drawn in the background color if the fill style is \var{GDK\_SOLID}.
|
||||||
|
\end{description}
|
||||||
|
\var{cap\_style} determines how line ends are drawn. The following values are
|
||||||
|
defined:
|
||||||
|
\begin{description}
|
||||||
|
\item[GDK\_CAP\_BUTT] The lines are drawn with square ends.
|
||||||
|
\item[GDK\_CAP\_NOT\_LAST] Idem as \var{GDK\_CAP\_BUTT}, only for zero-width
|
||||||
|
lines, the last dot is not drawn.
|
||||||
|
\item[GDK\_CAP\_ROUND] The end of the line is a semicircle. The circle has
|
||||||
|
diameter equal to the linewidth, and the center is the endpoint of the line.
|
||||||
|
\item[GDK\_CAP\_PROJECTING] Idem as [GDK\_CAP\_BUTT], only the line extends
|
||||||
|
half the linewidth outside the endpoint.
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
The effect of these elements will be shown in the next section.
|
||||||
|
|
||||||
|
To set a color, a \var{TGDkColor} record must be allocated. Colors are
|
||||||
|
specified using a RGB value. Unfortunately, not all graphics cards can
|
||||||
|
show all colors. In order to find out which screen color corresponds
|
||||||
|
to the RGB-specified color, the GDK uses a colormap, and allocates a
|
||||||
|
color that matches the closest to the specified color values.
|
||||||
|
When allocating a new color, the colormap should be specified.
|
||||||
|
|
||||||
|
A colormap can be obtained from a \var{TGTKWidget} object using the
|
||||||
|
\var{gtk\_widget\_get\_colormap} function; A color can then be allocated
|
||||||
|
using the \var{gdk\_colormap\_alloc\_color} function:
|
||||||
|
\begin{verbatim}
|
||||||
|
function gdk_colormap_alloc_color(colormap:PGdkColormap;
|
||||||
|
color:PGdkColor;
|
||||||
|
writeable:gboolean;
|
||||||
|
best_match:gboolean):gboolean;
|
||||||
|
\end{verbatim}
|
||||||
|
The \var{writeable} parameter specifies whether changes in the
|
||||||
|
\var{color} using \var{gdk\_color\_change} are allowed.
|
||||||
|
\var{best\_match} specifies whether a best match should be attempted
|
||||||
|
on existing colors or an exact value is required.
|
||||||
|
The function returns \var{True} if the allocation succeeded,
|
||||||
|
\var{False} otherwise.
|
||||||
|
|
||||||
|
\section{Drawing primitives}
|
||||||
|
Using the properties introduced in the previous section, drawing can be
|
||||||
|
attempted using the drawing primitives offered by GDK. GDK offers drawing
|
||||||
|
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:
|
||||||
|
\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 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.
|
||||||
|
|
||||||
|
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);
|
||||||
|
\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.
|
||||||
|
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}
|
||||||
|
records. These consist of 4 fields: \var{x1,y1,x2,y2}, each describing
|
||||||
|
the start and end point of a line segment. The segments will not be
|
||||||
|
connected.
|
||||||
|
|
||||||
|
The \var{gdk\_draw\_arc} can be used to draw a circle or a segment of
|
||||||
|
the circle, or an ellipse.
|
||||||
|
\begin{verbatim}
|
||||||
|
procedure gdk_draw_arc(filled,x,y,width,height,angle1,angle2 : gint);
|
||||||
|
\end{verbatim}
|
||||||
|
The \var{x,y, width} and \var{height} parameters describe a bounding
|
||||||
|
rectangle for the circle. The angles describe the start and extending
|
||||||
|
angle of the segment to be drawn: The circle segment starts at angle
|
||||||
|
\var{angle1} and ends at \var{angle1+angle2}. These angles are specified
|
||||||
|
in 1/64ths of a degree and are measured counterclockwise, starting at
|
||||||
|
the 3 o'clock direction. A circle segment drawn from 90 to 270 degrees
|
||||||
|
should therefore have as angles 90*64=5760 and 270*64=17280.
|
||||||
|
|
||||||
|
If filled is \var{True} (-1), then the segment will be connected to
|
||||||
|
the circle centre, and filled, in effect drawing a pie-slice.
|
||||||
|
|
||||||
|
Finally, for the \var{gdk\_draw\_string} function, the graphics context comes
|
||||||
|
before the graphics context:
|
||||||
|
\begin{verbatim}
|
||||||
|
procedure gdk_draw_string(drawable:PGdkDrawable; font:PGdkFont; gc:PGdkGC;
|
||||||
|
x:gint; y:gint; thestring:Pgchar);
|
||||||
|
\end{verbatim}
|
||||||
|
The meaning of the parameters for this functions should be obvious.
|
||||||
|
|
||||||
|
All this is demonstrated in the following program:
|
||||||
|
\begin{lstlisting}{}
|
||||||
|
program graphics;
|
||||||
|
|
||||||
|
{$mode objfpc}
|
||||||
|
{$h+}
|
||||||
|
|
||||||
|
uses glib,gdk,gtk,sysutils;
|
||||||
|
|
||||||
|
var
|
||||||
|
window,
|
||||||
|
area : PGtkWidget;
|
||||||
|
|
||||||
|
Function CloseApp(widget : PGtkWidget ;
|
||||||
|
event : PGdkEvent;
|
||||||
|
data : gpointer) : boolean; cdecl;
|
||||||
|
Begin
|
||||||
|
gtk_main_quit();
|
||||||
|
close_application := false;
|
||||||
|
End;
|
||||||
|
|
||||||
|
Function AllocateColor(R,G,B : Integer;
|
||||||
|
Widget : PGtkWidget) : PGdkColor;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=New(PgdkColor);
|
||||||
|
With Result^ do
|
||||||
|
begin
|
||||||
|
Pixel:=0;
|
||||||
|
Red:=R;
|
||||||
|
Blue:=B;
|
||||||
|
Green:=G;
|
||||||
|
end;
|
||||||
|
gdk_colormap_alloc_color(gtk_widget_get_colormap(Widget),
|
||||||
|
Result,true,False);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function Exposed(Widget: PGtkWidget;
|
||||||
|
event : PGdkEventExpose;
|
||||||
|
Data : gpointer) : Integer; cdecl;
|
||||||
|
|
||||||
|
Const
|
||||||
|
Triangle : Array[1..4] of TgdkPoint =
|
||||||
|
((X:10;Y:195),
|
||||||
|
(X:110;Y:195),
|
||||||
|
(X:55;Y:145),
|
||||||
|
(X:10;Y:195));
|
||||||
|
LineStyles : Array[1..5] of TgdkLineStyle =
|
||||||
|
(GDK_LINE_SOLID, GDK_LINE_ON_OFF_DASH,
|
||||||
|
GDK_LINE_DOUBLE_DASH, GDK_LINE_ON_OFF_DASH,
|
||||||
|
GDK_LINE_SOLID);
|
||||||
|
capstyles : Array[1..5] of TgdkCapStyle =
|
||||||
|
(GDK_CAP_ROUND,GDK_CAP_NOT_LAST, GDK_CAP_BUTT,
|
||||||
|
GDK_CAP_PROJECTING, GDK_CAP_NOT_LAST);
|
||||||
|
|
||||||
|
Var
|
||||||
|
SegTriangle : Array[1..3] of TgdkSegment;
|
||||||
|
Win : pgdkWindow;
|
||||||
|
gc : PgdkGC;
|
||||||
|
i,seg : Integer;
|
||||||
|
font : PgdkFont;
|
||||||
|
Angle1,Angle2 : Longint;
|
||||||
|
|
||||||
|
begin
|
||||||
|
gc:=gdk_gc_new(widget^.Window);
|
||||||
|
Win:=widget^.window;
|
||||||
|
With Event^.area do
|
||||||
|
gdk_window_clear_area (win,x,y,width,height);
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor(0,0,0,Widget));
|
||||||
|
gdk_draw_rectangle(win,gc,0,5,5,590,390);
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor(0,0,$ffff,Widget));
|
||||||
|
for I:=10 to 50 do
|
||||||
|
gdk_draw_point(win,gc,I*10,100);
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor($ffff,0,0,Widget));
|
||||||
|
for I:=10 to 50 do
|
||||||
|
begin
|
||||||
|
gdk_gc_set_line_attributes(gc,6,LineStyles[i div 10],CapStyles[i div 10],GDK_JOIN_MITER);
|
||||||
|
gdk_draw_line(win,gc,I*10,20,I*10,90)
|
||||||
|
end;
|
||||||
|
gdk_gc_set_line_attributes(gc,1,GDK_LINE_SOLID,GDK_CAP_BUTT,GDK_JOIN_MITER);
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor($ffff,0,$ffff,Widget));
|
||||||
|
seg:=(360 div 20) * 64;
|
||||||
|
For I:=1 to 20 do
|
||||||
|
gdk_draw_arc(win,gc,0,220-I*4,200-i*4,8*i,8*i,i*seg,seg*19);
|
||||||
|
For I:=1 to 20 do
|
||||||
|
gdk_draw_arc(win,gc,-1,380-I*4,200-i*4,8*i,8*i,(i-1)*seg,seg);
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,$ffff,Widget));
|
||||||
|
gdk_draw_polygon(win,gc,0,@triangle[1],4);
|
||||||
|
For I:=1 to 4 do
|
||||||
|
Triangle[i].Y:=400-Triangle[i].y;
|
||||||
|
gdk_draw_polygon(win,gc,-1,@triangle[1],4);
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,0,Widget));
|
||||||
|
For I:=1 to 4 do
|
||||||
|
Triangle[i].X:=600-Triangle[i].x;
|
||||||
|
gdk_draw_lines(win,gc,@triangle[1],4);
|
||||||
|
For I:=1 to 3 do
|
||||||
|
begin
|
||||||
|
SegTriangle[i].X1:=Triangle[i].X;
|
||||||
|
SegTriangle[i].Y1:=400-Triangle[i].Y;
|
||||||
|
SegTriangle[i].X2:=Triangle[i+1].X;
|
||||||
|
SegTriangle[i].Y2:=400-Triangle[i+1].Y;
|
||||||
|
end;
|
||||||
|
gdk_draw_segments(win,gc,@segtriangle[1],3);
|
||||||
|
font:=gdk_font_load('-*-helvetica-bold-r-normal--*-120-*-*-*-*-iso8859-1');
|
||||||
|
gdk_gc_set_foreground(gc,allocatecolor($ffff,$ffff,0,Widget));
|
||||||
|
For I:=1 to 4 do
|
||||||
|
gdk_draw_string(win,font,gc,I*100,300,Pchar(format('String %d',[i])));
|
||||||
|
result:=0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Begin
|
||||||
|
// Initialize GTK and create the main window
|
||||||
|
gtk_init( @argc, @argv );
|
||||||
|
window := gtk_window_new( GTK_WINDOW_TOPLEVEL );
|
||||||
|
gtk_window_set_policy(PgtkWindow(Window),0,0,1);
|
||||||
|
gtk_signal_connect (GTK_OBJECT (window), 'delete_event',
|
||||||
|
GTK_SIGNAL_FUNC( @CloseApp ), NIL);
|
||||||
|
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
|
||||||
|
area := gtk_drawing_area_new();
|
||||||
|
gtk_container_add( GTK_CONTAINER(window), Area);
|
||||||
|
gtk_signal_connect (GTK_OBJECT (area),'expose_event',
|
||||||
|
GTK_SIGNAL_FUNC(@Exposed),Nil);
|
||||||
|
gtk_drawing_area_size (PGTKDRAWINGAREA(area),600,400);
|
||||||
|
gtk_widget_show_all( window );
|
||||||
|
gtk_main();
|
||||||
|
end.
|
||||||
|
\end{lstlisting}
|
||||||
|
The main program startsby 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.
|
||||||
|
|
||||||
|
Note that the allocated colors are not freed again, so this program does
|
||||||
|
contain a memory leak.
|
||||||
|
|
||||||
|
\section{Animation}
|
||||||
|
|
||||||
|
|
||||||
|
\end{document}
|
Loading…
Reference in New Issue
Block a user