|
|

楼主 |
发表于 2006-8-31 12:23:00
|
显示全部楼层
Re: 超强扩展性的UI引擎
bluebaby9811: 超强扩展性的UI引擎
小弟我新近设计的UI引擎,具有超强的扩展性能,这里附上源程序,哪位有需要的尽管拿去。
...
迟来的文档
UI全称Use Interface(用户界面), 在一个PC的Graphics OS(图形操作系统)里,泛指能响应用户鼠标输入(目前PC上Graphics OS下的主要用户输入方式之一)的一些图形对象, 例如常见的Button(按钮),Nemu(菜单)以及(Window)窗体等。
一个完整的UI系统必须神形兼备, 神就是指逻辑处理, 形就是指外观(如何绘制)及外在表现(如何响应输入)。
此UI引擎呢只具备UI系统的神,也就是逻辑处理部份,不能算一个完整的UI系统,所以类名为CUIProcessor, C是Class单词的首字母,而UIProcessor就是UI处理器的意思, 熟悉C++的人都应能理解。
而它全部的精华就在于此,封装了UI系统的全部逻辑处理部分,却没有固定的形态,我都不知道该怎样形容它。
在进行详细的讲解前,我先来解释一个名词,就是“热区”,也许大家都熟悉不过了,但它对理解UI Processor的工作原理很重要。
热区:UI对象用来响应用户鼠标输入的一个屏幕区域,只有在此区域内的鼠标事件才能被UI对象捕获(如解释的不好,还请大家见谅!)。
所以说白了一个UI对象实质上只是一个能够接受用户鼠标输入的屏幕区域,而它长什么样子以及它能做什么并重要,重要的是在屏幕上众多的UI对象中,一个鼠标事件该由谁来响应或者说被谁给捕获了,这些UI对象中,有些是相互重叠在一起的,一些UI对象遮挡了其它的UI对象,但不管怎样,在最顶端的UI对象只有一个,当一个鼠标事件发生时,鼠标的坐标点要是都落在了这些重叠在一起的UI对象的热区内,这些UI对象应该都能捕获它,但实际上只能被一个UI对象所捕获,就是那个在最顶端的UI对象。想象一下射箭的情况吧,有若干箭靶挡在了你射出的箭矢的轨迹上,只有最前面的那个箭靶能中箭,当然有可能箭矢穿过前面的那个箭靶而射在后面的某个箭靶上,但这种能让你成为人们心目中的英雄的事情在我们这里是不被允许发生的。
上面只讨论了UI对象之间的遮挡关系的处理,还有一个关系是我们必须处理的,那就是父子关系。在众多的UI对象中,一些UI对象拥有若干个子UI对象(如Window),而一些UI对象没有一个子UI对象(如Button),所有的UI对象都必须有而且仅有一个UI父对象, 在一个UI应用系统中,无论如何都要有一个RootUI对象(根UI), 它的热区就是整个应用程序窗口大小,它捕获所有的用户鼠标输入事件,并按顺序传递给它的所有子UI对象。父亲能捕获的鼠标事件才能被其孩子捕获,所以鼠标事件总是父传子一级一级的传下去,直到被处理而反回或是没有被处理而反回,父亲传递它捕获的鼠标事件给它的孩子们(如果它有孩子的话),有多个孩子呢就要检测这些孩子们的遮挡关系,只能被处在最顶端的孩子所捕获, 如果它没有一个孩子或它所有的孩子都不能捕获这个鼠标事件,就终止鼠标事件的传递并由它自己来处理这个鼠标事件,通常实际应用时由RootUI捕获并处理的鼠标事件不算是被UI对象捕获的事件, 这样没有被其它UI对象捕获而被RootUI处理的鼠标事我们把它继续传给其它需要处理鼠标事件的模块处理。
通常的做法就是,如果一个鼠标事件被除RootUI外的任何UI一个对象捕获并处理,我们就不再把此鼠标事件传给其它也需要处理鼠标事件的模块,只有当一个鼠标事件被RootUI捕获并处理,我们才把它传给其它需要处理鼠标事件的模块处理。
我们来想象下吧,在一个UI应用系统中,假设RootUI下有两个子UI对象,我们分别给它们命名为WindowA、WindowB, WindowA又有两个子UI对象,命名为WindowA_Buton0、WindowA_Buton1, WindowB没有一个子UI对象,它们的参数分别如下:
注:所有的尺寸单位都是Pixel.
RootUI热区位置:x = 400, y = 300;
RootUI热区大小:w = 800, h = 600;
//假设程序窗口的客户区大小为800Pixel宽,600Pixel高。
RootUI孩子数:2;
{
RootUI孩子0:
WindowA;
WindowA热区位置:x = 400, y = 300;
WindowA热区大小:w = 400, h = 300;
WindowA孩子数:2;
{
WindowA孩子0:
windowA_Button0;
windowA_Button0热区位置:
x = 400, y = 300;
windowA_Button0热区大小:
w = 100, h = 50;
windowA_Button0孩子数:0;
WindowA孩子1:
WindowA_Button1;
WindowA_Button1热区位置:
x = 400, y = 300;
WindowA_Button1热区大小:
w = 200, h = 50;
WindowA_Button1孩子数:0;
}
RootUI孩子1:
WindowB;
WindowB热区位置:
x = 400, y = 300;
WindowB热区大小:
w = 600, h = 500;
WindowB孩子数:0;
}
处于孩子0位置的UI对象就是最顶端UI对象。
演示:
假设用户鼠标在(400,300)的位置:
RootUI -> WindowA -> WindowA_Button0,
由WindowA_Button0捕获。
假设用户鼠标在(300,300)的位置:
RootUI -> WindowA -> WindowA_Button1,
由WindowA_Button1捕获。
假设用户鼠标在(250,300)的位置:
RootUI -> WindowA,
由WindowA捕获。
假设用户鼠标在(150,300)的位置:
RootUI -> WindowB,
由WindowB捕获。
假设用户鼠标在(50,300)的位置:
RootUI,
由RootUI捕获
( 通常是调用其它需要处理鼠标事件的模块 )。
还有绘制的先后问题也是我们要解决的,绘制时就要先绘制最底层的UI对象,最后绘制最顶层的UI对象,这样如果它们有象素重叠的话在重叠的区域内后绘制的对象的象素就会把先绘制的对象的象素覆盖掉,这样在视觉上就会产生遮挡关系。父子之间也是先绘制父亲然后绘制孩子。
而我呢,只要把神做好,形呢就交给大家去自由发吧!
CUIProcessor就是基于上述思想实现的。
#ifndef _UI_H_
#define _UI_H_
typedef bool( *PUIFUNCTION )( void ); //函数指针
///////////////////////////////
//CUIProcessor class define start
//////////////////////////////////
#pragma pack(push,1)
class CUIProcessor
{
public:
enum RUNTIME_ERROR
{
ERROR_NO = 0, //没有错误
ERROR_ONDRAWFUNCTION = 1, //绘制函数出错
ERROR_ONMOUSEFUNCTION = 2,
//鼠标响应函数出错
ERROR_ONDRAWFUNCTION_NULL = 3,
//没有设置绘制函数
ERROR_ONMOUSEFUNCTION_NULL = 4,
//没有设置鼠标响应函数
};
enum CAPTURE_STATE
{
WITHOUT_CAPTURE = 0,
//没有捕获
CAPTURE = 1,
//捕获
};
struct SHOTAREA
{
int m_iCenterX;
int m_iCenterY;
DWORD m_dwWidth;
DWORD m_dwHeight;
SHOTAREA()
{
::memset( this, 0, sizeof( *this ) );
}
void Copy( SHOTAREA &Source )
{
::memcpy( this, &Source, sizeof( *this ) );
}
};
public:
int m_iTopChildNumber;
DWORD m_dwChildCount;
CUIProcessor **m_ppChilds;
CUIProcessor *m_pFather;
int m_iPlaceInFather;
bool m_bVisible;
SHOTAREA m_HotArea; //热区,指出响应鼠标一个距形区域
PBYTE m_pBitMaskMap;
//热区遮蔽图,它可以让CUIProcessor拥有任何形状的热区
//当有热区遮蔽图时,如检测到鼠标落到了矩形热区内,
//还要检测相应坐标点的屏蔽位是否非0,非0才算是触发了
//热区,用这个热区遮蔽图你能控制热区的为你想要的
//任何形状, 当然不想要的话就不要创建它,
//这样它就不起任何作用
PUIFUNCTION m_pOnDrawFunction;
//鼠标响应函数
PUIFUNCTION m_pOnMouseFunction;
//绘制函数
//在这两个函数里,你干任何事情
static CUIProcessor::RUNTIME_ERROR m_RuntimeError;
public:
CUIProcessor();
~CUIProcessor(){ Clear(); }
void Clear( void );
bool CreateChildRoom( DWORD dwChildCount );
//给孩子创建房间(给它们住?^-^)
bool AddChild( CUIProcessor &Child );
//添加孩子
bool DeleteChild( CUIProcessor &Child );//删除孩子
bool ChangeFather( CUIProcessor &Father );
//更改父亲(移名换姓?^-^)
void Hide(); //隐藏UI,进入隐藏状态后不接受鼠标响应,也不绘制
void Show(); //显示UI,也就是解除隐藏状态
void Active();
//激活UI,把UI放到最顶端(鼠标响应时的优先级最高),
//如果UI是隐藏状态的,激活后解除隐藏状态
//就象Windows的窗口那样,鼠标在上面单击一下,窗口
//就跳到最顶端
void SetHotArea( int iCenterX, int iCenterY,
DWORD dwWidth, DWORD dwHeight );
void SetHotArea( SHOTAREA &HotArea );
void GetHotArea( SHOTAREA &HotArea );
bool CreateBitMaskMap( PBYTE pBitmap,
DWORD dwWidth, DWORD dwHeight );
//从一个Bitmap创建热区遮蔽图
//大小必须与热区大小相同
bool IsVisible();
bool IsActive();
bool SetOnMouseFunction( PUIFUNCTION pFunction );
//设置鼠标响应函数
bool SetDrawFunction( PUIFUNCTION pFunction );
//设置绘制函数
CUIProcessor::CAPTURE_STATE
OnMouse( int iMouseX, int iMouseY );//检测鼠标
void OnDraw();//绘制
CUIProcessor::RUNTIME_ERROR
GetRuntimeError();//获取运行时的错误代码
};
#pragma pack(pop)
///////////////////////////////
//CUIProcessor class define end
////////////////////////////////
#endif
通常在应用时:
创建并设置好所有的CUIProcessor对象的热区,鼠标响应函数,绘制函数以及父子关系;
在做为RootUI的CUIProcessor对象的鼠标响应函数里,调用需要鼠标事件的其它模块, 如没有就什么也不做,返回ture;
在程序的更新循环里调用RootUI的OnMouse()函数;
在绘制循环里调用RootUI的OnDraw()函数;
通常它就能运行得很好。
特别说明:只要鼠标在CUIProcessor对象的热区内并被其捕获时,鼠标响应函数就被调用了,所以在鼠标响应函数里还得你自己检测是否发生鼠标事件以及是什么事件(如常见的单击,双击等)。
虽然用它实现一个真正的UI系统还有很多工作要做,但它确实有很好的扩展性,用来做Button、Window、Menu都没问题,甚至我可以这样说只有你想不到的,没有你做不到的。
讲了这么多,不知大家能否理解,没想到写代码及调试完才花了我3个小时而已,写这篇算是文档的文章却花了我一整天的时间,
有些逻辑关系在脑子里很清楚,可是用文字表述起来却困难得多,即使是写出来了也不知道别人能否明白,终于体会到编程容易文档难写的真理了,要是我们人类交流的方式能进化到神经元信号级的话,我们的发展速度不知会提高多少倍。
好了,牢骚也发了,还请大家多多捧场,有什么问题及建议,我会努力改进,力求把它做得更完美。
abc |
|