1. Computing

An SDL GUI for Empire Tutorial Two

Implementing the GUI


Screenshot of Empire Map with Controls

This is another tutorials on the Empire game. If you've found this article first, please see part one. Here's more information about the Empire Game.

In the previous SDL Gui tutorial I described the SDL GUI controls and this time I'm implementing them and using them in the Empire game.

I decided to create a family of structs, one for each control. As C doesn't have inheritable structs (like classes), I used the preprocessor macro. I'm using Visual Studio 2010 so a bit of digging found the C/C++ Preprocessor Reference page.

The idea is declare each struct with a common element defined by the macro and then just add the bits that make that control different. This bit of somewhat confusing code does that.

Note, if you are creating your own SDL project (for these sources in Visual Studio 2010/2012 or Visual C++ Express 2010/Visual Studio 2012 Express), then you should see How to setup Visual Studio 2010/Visual C++ 2010 Express with SDL.

enum controltype {esdlpanel,esdlbutton,esdlclickabletext,esdlcheckbox,esdllistbox,

#define sdlbase enum controltype ctype;\
int x,y,width,height,color;\
void (*pRender)(struct sdlcontrol * self) ;\
void (*pFree)(struct sdlcontrol * self) ;\
void (*pClick)(struct sdlcontrol * self) ;\
struct sdlcontrol* nextcontrol

struct sdlcontrol { sdlbase; };
typedef struct sdlcontrol * psdlcontrol;
typedef char * pchar;

struct sdlbutton {
    pchar labeltext;
    int labelsize;
    int labelcolor;

The multi-line macro sdlbase defines common elements in all controls including the panel. All controls are held in a big single linked list. The first one is the panel that holds all the controls and is white in the image above.

The controltype enum is stored in each control struct to identify the type and switch accordingly. For the first one (the panel), the x and y in that are absolute, i.e relative to the screen. For all other controls the x and y are relative to the panel and converted in the AddControl function.

Note for this version. I am using the existing single font single color text drawing. It's a fair bit of effort to use TrueType and it requires setting up the FreeType font system as well so I'm going to do that in another tutorial soon. The GUI will use TTF eventually and look much better than this (see View larger image.

Using a Macro

The sdlbase macro contains six lines. It would fit on one line but using the continuation character \ before the newline end makes it much more readable. Lines 1,2 and 4 are easy enough but it's the function pointers that are interesting. There is one for rendering the control, one for freeing up the control and one to handle clicks.

Note that these each take a pointer to a sdlcontrol struct, making it more like an object method and provides access to that control's width, height, x,y etc.. For controls with extra fields (e.g. sdlbutton), the self parameter has to be cast to to sdlbutton etc to access these extra fields.

The library is initialized to a call to initsdlguilib() which returns a pointer to a sdlcontrol struct. There's no sdlpanel ase it just uses the common elements. The typedef psdlcontrol is easier to write than struct sdlcontrol *.

Restructuring Empire

Because the sdlgui code needed to use SDL functions, it made sense to move a lot of the SDL code (print etc) plus common types and variables used into sdlgui.c with types, constants etc used in empire.c put into sdlgui.h and variables in sdlgui.c. And in empire.c those variables are now declared as extern.

So I moved for instance:

const char * imagenames[NUMIMAGES]= {"text.gif","maphexes1.gif","maphexes2.gif",};
const int sizesx[2]= {32,64};
const int sizesy[2]= {34,68};
const int xdir[8]= {0,1,1,1,0,-1,-1,-1};
const int ydir[8]= {-1,-1,0,1,1,1,0,-1};

from empire.c to sdlgui.c and in empire.c those became:

extern const char * imagenames[NUMIMAGES];
extern const int sizesx[2];
extern const int sizesy[2];
extern const int xdir[8];
extern const int ydir[8];

Initializing the Panel

This code below is used to initialize the library. As it always has a panel, this is created first and controls added afterwards.

psdlcontrol initsdlguilib(int x,int y,int width,int height,int color){
    psdlcontrol panel = (psdlcontrol)malloc(sizeof(struct sdlcontrol)) ;
    panel->ctype= esdlpanel;
    panel->x = x;
    panel->y = y;
    panel->width = width;
    panel->height = height;
    panel->color = color;
    panel->pRender = &RenderPanel;
    panel->pFree = &FreeControl;
    panel->pClick = NULL;
    panel->nextcontrol= NULL;
    panelx = x;
    panely = y;
    list = panel;
    return panel;

The panelx and panely values are offsets used to convert panel relative control x,y coordinates to global. Some controls such as a button have their own memory allocated for text strings etc.

Displaying the Panel

The RenderGUI(sdllib lib) function call walks the list of controls and calls the pRender function on each. There has to be one defined for each control type. I call them RenderPanel, RenderButton etc.

The Button Control

Defined by this struct below, it includes the pchar labeltext, and three ints for labelsize, color and font. Once the ttf code is working these three ints will be used but for now, just the labeltext is needed.

struct sdlbutton {
    pchar labeltext;
    int labelsize;
    int labelcolor;
    int labelfont;

In the example, two buttons are added to the panel with this code:

background = SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF );
black = SDL_MapRGB( screen->format, 0, 0, 0 );
panel = initsdlguilib(800,10,220,HEIGHT-80,background);
button=(struct sdlbutton *)addbutton(10,50,140,24,"Show Map",black,clickbutton);
button=(struct sdlbutton *)addbutton(10,90,140,24,"Reshroud",black,clickbutton2);

The clickbutton() and clickbutton2() are the event handlers called when the control is clicked.

It's Ok to Upcast

In this code below the addcontrol function uses psdlcontrol but behind the scenes it mallocs enough memory to hold an sdlbutton, even though it returns a psdlcontrol. Just to be clear, it returns a pointer to a struct sdlcontrol but is actually a pointer to an sdlbutton which is bigger by at least 16 bytes. When we render the button with this code below, self is a psdlcontrol but is actually pointing to an sdlbutton.

void RenderButton(psdlcontrol self) {
  int result;
  char buff[60];
  struct sdlbutton * button= (struct sdlbutton *)self;
  SDL_Rect rect= {(Sint16)self->x,(Sint16)self->y,(Uint16)self->width,(Uint16)self->height};
  result=SDL_FillRect( screen, &rect, self->color ) ;
  sprintf(buff,"%s",button->labeltext) ;
  print(button->x,button->y,buff) ;

For most of this function we just use the sdlcontrol part to draw the rectangle but to get access to the text it casts self to a struct button * and then can access the labeltext.

Handling Mouseclicks

How do the sdlcontrols react to clicks? In InitSetup, the pane was set to be at x = 800, with a width of 220 and a height from y=10 to 768-80 = 688. In the GameLoop function, the SDL_MOUSEBUTTONDOWN event was extended to call a new function CheckControlClikAt(int x,int y).

This function walks the control linked list, skipping the panel and checks if the x and y mouse click coordinates are within the rectangle of any control. If they are and there's a pClick handler defined (it's non-zero) then it's called. In the example, the two buttons and hooked up each to the functions called by hitting t and s. One shows random locations by poking holes in the shroud and the other reshrouds everything.

Tidying Up

I've added code to tidy up the controls, using a switch to decide what gets tidied up. It uses a function pointer again though in this case, all controls are tidied by a function called FreeControl that's called for each control in a loop from the freesdlguilib function.


This tutorial showed how to get a crude looking GUI working. I have to improve the look of the GUI with true type and add in the other controls. That will happen in future tutorials.

©2014 About.com. All rights reserved.