官方代码($sdk)\examples\05.UserInterface
这个例子是讲irr引擎的用户图形界面。里面只演示了窗口、按钮、滚动条、静态字体和列表框这几样常用的图形界面元素,其实这已经是irr用户图形界面的大部分内容了。irr用户图形界面,个人认为是鸡肋。用它,它太难看了,想要让它好看,得自己写扩展,这基本等于用irr的接口规范自己从新做一遍用户图形界面的基本元素。不用,得自己重新找一套GUI来和irr结合,使用起来不一定有irr原生的那么简单。下面看具体如何使用irr的GUI。
使用GUI一般都会产生一些用户反馈的交互信息,这些信息就是GUI事件。要想对这些GUI事件进行处理,首先就得获取这些事件。获取GUI事件最简单的方法就是上一个例子里用到的事件接收器,事件接收器所接收的事件就包括GUI事件,只是上个例子里没有使用而已。本例里同样自定义了一个MyEventReceiver事件接收器,只是这次的OnEvent方法里处理的事件换成了EET_GUI_EVENT事件。接收到的GUI事件是以SGUIEvent数据结构存储的。
structSGUIEvent
{
产生GUI事件的GUI元素Caller
gui::IGUIElement*Caller;
与Caller一起产生事件的GUI元素,一般不用
gui::IGUIElement*Element;
GUI事件的类型
gui::EGUI_EVENT_TYPEEventType;
};
GUI事件类型定义在EGUI_EVENT_TYPE枚举里
enumEGUI_EVENT_TYPE
{
失去焦点
EGET_ELEMENT_FOCUS_LOST= 0,
获得焦点
EGET_ELEMENT_FOCUSED,
光标停留在GUI元素上
EGET_ELEMENT_HOVERED,
光标离开元素
EGET_ELEMENT_LEFT,
元素关闭
EGET_ELEMENT_CLOSED,
按钮被点击
EGET_BUTTON_CLICKED,
滚动条被移动
EGET_SCROLL_BAR_CHANGED,
复选框发生变化
EGET_CHECKBOX_CHANGED,
列表框发生变化
EGET_LISTBOX_CHANGED,
列表框被再次选择
EGET_LISTBOX_SELECTED_AGAIN,
文件被选择
EGET_FILE_SELECTED,
目录被选择
EGET_DIRECTORY_SELECTED,
文件对话框选择取消
EGET_FILE_CHOOSE_DIALOG_CANCELLED,
消息框YES被按下
EGET_MESSAGEBOX_YES,
消息框NO被按下
EGET_MESSAGEBOX_NO,
消息框OK被按下
EGET_MESSAGEBOX_OK,
消息框取消被按下
EGET_MESSAGEBOX_CANCEL,
文本编辑框里按下回车键
EGET_EDITBOX_ENTER,
文本编辑框内容改变
EGET_EDITBOX_CHANGED,
文本编辑框标记区域改变
EGET_EDITBOX_MARKING_CHANGED,
TAB键控着元素改变
EGET_TAB_CHANGED,
菜单项被选中
EGET_MENU_ITEM_SELECTED,
组合框改变
EGET_COMBO_BOX_CHANGED,
旋转框改变
EGET_SPINBOX_CHANGED,
表格改变
EGET_TABLE_CHANGED,
表格头改变
EGET_TABLE_HEADER_CHANGED,
表格被再次选中
EGET_TABLE_SELECTED_AGAIN,
观察树节点失去选择
EGET_TREEVIEW_NODE_DESELECT,
观察树节点被选中
EGET_TREEVIEW_NODE_SELECT,
观察树节点被展开
EGET_TREEVIEW_NODE_EXPAND,
观察树节点被收拢
EGET_TREEVIEW_NODE_COLLAPSE,
EGET_TREEVIEW_NODE_COLLAPS= EGET_TREEVIEW_NODE_COLLAPSE,
GUI事件总数
EGET_COUNT
};
从GUI事件的数据结构可以看出,GUI事件很简单,通过区分GUI事件类型,可以很轻易的筛选出自己关心的GUI事件,再从产生GUI事件的Caller,就可以明确这个事件是不是自己关心的元素产生的。
例子里因为使用的GUI元素较少,同时为了使例子看似去更加简单明了,它为GUI元素设定了几个固定ID,如:GUI_ID_QUIT_BUTTON、GUI_ID_NEW_WINDOW_BUTTON、GUI_ID_FILE_OPEN_BUTTON和GUI_ID_TRANSPARENCY_SCROLL_BAR。这样就不必用全局变量或其他的方法保存动态生成的GUI元素的指针用来筛选是不是自己关注的元素,直接通过Caller的ID就可以区分出来。但这方法也有缺点,如果GUI是在程序外通过配置文件配置的,不同功能的GUI很有可能使用了相同的ID,这样会导致错误。
创建GUI元素的方法,在《HelloWorld!》例子里已经出现过。首先就是IGUIEnvironment*env =device->getGUIEnvironment(),通过irr设备获取GUI环境;然后再通过GUI环境的addXXXX方法添加不同的GUI元素。
GUI的字体设置,这也是个重要问题。在例子里字体设置部分如下:
获取环境GUI的默认皮肤
IGUISkin*skin = env->getSkin();
读取一个外部字体库
IGUIFont*font = env->getFont("../../media/fonthaettenschweiler.bmp");
将该外部设为默认皮肤的字体
if(font)
skin->setFont(font);
设置提示文字的字体为内嵌的标准字体
skin->setFont(env->getBuiltInFont(),EGDF_TOOLTIP);
但这方法只适合英文,如果要使用其他的文字,得对irr进行文字的扩展,这种载入一张图片作为字库的方法肯定是不行的。
在例子中,还有设置GUI透明度的方法。这部分在自定义的MyEventReceiver类的OnEvent方法中。捕获到EGET_SCROLL_BAR_CHANGED事件后,如下操作。
获取滚动条的位置,将该位置作为设置GUI透明度的参数
s32pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
对皮肤里每一种颜色重设透明度
for(u32 i=0; i<EGDC_COUNT ; ++i)
{
获取皮肤的第i个颜色
SColorcol = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
设置颜色的透明度
col.setAlpha(pos);
设置皮肤的第i个颜色
env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i,col);
}
在irr里还可以自己扩展IGUIElement,创建出引擎没有的GUI元素,在IGUIElement的OnEvent方法里,可以直接从消息循环里直接获取GUI事件,对自己扩展的GUI元素里的子元素事件直接进行相应的处理,不必通过事件接收器处理。如果嫌irr的GUI实在难看的话,可以在自己扩展的元素里,用重载draw方法,写一个更漂亮的GUI元素出来。不过这种重载draw方法来使GUI更漂亮的方法也有致命缺陷,就是不能灵活的设计出自己想要的界面,总不能对每一次的一小点需求变化都来进行一次draw的重载吧!如果要强行解决irr界面的美观和灵活性,这就不再是对irr扩展了,而是需要对irr进行改造了。