浅析μC/GUI-v3.90a之WM_ITERATE_START剪切域计算宏
//剪切域宗旨:本hWin内部被控件占用的部分不用绘制;窗体Z序中比本hWin窗体高的窗体遮盖住本hWin的部分不用绘制;
//本hWin超出parent窗体边界的部分不用绘制;窗体的窗体Z序比本hWin的parent窗体高的窗体遮盖住本hWin需要绘制的部分不需要绘制;
//这就是剪切域绘制的宗旨,让绘制部分最小,通过复杂的cpu计算,避免窗体互相遮盖部分的重复绘制,提高显式速度,增加lcd使用寿命.[gliethttp]
//计算完剪切域之后,通过WM__ActivateClipRect()将会使_ClipContext.CurRect传递给GUI_Context.ClipRect,最终影响绘图的有效空间
//拿对当前hWin进行GUI_Clear()来说,剪切域的绘制顺序是上到下窄条依次绘制,在上到下的过程中又会由左到右依次计算绘制
-------------------------------------------------------------------------------------
1.
...
{
r = *pr;
#if GUI_WINSUPPORT
WM_ADDORG(r.x0, r.y0);
WM_ADDORG(r.x1, r.y1); //平移矩形r
WM_ITERATE_START(&r) { //计算r矩形区域内的剪切域
#endif
GUI_Context.DispPosX = r.x0;
GUI_Context.DispPosY = r.y0;
_DispLine(s, MaxNumChars, &r);
#if GUI_WINSUPPORT
} WM_ITERATE_END();
#endif
}
...
-------------------------------------------------------------------------------------
2.WM_ITERATE_START宏
gui/wm/WM_GUI.h
#define WM_ITERATE_START(pRect) \
{ \
if (WM__InitIVRSearch(pRect)) \ //构造循环体
do {
-------------------------------------------------------------------------------------
3.WM__InitIVRSearch()函数
gui/wm/WM.c
int WM__InitIVRSearch(const GUI_RECT* pMaxRect) {
GUI_RECT r;
WM_Obj* pAWin;
GUI_ASSERT_LOCK();
if (WM_IsActive==0) {
WM__ActivateClipRect();
return 1;
}
if (++_ClipContext.EntranceCnt > 1) //如果重入,返回,保持计算操作的唯一性
return 1;
pAWin = WM_H2P(GUI_Context.hAWin); //当前句柄,详情请参考《浅析μC/GUI-v3.98之WM_H2P()句柄hWin转内存函数》
_ClipContext.Cnt = -1;
if (WM__PaintCallbackCnt) { //从回调函数进入的剪切计算
WM__GetInvalidRectAbs(pAWin, &r); //r=pWin->InvalidRect;r存放pWin失效矩形区
} else {
if (pAWin->Status & WM_SF_ISVIS) {
r = pAWin->Rect; //不是回调函数引起的剪切计算,那么整个pWin窗体作为失效区域
} else {
--_ClipContext.EntranceCnt; //窗体属性为不可见
return 0;
}
}
if (pMaxRect) {
GUI__IntersectRect(&r, pMaxRect); //r依据pMaxRect,缩小自己的矩形区域
}
/* If user has reduced the cliprect size, reduce the rectangle */
if (GUI_Context.WM__pUserClipRect) {
WM_Obj* pWin = pAWin; //用户指定了失效重绘区域,那么继续缩小r的矩形区域
GUI_RECT rUser = *(GUI_Context.WM__pUserClipRect);
#if WM_SUPPORT_TRANSPARENCY
if (WM__hATransWindow) { //具有半透明属性
pWin = WM_H2P(WM__hATransWindow);
}
#endif
WM__Client2Screen(pWin, &rUser); //以pWin(x0,y0)位坐标原点,平移rUser矩形
GUI__IntersectRect(&r, &rUser); //继续缩小r矩形区
}
#if WM_SUPPORT_TRANSPARENCY
if (WM__hATransWindow) { //如果有半透明窗体,那么需要继续缩小剪切域r
if (WM__ClipAtParentBorders(&r, WM__hATransWindow) == 0) {
--_ClipContext.EntranceCnt;
return 0;
}
}
#endif
//主要进行如下五步操作:
//一.以上所有工作主要是是计算当前激活窗体hWin自身与pMaxRect的平面剪切区域r,
//二.接下来WM__ClipAtParentBorders()函数完成和hWin所有父窗体边框剪切,如果有一个父窗体属性为不可见,直接0退出
if (WM__ClipAtParentBorders(&r, GUI_Context.hAWin) == 0) {
--_ClipContext.EntranceCnt;
return 0;
}
_ClipContext.ClientRect = r; //保存r重绘区域到IVR专用单元_ClipContext.ClientRect
return WM__GetNextIVR(); //hWin自己的、所有父窗体的同级兄弟窗体与该r进行平面、三维立体z序混合剪切
} //pWin->hNext其实从窗口Z序来讲,是在pWin上面的控件,而不把pWin->hNext叫作hWin的兄弟
//pWin->hNext以及hNext->hNext都有权力覆盖hWin窗体,因为他们在窗口Z序的比hWin更靠近顶层处
-------------------------------------------------------------------------------------
4.WM__GetInvalidRectAbs()函数
gui/wm/WM.c
static void WM__GetInvalidRectAbs(WM_Obj* pWin, GUI_RECT* pRect) {
*pRect = pWin->InvalidRect;
}
-------------------------------------------------------------------------------------
5.WM__GetNextIVR()函数
gui/wm/WM.c
int WM__GetNextIVR(void) {
#if GUI_SUPPORT_CURSOR
static char _CursorHidden;
#endif
if (WM_IsActive==0) {
return 0;
}
if (_ClipContext.EntranceCnt > 1) {
_ClipContext.EntranceCnt--;
return 0;
} //非法性检查
#if GUI_SUPPORT_CURSOR
if (_CursorHidden) {
_CursorHidden = 0;
(*GUI_CURSOR_pfTempUnhide) (); //如果当前鼠标,即上一次进入时鼠标不可见,那么显示鼠标
}
#endif
++_ClipContext.Cnt;
/* Find next rectangle and use it as ClipRect */
//三.与窗口Z序比hWin->parent大的窗体进行剪切计算,缩小本次hWin绘制的区域
//四.与窗口Z序比hWin大的窗体进行剪切计算,继续缩小本次hWin绘制的区域
//五.与hWin窗体内部所有Child控件进行剪切计算,继继续缩小本次hWin绘制的区域
if (!_FindNext_IVR()) { //见函数6的注解
_ClipContext.EntranceCnt--;
return 0;
}
WM__ActivateClipRect();
/* Hide cursor if necessary */
#if GUI_SUPPORT_CURSOR
if (GUI_CURSOR_pfTempHide) {
_CursorHidden = (*GUI_CURSOR_pfTempHide) ( &_ClipContext.CurRect);
}
#endif
return 1;
}
-------------------------------------------------------------------------------------
6._FindNext_IVR()函数
//2007-07-11 gliethttp宗旨就是:本hWin内部被控件占用的部分不用绘制,窗体Z序中比本hWin窗体高的窗体遮盖住本hWin的部分不用绘制
//本hWin超出parent窗体边界的部分不用绘制,窗体的窗体Z序比本hWin的parent窗体高的窗体遮盖住本hWin需要绘制的部分不需要绘制
//这样就是剪切域绘制的宗旨了,让绘制部分最小,通过复杂的cpu计算,避免窗体遮盖部分的重复绘制,提高显式速度,增加lcd使用寿命.
gui/wm/WM.c
static int _FindNext_IVR(void) {
WM_HMEM hParent;
GUI_RECT r;
WM_Obj* pAWin;
WM_Obj* pParent;
r = _ClipContext.CurRect;
//1.设置r的初始化值
if (_ClipContext.Cnt == 0) { //如果从WM__InitIVRSearch()->WM__GetNextIVR()->_FindNext_IVR()进入
r.x0 = _ClipContext.ClientRect.x0; //那么_ClipContext.Cnt==0成立,将WM__InitIVRSearch()计算的存储到_ClipContext.ClientRect的剪切域
r.y0 = _ClipContext.ClientRect.y0; //读取到r中
} else {
r.x0 = _ClipContext.CurRect.x1+1; //接续上一已经绘制的窄条剪切域,继续进行其它窄条剪切域的计算
r.y0 = _ClipContext.CurRect.y0; //绘制顺序是上到下窄条依次绘制,在上到下的过程中又会由左到右依次计算绘制
if (r.x0 > _ClipContext.ClientRect.x1) {
NextStripe: /* go down to next stripe */
r.x0 = _ClipContext.ClientRect.x0;
r.y0 = _ClipContext.CurRect.y1+1;
}
}
//2.检测剪切计算完成否
if (r.y0 >_ClipContext.ClientRect.y1) {
return 0;
}
//3.寻找pRect->y1这个下边沿在所有和pRect存在剪切关系控件窗体中的最小位置,即:"r.y1靠上计算"
pAWin = WM_H2P(GUI_Context.hAWin);
if (r.x0 == _ClipContext.ClientRect.x0) {
r.y1 = _ClipContext.ClientRect.y1;
r.x1 = _ClipContext.ClientRect.x1;
/* Iterate over all windows which are above */
/* Check all siblings above (Iterate over Parents and top siblings (hNext) */
for (hParent = GUI_Context.hAWin; hParent; hParent = pParent->hParent) {
//_Findy1就是寻找pRect->y1这个下边沿的最小位置
pParent = WM_H2P(hParent); //计算GUI_Context.hAWin自己的哥们窗体[Z序比hWin高的窗口],
_Findy1(pParent->hNext, &r, NULL); //和父窗体的哥们窗体分别和r剪切域[Z序比老爹高的窗口]
//2007-07-11 gliethttp _Findy1()计算pParent->hNext和hNext->hNext等窗口Z序比pParent窗口Z序大的r剪切域
//举例:如A,B两个窗口,A窗口Z序比B窗口Z序大,那么A就在B的上面,A可以覆盖B,但B不能覆盖A,因为A窗口Z序比B窗口Z序大
}
/* Check all children */
_Findy1(pAWin->hFirstChild, &r, NULL); //对pWin窗体内部的所有孩子控件进行同样的r剪切计算
}
//4.调整pRect->x0到所有存在剪切关系的控件窗口的最右边rWinClipped.x1+1处,即:"r.x0靠右计算"
Find_x0:
r.x1 = r.x0;
{
hParent = GUI_Context.hAWin;
}
for (; hParent; hParent = pParent->hParent) {
pParent = WM_H2P(hParent);
if (_Findx0(pParent->hNext, &r, NULL)) {
goto Find_x0; //如果pParent的窗口Z序上层中存在和该r剪切区域,调整r.x0到
} //相应窗口Z序对应窗口的x1+1处,goto Find_x0继续计算接下来的pParent
} //就这样一直将r.x0调整到所有与r存在剪切关系的窗口Z序上层窗体的右边x1+1处
/* Check all children */
if (_Findx0(pAWin->hFirstChild, &r, NULL)) { //在自己hWin窗体内的所有孩子控件进行r.x0靠右计算
goto Find_x0;
}
//5.如果r.x0 > r.x1即,r的右边框,那么该窄条剪切域完毕.goto NextStripe去找下一个窄条剪切域
r.x1 = _ClipContext.ClientRect.x1; //恢复"r.x0靠右计算"中破坏了的r.x1
if (r.x1 < r.x0) {
_ClipContext.CurRect = r;
goto NextStripe; //x0过界,计算下一个需要重绘窄条剪切域
}
//6.调整pRect->x1到所有存在剪切关系的控件窗口的最左边rWinClipped.x0-1处,即:"r.x1靠左计算"
{
hParent = GUI_Context.hAWin;
}
for (; hParent; hParent = pParent->hParent) {
pParent = WM_H2P(hParent);
_Findx1(pParent->hNext, &r, NULL);
}
/* Check all children */
_Findx1(pAWin->hFirstChild, &r, NULL); //在自己hWin窗体内的所有孩子控件进行r.x1靠左计算
if (_ClipContext.Cnt >200) {
return 0; //最大嵌套数目
}
_ClipContext.CurRect = r;
return 1; //2007-07-11 gliethttp计算完毕;通过WM__ActivateClipRect()将会使
} //_ClipContext.CurRect传递给GUI_Context.ClipRect,来影响绘图的有效空间
-------------------------------------------------------------------------------------
6._Findy1()函数
gui/wm/WM.c
static void _Findy1(WM_HWIN iWin, GUI_RECT* pRect, GUI_RECT* pParentRect) {
WM_Obj* pWin; //_Findy1就是寻找pRect->y1这个下边沿的最小位置
for (; iWin; iWin = pWin->hNext) { //窗体z序比hWin高的所有窗体中,剪切遮盖住本pRect的部分[gliethttp]:找y1.
int Status = (pWin = WM_H2P(iWin))->Status;
if (Status & WM_SF_ISVIS) {
GUI_RECT rWinClipped;
if (pParentRect) { //指定了剪切参考
GUI__IntersectRects(&rWinClipped, &pWin->Rect, pParentRect);
} else {
rWinClipped = pWin->Rect; //pWin->Rect作为剪切参考
}
if (GUI_RectsIntersect(pRect, &rWinClipped)) {
if ((Status & WM_SF_HASTRANS) == 0) {
if (pWin->Rect.y0 > pRect->y0) {
ASSIGN_IF_LESS(pRect->y1, rWinClipped.y0 - 1); //将pRect->y1和rWinClipped矩形的上边延y0对齐
} else {
ASSIGN_IF_LESS(pRect->y1, rWinClipped.y1); //将pRect->y1和rWinClipped矩形的下边延y1对齐
}
} else {
/* Check all children*/
WM_HWIN hChild;
WM_Obj* pChild;
for (hChild = pWin->hFirstChild; hChild; hChild = pChild->hNext) {
pChild = WM_H2P(hChild);
_Findy1(hChild, pRect, &rWinClipped);
}
}
}
}
}
}
-------------------------------------------------------------------------------------
6._Findx0()函数
gui/wm/WM.c
static int _Findx0(WM_HWIN hWin, GUI_RECT* pRect, GUI_RECT* pParentRect) {
WM_Obj* pWin;
int r = 0;
for (; hWin; hWin = pWin->hNext) { //窗体z序比hWin高的所有窗体中,剪切遮盖住本pRect的部分[gliethttp]:找x0.
int Status = (pWin = WM_H2P(hWin))->Status;
if (Status & WM_SF_ISVIS) {
GUI_RECT rWinClipped;
if (pParentRect) {
GUI__IntersectRects(&rWinClipped, &pWin->Rect, pParentRect);
} else {
rWinClipped = pWin->Rect;
}
if (GUI_RectsIntersect(pRect, &rWinClipped)) {
if ((Status & WM_SF_HASTRANS) == 0) {
pRect->x0 = rWinClipped.x1+1; //调整pRect->x0到所有存在剪切关系的控件窗口的右边rWinClipped.x1+1处
r = 1;
} else {
/* Check all children */ //半透明处理
WM_HWIN hChild;
WM_Obj* pChild;
for (hChild = pWin->hFirstChild; hChild; hChild = pChild->hNext) {
pChild = WM_H2P(hChild);
if (_Findx0(hChild, pRect, &rWinClipped)) {
r = 1;
}
}
}
}
}
}
return r;
}
-------------------------------------------------------------------------------------
6._Findx1()函数
static void _Findx1(WM_HWIN hWin, GUI_RECT* pRect, GUI_RECT* pParentRect) {
WM_Obj* pWin;
for (; hWin; hWin = pWin->hNext) { //窗体z序比hWin高的所有窗体中,剪切遮盖住本pRect的部分[gliethttp]:找x1.
int Status = (pWin = WM_H2P(hWin))->Status;
if (Status & WM_SF_ISVIS) {
GUI_RECT rWinClipped;
if (pParentRect) {
GUI__IntersectRects(&rWinClipped, &pWin->Rect, pParentRect);
} else {
rWinClipped = pWin->Rect;
}
if (GUI_RectsIntersect(pRect, &rWinClipped)) {
if ((Status & WM_SF_HASTRANS) == 0) {
pRect->x1 = rWinClipped.x0-1; //调整pRect->x1到所有存在剪切关系的控件窗口的左边rWinClipped.x0-1处
} else {
/* Check all children */ //半透明处理
WM_HWIN hChild;
WM_Obj* pChild;
for (hChild = pWin->hFirstChild; hChild; hChild = pChild->hNext) {
pChild = WM_H2P(hChild);
_Findx1(hChild, pRect, &rWinClipped);
}
}
}
}
}
}
发表评论评论 (3 个评论)
窗口剪切算法
有了窗口 Z 序,我们就可以计算每个窗口的剪切域。我们把因为窗口 Z 序而产生的剪切域称为“全局剪切域”,这是相对于窗口自身定
义的剪切域而言的,我们把后者称为“局部剪切域”。窗口中的所有输出,首先要受到全局剪切域的影响,其次受到局部剪切域的影响。我们
在这里重点讲解窗口的全局剪切域的生成和维护。
全局剪切域的生成和维护
在 MiniGUI 中,剪切域表示为若干互不相交的矩形之并集,这些矩形称为剪切矩形。最初,屏幕上没有任何窗口时,桌面的剪切域由一
个矩形组成,即屏幕矩形;当屏幕上只有一个窗口时,该窗口的剪切域由一个矩形组成,该矩形即为窗口在屏幕上的矩形,而桌面的剪切域却
可能是由多个矩形组成的。
读者很容易看出,在只有一个窗口的情况下,形成桌面剪切域的矩形最多只能有四个。
此时,如果有一个新的窗口出现,则新的窗口将同时剪切旧的窗口和桌面。而这时,桌面和旧窗口的剪切域将多出一些矩形,这些矩形应
该是原有剪切域中的每个矩形受到新窗口矩形影响之后生成的剪切矩形。同样,原有剪切域中的每个矩形只能最多只能派生出4个新剪切域,
而某些矩形根本不会受到新窗口矩形的影响。
这样,我们可以将某个窗口全局剪切域归纳为原有剪切域中排除(Exclude)某个矩形而生成的:
窗口的全局剪切域初始化为窗口矩形。
当窗口之上有其他窗口覆盖时,则该窗口的全局剪切域为排除新窗口矩形之后的剪切域。
沿 Z 序迭代第 2 步,直到最顶层窗口。
清单 1 中的代码是在显示一个新窗口时,MiniGUI 处理被该窗口所覆盖的其他所有窗口的代码。这段代码调用了剪切域维护接口中的
SubtractClipRect 函数计算新的剪切域。
清单 1 显示新窗口时计算被新窗口覆盖的窗口的全局剪切域
// clip all windows under this window.
static void clip_windows_under_this (ZORDERINFO* zorder, PMAINWIN pWin, RECT* rcWin)
{
PZORDERNODE pNode;
PGCRINFO pGCRInfo;
pNode = zorder->pTopMost;
while (pNode->hWnd != (HWND)pWin)
pNode = pNode->pNext;
pNode = pNode->pNext;
while (pNode)
{
if (((PMAINWIN)(pNode->hWnd))->dwStyle & WS_VISIBLE) {
pGCRInfo = ((PMAINWIN)(pNode->hWnd))->pGCRInfo;
pthread_mutex_lock (&pGCRInfo->lock);
SubtractClipRect (&pGCRInfo->crgn, rcWin);
pGCRInfo->age ++;
pthread_mutex_unlock (&pGCRInfo->lock);
}
pNode = pNode->pNext;
}
}
与排除矩形相反的操作是包含(Include)某个矩形到剪切域中。这个操作用于隐藏或者销毁某个窗口时。当一个窗口被隐藏或销毁时,
该窗口之下的所有窗口将受到影响,此时,要将被隐藏或销毁窗口的矩形包含到这些受影响窗口的全局剪切域中。为此,MiniGUI 的剪切域维
护接口中有一个函数专用于该类操作(IncludeClipRect)。为确保剪切域中矩形互不相交,该函数首先计算与每个剪切矩形的相交矩形,然
后将自己添加到该剪切域中。
但是,在某些情况下,我们必须重新计算所有窗口的全局剪切域,比如在移动某个窗口时。
摘要
本发明公开了一种确定图形用户界面中窗口剪切关系的方法。本发明的方法通过记录和维护各个窗口的剪切域中各矩形区域其上层遮盖窗口数量的计数值,在图形用户界面系统每次发生影响窗口剪切关系事件时,本发明不需要重新计算所有窗口的剪切关系,而仅计算实际受影响的窗口的剪切关系,因而减少了计算复杂度,缩短响应时间。
主权项
1.一种确定图形用户界面中窗口剪切关系的方法,其特征在于: 1)每次创建新的窗口时,按照其Z序插入到所有窗口列表的合适位置,计算该窗口被按Z序大于它的所有窗口剪切而形成的剪切域,对剪切域这一集合中的各矩形进行标记,标记方法是:如果该矩形区域其上不被任何窗口遮盖,则标记为0,否则,其上如有N个窗口遮盖,则标记为-N; 2)每次显示已经创建的窗口时,遍历所有Z序小于它的窗口的原有剪切域中各矩形区域,如果某个矩形区域和待显示窗口相交,则计算该矩形区域被待显示窗口剪裁的剪切域,该下层窗口的剪切域为原有剪切域减去该矩形区域后和该矩形区域剪切域的并集,也按照1)中的叙述标记规则进行标记; 3)每次隐藏已经显示的窗口时,遍历所有Z序小于它的窗口的原有剪切域中各矩形区域,如果某个矩形区域和待显示窗口相交,则将该矩形区域的标记加一,如果标记达到0,则意味着该矩形区域将被显示; 4)每次移动已经显示的窗口时,在移动过程中只重画,不修改剪切域,当移动结束后,将利用3)的步骤隐藏原位置窗口,利用2)的步骤在新位置显示窗口; 5)每次在已显示的窗口中切换,则利用3)的步骤,将该窗口隐藏,修改该窗口的Z序到最顶层,按照2)的步骤显示该窗口,但只需要遍历所有Z序大于该窗口原有Z序的窗口; 6)每次销毁已有的窗口时,如果该窗口已经被显示,则先利用3)的步骤隐藏该窗口,再将该窗口从窗口列表中移除;如果该窗口已经被隐藏,则直接将该窗口从窗口列表中移除。
4.1 新的区域算法
新的 GDI 采用了新的区域算法,即在 X Window 和其他 GUI 系统当中广泛使用的区域算法。这种区域称作"x-y-banned"区域,并且具有如下特点:
区域由互不相交的非空矩形组成;
区域又可以划分为若干互不相交的水平条带,每个水平条带中的矩形是等高,而且是上对齐的;或者说,这些矩形具有相同的高度,而且所有矩形的左上角 y 坐标相等。
区域中矩形的排列,首先是在 x 方向(在一个条带中)从左到右排列,然后按照 y 坐标从上到下排列。
在 GDI 函数进行绘图输出时,可以利用 x-y-banned 区域的特殊性质进行绘图的优化。在将来版本中添加的绘图函数,将充分利用这一特性进行绘图输出上的优化。
新的 GDI 增加了如下接口,可用于剪切区域的运算(include/gdi.h):
BOOL GUIAPI PtInRegion (PCLIPRGN region, int x, int y);
BOOL GUIAPI RectInRegion (PCLIPRGN region, const RECT* rect);
BOOL GUIAPI IntersectRegion (CLIPRGN *dst, const CLIPRGN *src1, const CLIPRGN *src2);
BOOL GUIAPI UnionRegion (PCLIPRGN dst, const CLIPRGN* src1, const CLIPRGN* src2);
BOOL GUIAPI SubtractRegion (CLIPRGN* rgnD, const CLIPRGN* rgnM, const CLIPRGN* rgnS);
BOOL GUIAPI XorRegion (CLIPRGN *dst, const CLIPRGN *src1, const CLIPRGN *src2);
PtInRegion 函数可用来检查给定点是否位于给定的区域中。
RectInRegion 函数可用来检查给定矩形是否和给定区域相交。
IntersectRegion 函数对两个给定区域进行求交运算。
UnionRegion 函数可合并两个不同的区域,合并后的区域仍然是 x-y-banned 的区域。
SubstractRegion 函数从一个区域中减去另外一个区域。
XorRegion 函数对两个区域进行异或运算,其结果相当于 src1 减 src2 的结果 A 与 src2 减 src1 的结果 B 之间的交。
在 MiniGUI 1.1.0 版本正式发布时,我们将添加从多边形、椭圆或圆弧等封闭曲线中生成剪切域的 GDI 函数。这样,就可以实现将 GDI 输出限制在特殊封闭曲线的效果。