数码相框实现二、显示设置页面、时间间隔设置页面

一、写在前面
​突然发现,如何先把思路讲清,然后再贴出一堆代码。这种分离式的讲解不仅难以将代码与原理联系在一起,
更重要的是后面贴出的一段代码实在枯涩难懂(一没注释,二代码太长)。因此,个人看法,应该一文按实现顺序拆分为几部分,
每一部分将思路与代码实现结合起来,且第一部分应提出本文目标,结尾部分再捋一下全篇思路,做出总结。
此外,如有参考资料或辅助资料放在最后面,帮助理解和知识拓展。
​行,就这么干!
二、本文内容概述
本文在前篇显示主界面基础上,完成显示设置页面、时间间隔页面及实现加减时间间隔。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-haXkrjr9-1590065301030)(C:\Users\zmk_02\AppData\Roaming\Typora\typora-user-images\image-20200520233553926.png)]

三、显示设置页面
思路:
1.显示主页面后,进入while循环等待触摸屏输入,没输入则休眠,否则子线程捕捉到输入事件后会唤醒主线程,
主线程再调用捕捉事件函数并返回事件(注: 事件用结构体封装,记录了触摸屏点击位置(X,Y)、事件类型(触摸屏或按键)、
点击时间、按键值(按键还没实现))。然后根据事件做出相应处理。
2. 因此,要想显示设置页面,首先捕捉到触摸屏点击输入事件后,判断点击位置落在“设置”图标按钮上,然后,
类似主页面显示一样,调用设置页面的Run函数,显示“设置页面”后进入while()循环,不断地捕捉触摸屏输入事件,
判断事件并作出相应处理。

瞧,设置页面的显示和功能跟主页面完全一致。
因此,编程就简单多了!直接复制主页面代码,做些修改就可以啦。
在前面分析基础上,实现分两步。

1)修改主页面while循环里的代码,判断点击在“设置”按钮后调用设置页面的Run()函数。代码如下
/* 这段代码在主页面的Run()函数里面  */
/* 获得输入事件(暂且只有触摸屏作为输入) */
int iIndex; 
int bPressed = 0;
int iIndexPressed = -1;
while(1)
{
        iIndex = GetMainPageInputEvent(g_atMainPageLayout, &tInputEvent);
        if(tInputEvent.iPressure == 0)
        { 
            if(bPressed)  // 曾经按下
            {
             	/* 曾经有按钮被按下, 则释放按钮 */
		ReleaseButton(&g_atMainPageLayout[iIndexPressed]);
		bPressed = 0;
		if (iIndexPressed == iIndex) /* 按下和松开都是同一个按钮 */
		{
			switch (iIndexPressed)
			{
				case 2: /* 设置按钮 */
				{
                            		/* 调用设置页面的Run函数 */
					GetPageOpr("setting")->Run();

					/* 从设置页面返回后显示当首的主页面 */
					ShowMainPage(g_atMainPageLayout);
					break;
				}
				default:
				{
					break;
				}
			}
		  }
		  iIndexPressed = -1;
            }
         }
         else
         {
            if(iIndex != -1)
            {
                if(!bPressed)
                {
                    	/* 未曾按下按钮 */
			bPressed = 1;
			iIndexPressed = iIndex;
			PressButton(&g_atMainPageLayout[iIndexPressed]);
                }
            }
         }  
}
2)实现页面设置文件 setting_page.c 
这部分代码就不贴出来了,因为实现逻辑和主页面的完全一样。这里回顾下实现思路,并注意两点。
实现思路:看下图

设置页面实现思路

注意两点
1)设置页面的 return.bmp 大小不同于前两个,需特殊处理(即缩放为 54x54, 前面两个bmp缩放为 108 x 54)
if(atLayout->pcIconName && strcmp(atLayout->pcIconName, "return.bmp") == 0)
        {
            iIconWidth = iIconWidth / 2;
            iIconX     = (iXres - iIconWidth) / 2;

            tScaledIconPixelDatas.iBpp	      = iBpp;
	    tScaledIconPixelDatas.iWidth      = iIconWidth;
	    tScaledIconPixelDatas.iHeight     = iIconHeight;
	    tScaledIconPixelDatas.iLineBytes  = iIconWidth * iBpp / 8;
	    tScaledIconPixelDatas.iTotalBytes = tScaledIconPixelDatas.iLineBytes * iIconHeight;
        }

2)  调用分配缓冲块函数中 注意,ptNew->tPixelDatas.aucPixelDatas = (unsigned char*)(ptNew + 1);
即(ptNew+1)先加括号,避免先强转为unsigned char*,然后 + 1 byte。这会导致调用ClearVideoMem
将iTotalBytes设置为0. 即点击设置后并不会显示出“设置页面”。(这段话没听懂没关系,不影响。如果遇到
点击设置页面后并没有显示,可考虑是否这里出了问题,我就掉坑在这里 :)
四、显示时间间隔设置页面
思路:
时间间隔设置页面的显示和功能与主页面、设置页面的逻辑完全相同,这里不再赘述。
区别在于,时间间隔的布局要比前面两个要复杂。需提前计算各图标的位置信息。
计算图标在LCD显示坐标并不难,也不是重点,因此这里忽略不讲。
重点在下面的第5点,如何实现时间间隔的加减。
五、实现加减时间间隔
要改变时间间隔,思路如下
如:要显示 07 ---> 转为字符串--->逐个字符处理,写入LCD的显存
前面两步很简单,用snprintf函数就可解决。难点在最后一步。处理过程如下:
确定单个字符的编码值,根据编码值找到字体(用freetype库),再把这个字体写到LCD显存对应位置。
(备注:所谓字体写到LCD显存,即把位图数据复制到LCD显存而已)

问题来了,如何确定要显示的字符在LCD的坐标值?
1)事先计算字符串的最大宽度和高度,然后计算第一个字符的原点坐标
2)确定显示位置后,根据字符编码确定字体位图,然后写入LCD的显存指定位置,如此不断循环处理
(注意:这里先写入缓冲区,再刷到LCD的显存FrameBuffer)

下面看一下代码实现
/*--------------------下面代码在interval_page.c文件的Run函数中----------------*/
while (1)
{
    iIndex = IntervalPageGetInputEvent(&g_tIntervalPageLayout, &tInputEvent);
    if (tInputEvent.iPressure == 0)
    {
        /* 如果是松开 */
        if (bPressed)
        {
				/* 曾经有按钮被按下 */
				ReleaseButton(&g_atIntervalPageIconsLayout[iIndexPressed]);
				bPressed = 0;
                if (iIndexPressed == iIndex) /* 按下和松开都是同一个按钮 */
			{
				switch (iIndexPressed)
				{
					case 0: /* inc按钮 */
					{
						iNumber++;
						if (iNumber == 60)
						{
							iNumber = 0;
						}
						GenerateIntervalPageSpecialIcon(iNumber, ptDevVideoMem);
						break;
					}
					case 2: /* dec按钮 */
					{
						iNumber--;
						if (iNumber == -1)
						{
							iNumber = 59;
						}
						GenerateIntervalPageSpecialIcon(iNumber, ptDevVideoMem);
						break;
					}
					case 3: /* ok按钮 */
					{
						return;
						break;
					}
					case 4: /* cancel按钮 */
					{
						return;
						break;
					}
					default:
					{
						break;
					}
				}
			}
			
			iIndexPressed = -1;
		}
	}
	else
	{
		/* 按下状态 */
		if (iIndex != -1)
		{
			if (!bPressed && (iIndex != 1))
			{
				/* 未曾按下按钮 */
				bPressed = 1;
				iIndexPressed = iIndex;
				PressButton(&g_atIntervalPageIconsLayout[iIndexPressed]);
			}
		}
	}		
}

/* 绘制图标中的数字 */
static int GenerateIntervalPageSpecialIcon(int dwNumber, PT_VideoMem ptVideoMem)
{
	unsigned int dwFontSize;
	char strNumber[3];
	int iError;
	

	dwFontSize = g_tIntervalNumberLayout.iBotRightY - g_tIntervalNumberLayout.iTopLeftY;
	SetFontSize(dwFontSize);

	/* 显示两位数字: 00~59 */
	if (dwNumber > 59)
	{
		return -1;
	}

	snprintf(strNumber, 3, "%02d", dwNumber);
	//DBG_PRINTF("strNumber = %s, len = %d\n", strNumber, strlen(strNumber));

	iError = MergerStringToCenterOfRectangleInVideoMem(g_tIntervalNumberLayout.iTopLeftX, g_tIntervalNumberLayout.iTopLeftY, g_tIntervalNumberLayout.iBotRightX, g_tIntervalNumberLayout.iBotRightY, (unsigned char *)strNumber, ptVideoMem);

	return iError;
}
/* -----------------------以下代码在render.c文件------------------------------------*/
/*
 * 在videomem的指定矩形中间显示字符串
 */
int MergerStringToCenterOfRectangleInVideoMem(int iTopLeftX, int iTopLeftY, int iBotRightX, int iBotRightY, unsigned char *pucTextString, PT_VideoMem ptVideoMem)
{
	int iLen;
	int iError;
	unsigned char *pucBufStart;
	unsigned char *pucBufEnd;
	unsigned int dwCode;
	T_FontBitMap tFontBitMap;
	
	int bHasGetCode = 0;

	int iMinX = 32000, iMaxX = -1;
	int iMinY = 32000, iMaxY = -1;

	int iFirstFontTopLeftX = 32000, iFirstFontTopLeftY = 32000;
	int iNewFirstFontTopLeftX , iNewFirstFontTopLeftY;

	int iWidth, iHeight;

	tFontBitMap.iCurOriginX = 0;
	tFontBitMap.iCurOriginY = 0;
	pucBufStart = pucTextString;
	pucBufEnd   = pucTextString + strlen((char *)pucTextString);

	/* 0. 清除这个区域 */
	ClearRectangleInVideoMem(iTopLeftX, iTopLeftY, iBotRightX, iBotRightY, ptVideoMem, COLOR_BACKGROUND);
	
	/* 1.先计算字符串显示的总体宽度、高度 */
	while (1)
	{
		iLen = GetCodeFrmBuf(pucBufStart, pucBufEnd, &dwCode);
		if (0 == iLen)
		{
			/* 字符串结束 */
			if (!bHasGetCode)
			{
				//DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
				return -1;
			}
			else
			{
				break;
			}
		}

		bHasGetCode = 1;
		pucBufStart += iLen;

		iError = GetFontBitmap(dwCode, &tFontBitMap);
		if (0 == iError)
		{						
			if (32000 == iFirstFontTopLeftX)
			{
				iFirstFontTopLeftX = tFontBitMap.iXLeft;
				iFirstFontTopLeftY = tFontBitMap.iYTop;
			}
			
			if (iMinX > tFontBitMap.iXLeft)
			{
				iMinX = tFontBitMap.iXLeft;
			}
			if (iMaxX < tFontBitMap.iXMax)
			{
				iMaxX = tFontBitMap.iXMax;
			}
			
			if (iMinY > tFontBitMap.iYTop)
			{
				iMinY = tFontBitMap.iYTop;
			}
			if (iMaxY < tFontBitMap.iXMax)
			{
				iMaxY = tFontBitMap.iYMax;
			}
			
			tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
			tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
		}
		else
		{
			DBG_PRINTF("GetFontBitmap for calc width/height error!\n");
		}
	}	

	iWidth  = iMaxX - iMinX;
	iHeight = iMaxY - iMinY;

	if (iWidth > iBotRightX - iTopLeftX)
	{
		return -1;
	}
	if (iHeight > iBotRightY - iTopLeftY)
	{
		return -1;
	}
    
	/* 2.确定第1个字符的原点 
	 * 2.1 先计算左上角坐标
	 */
	iNewFirstFontTopLeftX = iTopLeftX + (iBotRightX - iTopLeftX - iWidth) / 2;
	iNewFirstFontTopLeftY = iTopLeftY + (iBotRightY - iTopLeftY - iHeight) / 2;

	/*	 
	 * 2.2 再计算第1个字符原点坐标
	 */
	tFontBitMap.iCurOriginX = iNewFirstFontTopLeftX - iFirstFontTopLeftX;
	tFontBitMap.iCurOriginY = iNewFirstFontTopLeftY - iFirstFontTopLeftY;
	
	pucBufStart = pucTextString;	
	bHasGetCode = 0;
	while (1)
	{
		iLen = GetCodeFrmBuf(pucBufStart, pucBufEnd, &dwCode);
		if (0 == iLen)
		{
			/* 字符串结束 */
			if (!bHasGetCode)
			{
				DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
				return -1;
			}
			else
			{
				break;
			}
		}

		bHasGetCode = 1;
		pucBufStart += iLen;

		iError = GetFontBitmap(dwCode, &tFontBitMap);
		if (0 == iError)
		{
			/* 显示一个字符 */
			if (MergeOneFontToVideoMem(&tFontBitMap, ptVideoMem))
			{
				DBG_PRINTF("MergeOneFontToVideoMem error for code 0x%x\n", dwCode);
				return -1;
			}
			
			tFontBitMap.iCurOriginX = tFontBitMap.iNextOriginX;
			tFontBitMap.iCurOriginY = tFontBitMap.iNextOriginY;
		}
		else
		{
			DBG_PRINTF("GetFontBitmap for drawing error!\n");
		}
	}

	return 0;
}
static void ClearRectangleInVideoMem(int iTopLeftX, int iTopLeftY, int iBotRightX, int iBotRightY, PT_VideoMem ptVideoMem, unsigned int dwColor)
{
	int x, y;
	for (y = iTopLeftY; y <= iBotRightY; y++)
		for (x = iTopLeftX; x <= iBotRightX; x++)
			SetColorForPixelInVideoMem(x, y, ptVideoMem, dwColor);
}
/* 返回值: 设置了VideoMem中多少字节 */
static int SetColorForPixelInVideoMem(int iX, int iY, PT_VideoMem ptVideoMem, unsigned int dwColor)
{
	unsigned char *pucVideoMem;
	unsigned short *pwVideoMem16bpp;
	unsigned int *pdwVideoMem32bpp;
	unsigned short wColor16bpp; /* 565 */
	int iRed;
	int iGreen;
	int iBlue;

	pucVideoMem      = ptVideoMem->tPixelDatas.aucPixelDatas;
	pucVideoMem      += iY * ptVideoMem->tPixelDatas.iLineBytes + iX * ptVideoMem->tPixelDatas.iBpp / 8;
	pwVideoMem16bpp  = (unsigned short *)pucVideoMem;
	pdwVideoMem32bpp = (unsigned int *)pucVideoMem;

	//DBG_PRINTF("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	//DBG_PRINTF("x = %d, y = %d\n", iX, iY);
	
	switch (ptVideoMem->tPixelDatas.iBpp)
	{
		case 8:
		{
			*pucVideoMem = (unsigned char)dwColor;
			return 1;
			break;
		}
		case 16:
		{
			iRed   = (dwColor >> (16+3)) & 0x1f;
			iGreen = (dwColor >> (8+2)) & 0x3f;
			iBlue  = (dwColor >> 3) & 0x1f;
			wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
			*pwVideoMem16bpp	= wColor16bpp;
			return 2;
			break;
		}
		case 32:
		{
			*pdwVideoMem32bpp = dwColor;
			return 4;
			break;
		}
		default :
		{			
			return -1;
		}
	}

	return -1;
}
static int MergeOneFontToVideoMem(PT_FontBitMap ptFontBitMap, PT_VideoMem ptVideoMem)
{
	int i;
	int x, y;
	int bit;
	int iNum;
	unsigned char ucByte;

	if (ptFontBitMap->iBpp == 1)
	{
		for (y = ptFontBitMap->iYTop; y < ptFontBitMap->iYMax; y++)
		{
			i = (y - ptFontBitMap->iYTop) * ptFontBitMap->iPitch;
			for (x = ptFontBitMap->iXLeft, bit = 7; x < ptFontBitMap->iXMax; x++)
			{
				if (bit == 7)
				{
					ucByte = ptFontBitMap->pucBuffer[i++];
				}
				
				if (ucByte & (1<<bit))
				{
					iNum = SetColorForPixelInVideoMem(x, y, ptVideoMem, COLOR_FOREGROUND);
				}
				else
				{
					/* 使用背景色 */
					// g_ptDispOpr->ShowPixel(x, y, 0); /* 黑 */
					iNum = SetColorForPixelInVideoMem(x, y, ptVideoMem, COLOR_BACKGROUND);
				}
				if (iNum == -1)
				{
					return -1;
				}
				bit--;
				if (bit == -1)
				{
					bit = 7;
				}
			}
		}
	}
	else if (ptFontBitMap->iBpp == 8)
	{
		for (y = ptFontBitMap->iYTop; y < ptFontBitMap->iYMax; y++)
			for (x = ptFontBitMap->iXLeft; x < ptFontBitMap->iXMax; x++)
			{
				//g_ptDispOpr->ShowPixel(x, y, ptFontBitMap->pucBuffer[i++]);
				if (ptFontBitMap->pucBuffer[i++])
				{
					iNum = SetColorForPixelInVideoMem(x, y, ptVideoMem, COLOR_FOREGROUND);
				}
				else
				{
					iNum = SetColorForPixelInVideoMem(x, y, ptVideoMem, COLOR_BACKGROUND);
				}
				
				if (iNum == -1)
				{
					return -1;
				}
			}
	}
	else
	{
		DBG_PRINTF("ShowOneFont error, can't support %d bpp\n", ptFontBitMap->iBpp);
		return -1;
	}
	return 0;
}
上面的是单击实现时间间隔的加减,那长按如何实现呢?

思路是这样的:第一次点击时,记录点击的时间,然后显示下沉效果,如果点着不放,
则比对此次时间与上次点击时间间隔是否>2s, 如果是,则设置一个标志位 bFast=1 以开启长按累加/减模式,
如果继续不放,后面就会进入到快速累加/减模式,每隔50ms加/减一次。如果释放了,
则清零bFast 以 取消长按模式,可以单击累加/减。
注意:不清零会导致长按<2s也会出现连续的加/减。因此,编程注意这点。

下面看看代码实现。
/* 2. 获得输入事件(暂且只有触摸屏作为输入) */
while(1)
{
	iIndex = GetIntervalPageInputEvent(&g_tIntervalPageLayout, &tInputEvent);
	if(tInputEvent.iPressure == 0)
	{ 
		bFast = 0; // 记得清零 !
		if(bPressed)  // 曾经按下
             	{
                 	/* 曾经有按钮被按下 */
     			ReleaseButton(&g_atIntervalPageLayout[iIndexPressed]);
     			bPressed = 0;
                 	if(iIndex == iIndexPressed)
                 	{
                    		switch (iIndexPressed)
      				{
       					case 0: /* inc按钮 */
       					{
        					iIntervalSecond++;
        					if (iIntervalSecond == 60)
        					{
         						iIntervalSecond = 0;
        					}
        				GenerateIntervalPageSpecialIcon(iIntervalSecond, ptDevVideoMem);
        				break;
       					}
       					case 2: /* dec按钮 */
       					{
        					iIntervalSecond--;
        					if (iIntervalSecond == -1)
        					{
         						iIntervalSecond = 59;
        					}
        				GenerateIntervalPageSpecialIcon(iIntervalSecond, ptDevVideoMem);
        					break;
       					}
       					case 3: /* ok按钮 */
       					{
        					return;
        					break;
       					}
       					case 4: /* cancel按钮 */
       					{
        					return;
        					break;
       					}
       					default:
       					{
        					break;
       					}
                    		}
                 	}
     			iIndexPressed = -1;
             	}
         	}
         else
         {
		if(iIndex != -1)
             	{
                 	if(!bPressed && (iIndex != 1))
                 	{
                     		/* 未曾按下按钮 */
      				bPressed = 1;
      				iIndexPressed = iIndex;
                     		tInputEventPrePress = tInputEvent;  /* 记录下来 */
      				PressButton(&g_atIntervalPageLayout[iIndexPressed]);
                 	}
             	}
             	/* 如果按下的是"inc.bmp"或"dec.bmp" 
  		* 连按2秒后, 飞快的递增或减小: 每50ms变化一次
  		*/
             	if ((iIndexPressed == 0) || (iIndexPressed == 2))
 		{
  			if (bFast && (TimeMSBetween(tInputEventPrePress.time, tInputEvent.time) > 50))
  			{ 
   				iIntervalSecond = iIndexPressed ? (iIntervalSecond - 1) : (iIntervalSecond + 1);
   				if (iIntervalSecond == 60)
   				{
    					iIntervalSecond = 0;
   				}
   				else if (iIntervalSecond == -1)
		   		{
		    			iIntervalSecond = 59;
		   		}
		   		GenerateIntervalPageSpecialIcon(iIntervalSecond, ptDevVideoMem);
   				tInputEventPrePress = tInputEvent;
  			}
  			if (TimeMSBetween(tInputEventPrePress.time, tInputEvent.time) > 2000)
  			{
   				bFast = 1;
   				tInputEventPrePress = tInputEvent;
  			}
 		}
         }  
}
六、全文总结
前文显示主界面基础上,增加了两个页面——设置页面和时间设置页面,此外实现时间间隔的加减(点击或长按)。
对于页面的显示,套路与显示主页面完全一样(代码可直接复制,再修改即可)。
三个页面的区别在于图标位置计算越来越复杂。这个并不难理解的,图纸上先画一下确定坐标位置再编程实现即可。

本文的难点在于加减时间间隔,涉及一系列的处理。但总体思路还是很清晰的。
第一步:计算时间间隔字符串在LCD的坐标位置  
第二步:根据字符串每个字符编码值确定字体位图数据,再写入缓冲块“相应位置”,最后刷到LCD的显存FB显示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值