ClanLib SDK

GUI Framework

ClanLib features a complete graphical user interface (GUI) toolkit. A GUI toolkit normally consists of a general GUI component framework, and then some standard components generally needed by most applications. In clanGUI, the different user interface elements are split into components, handled by CL_GUIComponent, where each component covers a specific part of the screen and performs a specific role. The components are arranged in a tree-like structure, which for a simple application may look like this:

Besides CL_GUIComponent, the other base components of the GUI framework are:

The GUI Manager

CL_GUIManager manages the environment in which GUI components run. The functions of the GUI manager can generally be split into these groups:

At the heart of the GUI system is the message queue and the message pump. All communication between the window manager and the GUI is done by queueing messages onto the message queue, which then are dispatched to the relevant GUI component. For example, if the user hits a key, this generates a CL_GUIMessage_Input message, which is sent to the currently focused component.

There are many situations where the application will want to filter messages, change where they are going, or simply perform other operations when there are no message to be dealt with. To maximize control over what happens between messages, the application can read and dispatch the messages itself, or it can call the default message loop via CL_GUIManager::exec().

But enough talking, here's how you create a GUI and pump the message queue:

// Create GUI environment:
CL_ResourceManager resources(..);
CL_GUIDefaultTheme theme;
CL_CSSDocument css_document(..);
CL_GUIWindowManagerSystem window_manager;
CL_GUIManager gui_manager;
gui_manager.set_resources(resources);
gui_manager.set_css_document(css_document);
gui_manager.set_window_manager(&window_manager);

// Create components:
CL_GUITopLevelDescription desc;
desc.set_title("ClanLib GUI");
CL_Window window(CL_Rect(0,0, 640, 480), &gui_manager, desc);
CL_PushButton button1(&window);
button1.set_geometry(CL_Rect(100, 100, 200, 120));
button1.set_text("Okay!");
button1.func_clicked().set(&on_button1_clicked, &button1);

// Pump the message queue:
CL_AcceleratorTable accelerator_table;
while (!gui_manager.get_exit_flag())
{
	CL_GUIMessage message = gui_manager.get_message();
	gui_manager.dispatch_message(message);
	if (!message.is_consumed())
		accelerator_table.process_message(message);
}

void on_button1_clicked(CL_PushButton *button)
{
	button->exit_with_code(0);
}

The message pump above does exactly the same as CL_GUIManager::exec, so if you do not need any additional processing, you can just call that one.

Components

As mentioned in the introduction, the screen is split into components, which are rectangular areas where each component draws itself and processes input. Components can be split into two types: top-level components and child components.

The top-level components are constructed using a CL_GUITopLevelDescription class, which is passed on to the window manager for further interpretation. The system window manager creates a CL_DisplayWindow for each top-level component, where the information in the description class is used to give that window a title, icon, border and other styling properties. The texture window manager creates a new texture for the window and ignores most of the other properties of the description.

Child components are constructed by only passing a parent component to the constructor of CL_GUIComponent. The window manager is not aware of child components and does not require any special extra data to construct them. A child component is initially created at (0,0) with a (0,0) size, which means that you will have to explicitly set the geometry of the component after you created it.

Component Messages

The GUI system communicates with the component using GUI messages. They are dispatched to the component, which means that CL_GUIManager::dispatch_message() invokes the callback given by CL_GUIComponent::func_process_message(). It can receive messages of the following types:

When the GUI system needs the component to paint itself, it will invoke CL_GUIComponent::func_render(). Likewise, CL_GUIComponent has other callbacks you can hook into to react to certain events.

Example of a component that changes color if mouse is hovering above it:

class MyComponent : public CL_GUIComponent
{
public:
	MyComponent(const CL_GUIComponent *parent)
	: CL_GUIComponent(parent), above(false)
	{
		set_type("MyComponent");
		func_render().set(this, &MyComponent::on_render);
		func_process_message.set(this, &MyComponent::on_process_message);
	}
	
private:
	void on_render(CL_GraphicContext &gc, const CL_Rect &clip_rect)
	{
		CL_Rect client(CL_Point(0,0), get_geometry().get_size());
		CL_Draw::fill_rect(
			gc,
			client,
			above ? CL_Colord::darkkhaki : CL_Colord::lightgoldenrodyellow);
	}
	
	void on_process_message(const CL_GUIMessage &message)
	{
		if (message.is_type(CL_GUIMessage_Pointer::get_type())
		{
			CL_GUIMessage_Pointer msg_pointer = message;
			switch (msg_pointer.get_pointer_type())
			{
			case CL_GUIMessage_Pointer::pointer_enter:
				above = true;
				break;
			case CL_GUIMessage_Pointer::pointer_leave:
				above = false;
				break;
			}
			// Cause repaint of component:
			request_repaint();
		}
	}

	bool above;
}

Using clanGUI Compared to clanDisplay Rendering

One of the things that confuse people the most when it comes to the GUI is how the screen is updated. In particular, if you are attempting to port an application using clanDisplay to clanGUI, your application typically does something along these lines:

CL_GraphicContext gc = displaywindow.get_gc();
while (!exit)
{
	update_game_logic();
	render_game(gc);
	displaywindow.flip();
	CL_KeepAlive::process();
}

A common mistake is to assume that if you want to render the GUI, you can simply add a call to gui_manager.exec() just before the displaywindow.flip() line. This will not work. The gui_manager.exec() function will not exit until something in the GUI calls CL_GUIComponent::exit_with_code(), causing only the GUI to be rendered and your game will disappear and hang.

The best way to fix this problem is to get rid of your own while(!exit) loop and create your game screen as a CL_GUIComponent. Since your game probably wants to constantly repaint itself, you can use the CL_GUIComponent::set_constant_repaint() function to achieve this. Also, depending on how often your game logic is to be run, you can either place this in the render callback function or use a CL_Timer to call it at desired intervals.

Here's a simple example how how a game component might look like:

class GameWindow : public CL_Window
{
public:
	GameWindow(CL_GUIManager *manager, const CL_DisplayWindowDescription &desc)
	: CL_Window(desc)
	{
		func_render().set(this, &GameWindow::on_render);
		set_constant_repaint(true);
		
		button = new CL_PushButton(this);
		button->set_geometry(CL_Rect(10, 10, 150, 25));
		button->set_text("A button on top of my game!");
	}

private:	
	void on_render(CL_GraphicContext &gc, const CL_Rect &clip_rect)
	{
		update_game_logic();
		render_game(gc);
	}
	
	CL_PushButton *button;
};