UEFI开发探索45 – GuiLite概览

请保留-> 【原文:  https://blog.csdn.net/luobing4365http://yiiyee.cn/blog/author/luobing/】

在开始这个探索系列的时候,我就计划在UEFI上移植一个完整的GUI库。

前面开发各种图形编程、特效实现的代码,其实有很大一部分来自于我之前另外一个项目-Foxdisk。在这个项目中,某种程度上实现了任务的切换,可以看做一个小型的、带有图形显示的shell界面。

不过,Foxdisk中的键盘处理是完全脱离于图形的,而且也没有实现鼠标的处理。简而言之,这是一个很松散的、模块化的GUI库。与我期望的,类似于MFC、QT、JUCE之类的库相差甚远。

UEFI不是一个完整的操作系统,所选用的GUI库实际上最好偏向于嵌入式的。在简单考察了几个开源的GUI库后,包括LearingGUi、GuiLite、littlevgl等,我选择了从GuiLite开始着手这项工作。

一方面是因为其代码量看起来不大,6000行左右,花费的时间应该不会太多;二是这款开源库是国人开发的,我也加入了作者建的QQ群,有什么问题可以很方便地请教。

1 GUILite类的组织

按照其文档介绍,GuiLite只做两个工作:界面元素管理和图形绘制。而图形绘制不依赖于界面管理,可以独立存在,以应对需要移植到资源有限的单片机环境。

其类的组织图如下:

图1 GUILite类图

GUILite的类图有点像MFC的结构,深入进去也能发现,其消息处理机制也很像MFC。作者提炼了需要的部分,做成了一个精简的、适用多平台的图形库,非常了不起。

c_cmd_target类主要用来处理消息的定位,核心函数是find_msg_entry();

c_wnd类定义了窗口的基本框架,大部分的控件类,包括c_dialog、c_button等均派生于它;

c_display和c_surface主要用来实现图形的虚拟绘制。所谓虚拟绘制,也就是说并不在实际硬件上绘制,而是定义内存缓冲区,在缓冲区内将像素点排列好。也就是提供了类似Linux的Framebuffer中间层机制,应用开发人员可以直接将缓冲区的数据写入到硬件中。

c_bitmap和c_word类实现了bmp图的绘制(仍旧将数据写入内存缓冲区)和文字的绘制; 大致的脉络就是如此,核心的消息传送和图形绘制,看懂了这几个类,就没有太大的问题了。

2 消息机制

GUILite的窗口(c_wnd)实例,在运行的时候,是全部联系在一起的。这是构建好的一个大链表,所有的消息(包括鼠标点击、键盘消息等)都可在此链表中溯源,找到其从属的窗口。

在c_wnd类中,包含以下成员变量:
c_wnd*                     m_parent;
         c_wnd*                     m_top_child;
         c_wnd*                     m_prev_sibling;
         c_wnd*                     m_next_sibling;

分别为其父窗口、第一个子窗口、前兄弟窗口和后兄弟窗口,链表就是依赖这些类指针实现的。

与消息机制相关的另一关键数据结构为WND_TREE,其定义如下:
typedef struct struct_wnd_tree{
         c_wnd*                                      p_wnd;
         unsigned int                       resource_id;
         const char*                                str;
         short                               x;
         short                                   y;
         short                                   width;
         short                          height;
         struct struct_wnd_tree* p_child_tree;
}WND_TREE;

它相当于窗口的资源文件,包含了窗口实例的指针、资源ID、字符串等信息,窗口链表的构建就是基于用户提供的此信息来实现的,核心的实现函数为c_wnd::connect()。

整个机制建立的步骤如下:

1) 构建WND_TREE型的数组,比如:

图2 窗口树示例(摘自例程HelloWidget)

需要注意的是,数组最后一项必须为空,程序在扫描过程中,是以此作为结束判断的。

2) 使用connect()函数将所有窗口连接起来。比如:

s_my_ui.connect(NULL, ID_ROOT, 0, 0, 0, UI_WIDTH, UI_HEIGHT, s_main_widgets); (摘自HelloWidget的Uicode.cpp line166)。

此函数执行后,以s_my_ui为父窗口,上述所有的窗口(s_edit1、s_edit2等)均通过自身的成员变量m_parent、m_top_child、m_prev_sibling、m_next_sibling连接起来了。

3) 消息处理。

GUILite中准备了消息处理的回调机制,主要通过下面的宏和处理函数find_msg_entry()(c_cmd_target类的成员函数)来实现的:

图3 消息宏(摘自例程HelloWidget)

数据结构GL_MSG_ENTRY中包含了消息ID和对应的回调函数。在c_wnd的成员函数notify_parent中调用find_msg_entry,根据消息ID调用回调函数。

建立了上述窗口链表和消息机制后,所有按键和手势消息(比如鼠标、触摸板等)均可以在链表中回溯,直到找到从属的窗口,调用相应的处理函数。

3 程序编写要点

GUILite目前还在维护中,所以随时有可能更新。不过,大部分的机制已经建立起来了,后续的应用代码编写流程都差不多。

一般可遵循以下步骤编写:

1) 构建自己的窗口类和显示需要的窗口元素,包括各种控件,通过connect函数将这些窗口联系起来;

2) 在编写的窗口类中实现消息处理函数,特别是对鼠标、键盘消息的处理;

3) 编写平台相关的处理函数。包括与平台的鼠标消息、键盘消息对接,以及图形的绘制等。以MFC的鼠标左击消息为例,它是建立了WM_LBUTTONDOWN消息的处理函数OnLButtonDown,应该在此函数中建立与GuiLite窗口类的on_touch的对应关系。

其余的细节不一一阐述了。学习到这儿,我觉得已经可以着手进行UEFI的移植工作了,下一篇先建立起支持C++的UEFI程序框架,完成最初的想法。

( 学习过程中问了不少问题,在此感谢作者的耐心指点^^  开源代码地址:
https://gitee.com/idea4good/GuiLite/blob/master/documents/HowToWork-cn.mdhttps://github.com/idea4good/GuiLite)

791 total views, 1 views today

《UEFI开发探索45 – GuiLite概览》有3个想法

发表评论

电子邮件地址不会被公开。