Tutorials

Andorra 2D GUI
Part 1 - Using the GUI system


Introduction

Virtually all professional games do not use the standard components that Windows offers to build up the user interface. The GUI system should be adapted to the game after all - a simulation of the middle ages does certainly not benefit from higgledy-piggledy Windows XP buttons. Also, it is difficult to display the standard components in a hardware-accelerated way. To give you the possibility of creating consistent GUIs which are fully integrated into your game, Andorra 2D provides a fairly powerful and in most cases sufficient GUI system. This can easily be extended with self-made components and is also fully skinnable.
Please note: Writing such a GUI system is very time-consuming, which is why its development is currently suspended. If you have created new components on your own or wish to extend the system, please send me an e-mail.


Preparations
First you have to create a GUI in the supplied GUI editor, which works in a similar way to Delphi. In the process you should save each form as a separate GUI file. (*.axg, Andorra XML GUI)
Skins can also be created with the supplied skin editor which, however, is still somewhat complicated at the moment. Anyhow, the supplied skin file "sunna.axs" can be modified to achieve the same result.
Apart from that, an Andorra GUI needs a cursor definition file. For this, create an image list with the according cursors with the image list editor and write a simple XML file according to the following example, in which you refer to the individual entries:

Delphi-Code:
<?xml version="1.0" encoding="iso-8859-1"?>
  <set>
    <images source="cursors.ail"/>
    <cursor name="default" src="default">
      <hotspot x="0" y="0"/>
    </cursor>
    <cursor name="wait" src="wait">
      <hotspot x="6" y="11"/>
      <anim start="0" stop="7" speed="25"/>
    </cursor>
    <cursor name="cross" src="cross">
      <hotspot x="8" y="8"/>
    </cursor>   
  </set>


Integration
To use the GUI in a programme now, we first need some units:
The base frame of all GUI components is located in "AdGUI" as well as the implementation of the cursors and the GUI manager. It is important to include the unit "AdComponents", if you use the supplied components. The reason for this is that with including the unit, the corresponding components are registered in the GUI system. If the file is not included, an error message is displayed because the specified components are not registered and therefore cannot be found.
First of all we need an instance of the class "TAdGUI", which takes care of loading, saving, skins and cursors automatically.

Delphi-Code:
AdGUI := TAdGUI.Create(AdDraw); //Instantiating the class
AdGUI.Skin.LoadFromFile('sunna.axs'); //Loading the skin
AdGUI.Cursors.LoadFromFile('cursors.xml'); //Loading the cursor
AdGUI.LoadFromFile('gui.axg'); //Loading the GUI

Important: Commonly made mistakes
- Forgetting the unit "AdComponents"
- Forgetting to include a PNG loader (such as AdPNG, AdDevIL or AdFreeImage), because the standard skin "Sunna" uses the format PNG to save the data.

Of course, "AdGUI" has to be freed in the end. One only has to call the method

Delphi-Code:
AdGUI.Update


in the game loop.
Technically, these steps should suffice to display the GUI. However, the emphasis lies on "display" - one can hit the mouse button as hard as one likes, nothing happens. Why would it? After all, mouse and keyboard events have to be passed on to the GUI system first. There are two possibilities for doing this. The first one is the easier one which should be enough in most cases.
We use the class "TAdGUIConnector" from the unit "AdGUIConnector". This loops the events of the AdDraw parent window system through the GUI system. For this, the following steps are necessary:

Delphi-Code:
AdConnector := TAdGUIConnector.Create(AdGUI); //Creating the GUI connector
AdConnector.ConnectEventHandlers(AdDraw.Window); //Linking it to the parent window system

The GUI connector should also be freed upon terminating the programme.
But the GUI connector (still) has a few flaws: One can only link the events to a component of the class "TForm" - if the AdDraw is on a panel, the events have to be passed on manually.
However, this method is not that bad and also has another inherent advantage: Imagine a construction game, in which freely movable windows are on the screen. How can the game know if we clicked on the window or on the game field? Not a problem with the manual linking!

Delphi-Code:
procedure Form1Click(Sender:TObject)
var
  p:TPoint;
begin
  GetCursorPos(p); //Read current cursor position
  p := ScreenToClient(p); //Convert to form-relative coordinates
  if not AdGUI.Click(p.X, p.Y) then //Execute event handling
  begin
   //The game field was clicked
  end;
end;

At this, the "Click" method of the GUI system is simply called, which recursively calls the click methods of all the other elements. When it is detected that the click was indeed on a control element, the "Click" method returns "true".
This works analogously with every other event handler.


Accessing control elements
Now it would be highly convenient if we could access the elements created in the editor in our programme somehow. In the editor, the elements are assigned a name which is stored in the property "Name" - just like in Delphi. The method "FindComponent" of TAdGUI returns the wanted element when given the corresponding name. To access component-specific properties, we only have to cast the result to the according component class.

Delphi-Code:
TAdButton(AdGUI.FindComponent('AdButton1')).Caption := 'Hello!';


Control element events
Our GUI system is still fairly inanimate - we do not react to events such as clicks at all yet.
For that we can simply link procedures to events such as "OnClick". We have to create a procedure that has exactly the same parameters as the event procedure. These can be found out in the code completion. Please note that the declared procedure has to be part of a class. Example:

Delphi-Code:
type
  TForm1 = class(TForm)
    [...]
  private
    procedure AdButtonClick(Sender:TObject);
  public
    [...]
end;

[...]

procedure TForm1.AdButtonClick(Sender:TObject);
begin
  TAdButton(AdGUI.FindComponent('AdButton1')).Caption := 'You clicked me!!';
end;

procedure TForm1.FormCreate(Sender:TObject);
begin
  //Initialise Andorra 2D
  [...]
  //Load GUI
  [...]

  //Assign events
  TAdButton(AdGUI.FindComponent('AdButton1')).OnClick := AdButtonClick;
end;

By the way, this works the same way in the VCL.

Customised components
To create customised components, derive a class from "TAdComponent" and register it in the "initialization" clause of the unit. Along the lines of "Code is worth a thousand words" you best learn how this works by reading the unit "AdComponents". To use your creations in the editor, you have to include the corresponding class there, too.

Extend code with a loaded GUI
Calling "LoadFromFile" usually replaces the whole GUI - this can be avoided with the following code. Please note that you have to include the unit "AdSimpleXML" additionally.

Delphi-Code:
procedure ExpandGUI(afile: string);
var
  elems:TAdSimpleXMLElems;
  xml:TAdSimpleXML;
  j:integer;
  cref:TPersistentClass;

  function SearchFirst(elem:TAdSimpleXMLElem):TAdSimpleXMLElems;
  var
    i:integer;
  begin
    result := nil;
    for i := 0 to elem.Items.Count - 1 do
    begin
      if elem.Items.Name <> 'TAdGUI' then
begin
result := elem.Items;
exit;
end
else
begin
result := SearchFirst(elem.Items);
if result <> nil then
begin
exit;
end;
end;
end;
end;

begin
xml := TAdSimpleXML.Create;
xml.LoadFromFile(afile);
elems := SearchFirst(xml.Root);
if elems <> nil then
begin
for j := 0 to elems.Count - 1 do
begin
cref := GetClass(elems.Item[j].Name);
if cref <> nil then
begin
with TAdComponent(TAdComponentClass(cref).Create(AdGUI)) do
begin
LoadFromXML(elems.Item[j]);
end;
end;
end;
end;
xml.Free;
end;

Conclusion
This information should help you deal with the GUI system. It is most important to take a look at the properties of the components. Don't be afraid to take a look at the implementation of a component - then emerging questions usually sort themselves out. After all, this is fairly easy in Delphi but also in Lazarus: If you click on an identifier while pressing the CTRL key, you automatically navigate to its declaration.

Copyright and licence
(c) by Andreas Stöckel November 2007
Translation by 3_of_8 (Manuel Eberl)

The content of this tutorial is subject to the
GNU Licence for Free Documentation


This page was generated with the help of the following PHP-Scripts: GeSHi a free PHP Syntax highlighter and StringParser_BBCode a free BBCode Parser.