天天动画片 > 八卦谈 > DirectUI GUI库设计与实现概述

DirectUI GUI库设计与实现概述

八卦谈 佚名 2024-03-09 02:24:22

Software->Desktop->C++ GUI

对于传统Win32 GUI编程,微软提供一整套UI标准,比如窗口、按钮、滚动条、列表等,每一个窗口(控件也是窗口)都有一个句柄(window hwnd),其能响应的消息和行为都有规范,UI绘制效果能在某种程度上扩展,但因整体框架已定,想实现某些特效限制很大。

微软在实现某些内部软件如MSN时,其Win32窗口整体只有一个DirectUIHWND句柄(spy++监控),内部的控件没有句柄,为逻辑上的子窗口,能不受Win32框架限制实现更多更炫的UI渲染。虽然DirectUIHWND技术未面向开发者开放,但这种UI理念被命名为DirectUI并沿用下来。

DirectUI是一种UI开发思想,代表从零构建一套UI框架,其UI控件都为逻辑上的窗口,没有句柄(windowless),最大自由的实现渲染效果。

大公司自有产品很多采用自有DirectUI库开发,如腾讯的qq。事实上,DirectUI是一个桌面时代比较老的概念,WIn32时代的产物。如果从DirectUI的思想去发散思维,PC游戏,WEB前端都属于天然的DirectUI,如大名鼎鼎的VS Code作为桌面软件实际是WEB技术开发。

武汉嘉迅光电的AmazeUI库是一个DirectUI库,约2010年初始自主开发,现已开源。如何从零构建一套DirectUI库,以AmazeUI为例进行概述。

Git地址:https://e.coding.net/biyou/AmazeUI/AmazeUI.git

AmazeUI目录结构


Step1:2D基础元素绘制:点、线、矩形、字体、图片、区域裁剪

底层绘制API选用DirectX9.0C(能支持XP),在DirectX 9.0中去掉了DirectDraw(存在于DirectX 8.0中),只能选用D3D来完成2D绘制。

点、线、矩形的绘制调用API-DrawPrimitiveUP(),对D3DFVF_XYZRHW类型的顶点数据进行操作。通过该API函数,Direct3D不执行世界、观察和透视变换以及光线运算,它会认为顶点已经经过了这些处理(即顶点的X、Y坐标值等于WIN32窗口上的像素点坐标值),并直接将顶点传递给驱动器进行光栅操作。该方法实现简单,缺点为UI效果被局限到了2D。

下面代码片段为矩形的绘制,绘制4条线,线以顶点形式描述。

矩形绘制代码片段

图片绘制基于ID3DXSprite,字体基于ID3DXFont和ID3DXSprite。区域裁剪是个非常有用的功能,限定了绘制区域,调用API-SetScissorRect()实现,在进行多次区域裁剪时需计算最终的相交区域。最终2D基础元素绘制效果如下:

2D元素绘制


Step2:UI线程,实现消息循环处理机制

UI除了信息呈现外,还需与事件进行交互(事件和消息一一对应),交互分两种:

1)对外部设备事件(包括鼠标、键盘等)及时响应。

2)对内部通告事件(如UI上数据更新等)及时反馈。外部设备事件和内部通告事件都定义为消息。


消息循环处理机制要实现以下功能:

1)管理一个消息队列,能push或pop消息(通过信号量互斥进行保护)。

2)发起一个线程对消息进行循环处理,该线程被称为UI线程。

3)消息循环处理的时间间隔为33ms,如果每个33ms都触发绘制,则一秒有30帧绘制。


UI线程有一个非常重要的特性,就是对事件进行串行处理,理论上所有与UI相关的操作都需要放到UI线程中进行,实现互斥保护。串行处理事件能避免数据错误,设想一个场景,一个表格控件Gird在绘制过程时,如果并行删除了几行数据,绘制继续进行可能因数据消失而程序崩溃。

DirectUI消息仿Win32消息,将窗口句柄换为了逻辑窗口指针。

DirectUI消息

Step3:实现动画机制

绚丽的UI需要动画效果支持,UI动画分几种情况:

1)类游戏角色动画。

2)UI组件动画,如插入符的闪烁效果。

3)UI控件动画,显示控件过渡动画效果。其中1和2都是纯粹的动画。


定义动画基类,所有的动画效果都从该动画基类派生。

定义动画管理类,实现以下功能:

1)添加或删除动画,内部维护一个动画对象队列。

2)在消息循环处理中,每一帧对动画队列进行处理,判断本帧是否需要绘制动画,删除已经绘制完成的动画。

3)绘制本帧所有动画。(控件动画例外,控件动画在控件绘制中进行。)


下图为动画效果示例,红色闪烁条为插入符,2个ghost为类游戏角色动画。

动画效果


Step4:实现逻辑子窗体,窗体容器管理

UI通过各种UI组件呈现信息,UI组件都基于窗口,表现为控件及其组合。UI框架需负责UI组件的生成、销毁、布局计算以及维护管理。


定义逻辑窗口基础类,所有窗口(含控件)都派生于窗口基础类,主要含以下信息和功能:

1)  相关位置信息,包括窗口大小、相对父窗口位置、相对句柄窗口位置,用于鼠标消息定位处理,是否focus。

2)  层次信息,绘制时进行遮挡。定义最顶层的窗口层次为1,深度值为0.2,每一层窗口在父窗口基础上层次加1,深度值减0.01。

3)  窗口容器指针,若不为空,用于容纳子窗口。

4)  窗口绘制过程。


定义窗口容器类,功能如下:

1)最顶层的Win32句柄窗口和每个父窗口都必须绑定一个窗口容器类。

2)容纳管理子窗口,可创建或销毁子窗体。

3)将消息传递给子窗口进行处理。

4)绘制所有子窗口。


结合UI线程,其中消息处理流程如下:

消息处理过程


窗口的生成和删除都与容器相关,并涉及到递归(子窗口)。窗口的生成流程:

窗口生成过程


窗口的删除流程:

窗口删除过程



Step5:实现各类控件

UI框架需提供各种控件用于展示交互:

AmazeUI支持的控件


控件为实现了各种特殊功能的定制化逻辑子窗口,依赖于各类消息响应与定制化绘制。定义控件基础类,包含了所有被重载的消息处理函数,通过模板实现类似虚函数的多态,但开销更低(该方法借鉴于WTL),代码片段如下。

控件基类代码

所有的控件均派生于UIControlBase。

以UIButton按钮控件为例说明,需响应以下事件:

1)鼠标左键按下,触发击鼓动画特效

2)鼠标左键松开,发送Notify

3)获得键盘Enter消息,发送Notify

4)鼠标悬浮,触发绘制状态改变UIButton的定制化绘制为绘制图片和字符串。


grid、chart、tab控件效果图:

控件效果图1

其他控件效果图:

控件效果图2


Step6:支持子窗口布局计算,支持插件机制,3D元素绘制,待支持XML UI布局

至Step5一个可用的UI库已经成型,Step6内容对UI库属于锦上添花,暂不叙述。

最后看下UI库的初始生成代码,一个Lambda函数指针封装到WM_MQ消息发送到消息队列中,这样就将整个UI创建都放入UI线程中。


参考资料

1)中文名《DirectX 9.0 3D游戏开发编程基础》 英文名<Introduction to 3D Game Programming with DirectX 9.0>

2)《Windows游戏编程大师技巧》

3)Bjarke Viksoe 于2005年发布的文章 UI: Become windowless,其链接地址为http://www.viksoe.dk/code/windowless1.htm

本文标题:DirectUI GUI库设计与实现概述 - 八卦谈
本文地址:www.ttdhp.com/article/50533.html

天天动画片声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
扫码关注我们