SDL2的GUISAN库简介

简介

GUISAN是一个基于SDL2的开源的GUI控件库,原本是为了一个叫GUICHAN的小游戏而编写的框架。虽然和Qt、C#中的GUI控件没法比,但是他更加简单,可以更好的通过他的代码来进行框架的研究学习。

源码

官方的版本托管在kallisti5的github上,不过由于它是用Sconscript来编译的,对于大多数人来说不是很习惯,因此我把他用makefile重新编译了一遍,把静态库独立出来方便以后的使用,同时顺便删掉了一些文件,并且用doxygen生成了一个文档方便学习。

我把修改后的版本放在了我的github上。

简要分析

GUISAN的include文件夹内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
include
├── guisan
│   ├── actionevent.hpp
│   ├── actionlistener.hpp
│   ├── basiccontainer.hpp
│   ├── cliprectangle.hpp
│   ├── color.hpp
│   ├── deathlistener.hpp
│   ├── defaultfont.hpp
│   ├── event.hpp
│   ├── exception.hpp
│   ├── focushandler.hpp
│   ├── focuslistener.hpp
│   ├── font.hpp
│   ├── genericinput.hpp
│   ├── glut.hpp
│   ├── graphics.hpp
│   ├── gui.hpp
│   ├── imagefont.hpp
│   ├── image.hpp
│   ├── imageloader.hpp
│   ├── inputevent.hpp
│   ├── input.hpp
│   ├── keyevent.hpp
│   ├── key.hpp
│   ├── keyinput.hpp
│   ├── keylistener.hpp
│   ├── listmodel.hpp
│   ├── mouseevent.hpp
│   ├── mouseinput.hpp
│   ├── mouselistener.hpp
│   ├── opengl
│   │   ├── openglgraphics.hpp
│   │   ├── openglimage.hpp
│   │   └── openglsdlimageloader.hpp
│   ├── opengl.hpp
│   ├── platform.hpp
│   ├── rectangle.hpp
│   ├── sdl
│   │   ├── sdlgraphics.hpp
│   │   ├── sdlimage.hpp
│   │   ├── sdlimageloader.hpp
│   │   ├── sdlinput.hpp
│   │   ├── sdlpixel.hpp
│   │   └── sdltruetypefont.hpp
│   ├── sdl.hpp
│   ├── selectionevent.hpp
│   ├── selectionlistener.hpp
│   ├── widget.hpp
│   ├── widgetlistener.hpp
│   ├── widgets
│   │   ├── button.hpp
│   │   ├── checkbox.hpp
│   │   ├── container.hpp
│   │   ├── dropdown.hpp
│   │   ├── icon.hpp
│   │   ├── imagebutton.hpp
│   │   ├── label.hpp
│   │   ├── listbox.hpp
│   │   ├── radiobutton.hpp
│   │   ├── scrollarea.hpp
│   │   ├── slider.hpp
│   │   ├── tabbedarea.hpp
│   │   ├── tab.hpp
│   │   ├── textbox.hpp
│   │   ├── textfield.hpp
│   │   └── window.hpp
│   └── x.hpp
└── guisan.hpp

4 directories, 64 files

总体上来说,大概可以分为事件处理、基本控件和显示等辅助部分;对于显示部分,他这里不仅可以使用SDL2,还可以直接使用opengl。最后用一个头文件guisan.hpp对整个框架代码进行统一包含。

src文件夹和include文件夹的内容相互对应,不再细说。

事件处理

GUISAN的事件处理主要基于gcn::Event这个虚基类,派生的类图如下:

作为一个UI库,他设计的事件处理机制比SDL2相对庞大的机制相比已经简化很多了,也更加专注于与用户进行交互的事件。

基础控件

GUISAN的控件基本继承自gcn::Widget这个虚基类,派生的类图如下:

可以看到,控件都是用多重继承,既继承自Widget,又根据任务逻辑继承自各个事件监听器。这当中最常见的应该就是gcn::Container这个类了,这是存放所有其他控件的地方。当然,我们还可以根据需要自定义控件,例如上图中的FFXXX,这是demo里自定义的控件。

其他

除了上面这两个方面,GUISAN还提供了很多辅助的工具,比如gcn::Color,gcn::Exception,gcn::Image等等,以及一些必不可少的与SDL2相关的类。

样例

下面是GUISAN自带的最简单的例子,作为GUISAN框架使用的模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/**
* SDL Hello World example for Guichan.
*/

// Include all necessary headers.
#include <iostream>
#include <guisan.hpp>
#include <guisan/sdl.hpp>
#include "SDL2/SDL.h"

/*
* Common stuff we need
*/
bool running = true;

/*
* SDL Stuff we need
*/
SDL_Window* window;
SDL_Surface* screen;
SDL_Event event;

/*
* Guichan SDL stuff we need
*/
gcn::SDLInput* input; // Input driver
gcn::SDLGraphics* graphics; // Graphics driver
gcn::SDLImageLoader* imageLoader; // For loading images

/*
* Guichan stuff we need
*/
gcn::Gui* gui; // A Gui object - binds it all together
gcn::Container* top; // A top container
gcn::ImageFont* font; // A font
gcn::Label* label; // And a label for the Hello World text

/**
* Initializes the Hello World
*/
void init()
{
/*
* Here we initialize SDL as we would do with any SDL application.
*/
SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow("guisan SDL2 hello world",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480,
0);

screen = SDL_GetWindowSurface(window);

// We want to enable key repeat
//SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

/*
* Now it's time for Guichan SDL stuff
*/
imageLoader = new gcn::SDLImageLoader();
// The ImageLoader in use is static and must be set to be
// able to load images
gcn::Image::setImageLoader(imageLoader);
graphics = new gcn::SDLGraphics();
// Set the target for the graphics object to be the screen.
// In other words, we will draw to the screen.
// Note, any surface will do, it doesn't have to be the screen.
graphics->setTarget(screen);
input = new gcn::SDLInput();

/*
* Last but not least it's time to initialize and create the gui
* with Guichan stuff.
*/
top = new gcn::Container();
// Set the dimension of the top container to match the screen.
top->setDimension(gcn::Rectangle(0, 0, 640, 480));
gui = new gcn::Gui();
// Set gui to use the SDLGraphics object.
gui->setGraphics(graphics);
// Set gui to use the SDLInput object
gui->setInput(input);
// Set the top container
gui->setTop(top);
// Load the image font.
font = new gcn::ImageFont("fixedfont.bmp", " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
// The global font is static and must be set.
gcn::Widget::setGlobalFont(font);

// Create a label with test hello world
label = new gcn::Label("Hello World");
// Set the labels position
label->setPosition(280, 220);
// Add the label to the top container
top->add(label);
}

/**
* Halts the application
*/
void halt()
{
/*
* Destroy Guichan stuff
*/
delete label;
delete font;
delete top;
delete gui;

/*
* Destroy Guichan SDL stuff
*/
delete input;
delete graphics;
delete imageLoader;

/*
* Destroy SDL stuff
*/
SDL_DestroyWindow(window);
SDL_Quit();
}

/**
* Checks input. On escape halt the application.
*/
void checkInput()
{
/*
* Poll SDL events
*/
while(SDL_PollEvent(&event))
{
if (event.type == SDL_KEYDOWN)
{
if (event.key.keysym.sym == SDLK_ESCAPE)
{
running = false;
}
if (event.key.keysym.sym == SDLK_f)
{
if (event.key.keysym.mod & KMOD_CTRL)
{
printf("TODO: Toggle full screen!\n");
}
}
}
else if(event.type == SDL_QUIT)
{
running = false;
}

/*
* Now that we are done polling and using SDL events we pass
* the leftovers to the SDLInput object to later be handled by
* the Gui. (This example doesn't require us to do this 'cause a
* label doesn't use input. But will do it anyway to show how to
* set up an SDL application with Guichan.)
*/
input->pushInput(event);
}
}

/**
* Runs the application
*/
void run()
{
while(running)
{
// Poll input
checkInput();
// Let the gui perform it's logic (like handle input)
gui->logic();
// Draw the gui
gui->draw();
// Update the screen
SDL_UpdateWindowSurface(window);
}
}

int main(int argc, char **argv)
{
try
{
init();
run();
halt();
}
/*
* Catch all Guichan exceptions
*/
catch (gcn::Exception e)
{
std::cerr << e.getMessage() << std::endl;
return 1;
}
/*
* Catch all Std exceptions
*/
catch (std::exception e)
{
std::cerr << "Std exception: " << e.what() << std::endl;
return 1;
}
/*
* Catch all Unknown exceptions
*/
catch (...)
{
std::cerr << "Unknown exception" << std::endl;
return 1;
}

return 0;
}

编译的时候注意要将事先编译好的静态库加到PATH里或者写在编译命令里,并且要用-I参数包含头文件夹。

运行输出:

当然,下面这个样例展示了更多的控件: