mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-20 15:49:27 +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