| -Incorporating 2D elements- |
| All the 3D scenes rendered, are quite unusable without a decent interface to present to the user. True, the interface itself could be a 3D scene, but I am talking about the norms, and not about changing convention. One of the most basic 2D elements is text. One could use wglUseFontBitmaps, but it is rather slow. One of the fastest ways to draw text onscreen is to use the 3D rasterizer to display texturemapped quads, each quad of which has a different character mapped onto it. An excellent tutorial on the subject is on NeHe's website. If you want to check it out, click here. In this tutorial, I will describe an architecture for rendering interface elements (and using them). |
| First off, we all need a base class that all UI objects will use. I have called this not quite surprisingly, TUIElement. The declaration is given below. |
TUIClickEvent=procedure (Sender:TUIElement) of object;
TUIElement=class
private
//Top,Left, Width and Height of the element
fX,fY,fWidth,fHeight:integer;
//Is the mouse over it?
fHighlighted:boolean;
//Frame Update stuff
fFrameInterval,fLastTickCount:cardinal;
//Enabled/Disabled
fEnabled:boolean;
//Click Event
fOnClick:TUIClickEvent;
//The name of the object
fName:string;
public
constructor Create;
//Desscendants can override Update to perform
//Time keyed animation or whatever
function Update(TickCount:cardinal):boolean;virtual;
//All descendants MUST render themselves
procedure Render;virtual;abstract;
//Call this if you want to simulate
//a click on the button
procedure DoClick;virtual;
//DUH!!
property Name:string
read fName
write fName;
property X:integer
read fX
write fX;
property Y:integer
read fY
write fY;
property Width:integer
read fWidth
write fWidth;
property Height:integer
read fHeight
write fHeight;
property Highlighted:boolean
read fHighLighted
write fHighlighted;
property Enabled:boolean
read fEnabled
write fEnabled;
property OnClick:TUIClickEvent
read fOnClick
write fOnClick;
end;
|
| Two methods merit description. One is the Update method, and the other is DoClick. |
procedure TUIElement.DoClick;
begin
//If the click event handler is assigned,
//then it is invoked
if assigned(fOnClick) then fOnClick(Self);
end;function TUIElement.Update(TickCount: cardinal): boolean;
begin
//Set result to false initially
result:=false;
//If the object is disabled, then dont update.
//This means successors will also not Update since
//all of them should check inherited result before
//proceeding with their Update
if not fEnabled then exit;
if (TickCount-fLastTickCount)>=fFrameInterval then
begin
//fFrameInterval ticks have passed since the lase Update
//Updation can now take place
fLastTickCount:=TickCount;
result:=true;
end;
end;
|
| Now that we are done with the base class, lets see how a basic rollover is implemented as a descendant. Basically, a rollover must do two things. Determine whether the mouse is on itself, and change the image accordingly, and respond to a click in its client area. Here comes the declaration. |
TRollOverImage=class(TUIElement)
private
//The texture that is not highlighted
fBackTex,
//The highlight texture
fForeTex:TTexture;
public
constructor Create;
destructor Destroy;override;
procedure Render;override;
property BackTex:TTexture
read fBackTex
write fBackTex;
property ForeTex:TTexture
read fForeTex
write fForeTex;
end;
|
| Looks pretty simple, since almost all the properties/methods required have been taken from the base class. We shall take a look at the Render method implementation as well, since to write subsequent classes we require knowlodge of how this is rendered. All this method does is to render the background quad, and if the Hilighted property is set, then it renders another quad on top of this one, using the foreground texture Alpha channel information to blend the two together. That translates to a TTexture.Transparency call or a glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) call. Take a look at the code now. |
fBackTex.Enable;
fBackTex.Opaque;
DrawQuad(fX,fY,fWidth,fHeight); if fHighlighted then
begin
fForeTex.Enable;
fForeTex.Transparency;
DrawQuad(fX,fY,fWidth,fHeight);
end;
|
| From here, we can go anywhere. Take a look at TRolloverButton.Render. It's almost exactly the same as TRolloverImage. Pretty darn simple to modify, wouldnt you say! |
if Enabled then
inherited Render
else
begin
fDisabledTex.Enable;
fDisabledTex.Opaque;
DrawQuad(X,Y,Width,Height);
end;
|
| I'm not going to explain TRolloverCheckBox. I leave that as an excercise to the reader. What I will do, however, is to explain another class that is crucial to the entire setup. |
| Alright, we now have some interface elements. If we are going update each element separately whenever the mouse moved, or was clicked, then we'd have some extremely painful code on our hands. So, what we do is write another class called TUIManager. This class will be responsible for managing the objects once added. So, the mouse events have only to be given to the manger, and this will make sure it gets filtered to the element on which the event occurred. Another declaration? |
TUIManager=class
private
//The elements are maintained as a TList fElements:TList;
public
constructor Create;
destructor Destroy;override;
//Mouse move should be called when the mouse moves
procedure MouseMove(X,Y:integer);
|
| That was pretty small, was'nt it? There! We're done. Now we can show some stuff on the screen so the user can make choices. Lucifer will have a lot more input classes, I just wanted to keep the tutorial simple. The tut sample is very spartan, so I'll sxplain it a bit. There are two interface elements, a button and a checkox (yeah, the round thingy). Checking the checkbox, disables the button and vice versa. Clicking on the button invokes a dialog. simple, huh! Its really late. I'm off to bed. Good thing todays Sunday. I ca nsleep all day! :) ZZZZZZZ |
| >>Download the
tutorial source You'll also need, in case you dont have it already >>Download the Lucifer units (required) |