STemWin Radial menu的实现方法和原理,motion support,sin cos求出每一个径向菜单中的每一个图标位置

一、实现思路:

1.首先我们想要的是滑动屏幕这个“手势”,然后图标就会跟着这个“手势”滑动的方向来运动(改变图标的位置)。
2.我们想要的是当图标滑到我们设定的位置左右时松开它能自动回正到我们设计好的正中位置(类似手机翻动到下一页并没有完全翻到时它能自动跳到下一页或退回到上一页对齐使其吸附到特定的位置,而不是处于中间或其它的任意位置),并且我们快速滑一下松开的时候会产生惯性移动的效果(第一点可以通过motion support的snapX snapY参数来实现,第二点可以通过其参数Period来实现或使用默认值)。
3.我们需要的是一个径向的菜单。如何算出每一个图标的位置呢?可由三角函数公式推算出来点我查看原理 y1 = r * Sin(θ) + y0 ; x1 = r * Sin(θ) + x0; 根据此公式就可以算出每个图标的位置,然后根据“手势”来更改θ值对应改变图标的位置。

二、实现过程:

所需要用到的关键元素:

1.motion support 运动功能支持,需要注意的是低版本的emWin是不支持的,这里用的是5.30。而且需要调用一次WM_MOTION_Enable(1);来使能运动功能。创建的时候 创建标记 WM_CF_MOTION_Y ,WM_CF_MOTION_Y 既可使能其X轴或Y轴的运动功能。也可调用WM_MOTION_SetMoveable(WM_HWIN hWin, U32 Flags, int OnOff)来使能或失能创建标记。
2.motion support 需要我们在程序中自定义处理,而不是用默认的处理方式否则就变成了对窗口的移动了。
3.制作图标,图标的来源可以由美工制作或者去相应的图标网站上下载。各种途径得到图片后需要图片最好是有alpha通道以便让图片的背景是透明的,如果下载的ICO的图片可以在安富莱的emwin教程里查看到如果转换ICO图片为位图点我查看怎么利用ps给图片加上alpha通道让图片背景透明最后用STemWin的位图转C小工具把其转为C文件便于调用。

三、开始实现

3.1实现步骤

一、WM_MOTION_Enable(1);开启motion support。
二、建一个窗口用来绘制图标,另一个窗口用来实现 motion support的相关操作。
三、编绘制图标的回调函数和motion support的回调函数,并由用户来处理motion support。
如果由管理器默认处理移动的就是窗口,如下图:
默认处理

3.2了解motion support,为实现做好准备

在实现此之前我们需要熟悉的是:WM_MOTION_Enable(1);开启了运动功能后,我们可以在回调中收到MsgId为WM_MOTION的消息,并且其消息中的Data.p里保存着WM_MOTION_INFO的结构体指针。这个结构体里的元素如下

DatatypeElement Description
intCmdDetails can be found in the section “Motion support” on page 383.
intdxDistance in X to be used to move the window.
intdyDistance in Y to be used to move the window.
intdaDistance in 1/10 dregrees to be used to move an item.
intxPosUsed to return the current position in X for custom moving operations.
intyPosUsed to return the current position in Y for custom moving operations.
intPeriodDuration of the moving operation after the PID has been released.
intSnapXRaster size in X for snapping operations, 0 if no snapping is required.
intSnapYRaster size in Y for snapping operations, 0 if no snapping is required.
intFinalMoveSet to 1 on the final moving operation.
U32FlagsTo be used to enable motion support.

这里讲清楚了每一个元素的含义和其作用。

WM_MOTION_INFO 结构体中的Cmd对应着当前操作的三种信息Cmd的三种可能的值如下:

FlagDescription
WM_MOTION_INITSent to a window to initiate a motion operation.
WM_MOTION_MOVESent to a window to achieve custom moving operations.
WM_MOTION_GETPOSSent to get the current position of custom moving operations.

以上图表,列举出了Cmd的值的可能性。
在创建窗口的时候也可以利用创建标志来来实现窗口的移动性,可用的标志如下:

FlagDescription
WM_CF_MOTION_REnables circular moveability for objects within the window.
WM_CF_MOTION_XEnables movability for the X axis.
WM_CF_MOTION_YEnables movability for the Y axis.

3.3建立两个窗口来处理绘制和处理motion support

3.3.1流程

首先需要对PAPA结构体中的一些数据初始化。
然后建立一个窗口,利用它的回调函数在窗口里绘制图标。
再建立一个窗口,利用其回调函数来自定义处理motion support产生图标的移动数据。

3.3.2 代码实现

 static ITEM_INFO  aItemInfo[GUI_COUNTOF(_aBitmapItem)]; //创建与图标数量所对应的ITEM_INFO来保存其坐标和索引
  static PARA       Para;
  WM_HWIN           hDraw;
  WM_HWIN           hMotion;
  PARA            * pPara;
  int               xSizeWindow;
  int               ySizeWindow;
  int               xSize;
  int               ySize;
  int               i;

  WM_SetCallback(WM_HBKWIN, _cbBk);
  xSize = LCD_GetXSize();
  ySize = LCD_GetYSize();
  //
  // Initialize parameter structure for items to be shown
  //
  pPara = &Para;
  pPara->NumItems    = GUI_COUNTOF(_aBitmapItem); //计算出图标的个数。
  pPara->pBitmapItem = _aBitmapItem;	 //指向图片数据数组,
  pPara->pItemInfo   = aItemInfo;				//指向每一个图标位置信息和索引号的数组
  pPara->Pos         = 0;
  //
  // Create radial menu window
  //
  hDraw              = WM_CreateWindowAsChild(20, 40, xSize - 40, ySize - 80, WM_HBKWIN,
   WM_CF_SHOW | WM_CF_HASTRANS, _cbDraw, sizeof(pPara));
  //
  // Create transparent window which receives the motion messages
  //
  xSizeWindow        = WM_GetWindowSizeX(hDraw);
  ySizeWindow        = WM_GetWindowSizeY(hDraw);
  hMotion            = WM_CreateWindowAsChild(0, ySizeWindow / 2, xSizeWindow, ySizeWindow / 2, WM_HBKWIN, 
  WM_CF_SHOW | WM_CF_MOTION_X | WM_CF_HASTRANS, _cbMotion, sizeof(pPara));

  //
  // Add pointer to parameter structure to windows
  //
  WM_SetUserData(hDraw,   &pPara, sizeof(pPara));
  WM_SetUserData(hMotion, &pPara, sizeof(pPara));
  //
  // Create checkbox
  //
  _CreateCheckbox(pPara);

3.3.3 代码说明

在上面的代码中需要注意的是我们把两个窗口都设置了WM_CF_HASTRANS透明,使其不遮盖桌面窗口。而且处理移动的窗口一定要在绘制窗口的上方,才能接受到操作信息。所以是先创建的绘制窗口再创建的处理移动的窗口。上面的代码也初始化了PAPA中的一部分数据,并创建了两个桌面子窗口且都给他们分配了额外的字节传入了用户数据(WM_SetUserData(hDraw, &pPara, sizeof(pPara)))。这里需要注意的是这个用户数据是一个结构体,其和其中的两个结构体成员如下代码:

typedef struct {
  const GUI_BITMAP * pBitmap; //图像数据
  const char       * pText;	//
  const char       * pExplanation; //说明 字符串
} BITMAP_ITEM;

typedef struct {
  int xPos;  
  int yPos; //X,Y坐标
  int Index; //索引
} ITEM_INFO;

typedef struct {
  int                 DoAnimation;
  int                 Pos;		//当前位置
  int                 NumItems;  //记录总共有多少个图标
  int                 xSizeItem,   ySizeItem; //图标的X,Y大小,每个图标的大小应当相同所以只记录一次
  int                 xSizeWindow, ySizeWindow;//窗口的x,y大小
  int                 rx,          ry;		//图标相对于圆心的半径
  int                 mx,          my;   //图标所围绕的中心
  int                 FinalMove;	 //记录“手势”移动,是否松开。
  const BITMAP_ITEM * pBitmapItem; //指向BITMAP_ITEM数组
  ITEM_INFO         * pItemInfo;	//指向ITEM_INFO数组
} PARA;

图标图片数据和其说明信息数组如下:

static const BITMAP_ITEM _aBitmapItem[] = {
  {&_bmBrowser, "Browser" , "Use the browser to explore the www"},
  {&_bmClock,   "Clock"   , "Adjust current time and date"},
  {&_bmDate,    "Date"    , "Use the diary"},
  {&_bmEmail,   "Email"   , "Read an email"},
  {&_bmSystem,  "System"  , "Change system settings"},
  {&_bmRead,    "Read"    , "Read a document"},
  {&_bmWrite,   "Write"   , "Write an email"},
  {&_bmPassword,"Password", "Determine the system password"},
  {&_bmwifi_48px,  "WIFI" , "Select WIFI"},
  {&_bmwechat_48px,  "Wechat" , "Wechat" },
  {&_bm_alipay_48px, "Alipay" , "Alipay" },
  { &_bmsingfun,	 "Singfun" , "Singfun" },
};

3.4创建窗口回调函数用于绘制图标

3.4.1绘制图标的整个流程

绘制图标所需要的一些信息:
绘制所需要的大部分信息都整合在 PARA结构体中,其中有
1.是需要做一个结构体来保存每一个图标的图片信息和其位置信息。
2.需要知道图标图片的大小以便用于绘制在位置的中心位置。
3.需要知道窗口的大小以便绘算出窗口的中心,和需要的半径x,y。
4.需要知道所有多少图标,便于计算位置的时候均匀分布。
5.需要知道 移动距离 是多少根据此来移动图标

3.4.2代码实现

static void _cbDraw(WM_MESSAGE * pMsg) {
  WM_HWIN     hWin;
  PARA      * pPara;
  int         i, a1000, Swap;
  I32         SinHQ;
  I32         CosHQ;
  I32         a;
  float		  aStep;
  ITEM_INFO   ItemInfo;

  hWin = pMsg->hWin;
  switch (pMsg->MsgId) {
  //case WM_MOTION:
	 // a = a;
	 // break;
  case WM_PAINT:
    WM_GetUserData(hWin, &pPara, sizeof(pPara));
    //
    // One time initialization of parameter structure
    //
    if (pPara->xSizeItem == 0) {
      pPara->xSizeWindow = WM_GetWindowSizeX(hWin);
      pPara->ySizeWindow = WM_GetWindowSizeY(hWin);
      pPara->xSizeItem   = pPara->pBitmapItem[0].pBitmap->XSize;
      pPara->ySizeItem   = pPara->pBitmapItem[0].pBitmap->YSize;
      pPara->rx          = ( pPara->xSizeWindow       - pPara->xSizeItem) / 2; 
      pPara->ry          = ((pPara->ySizeWindow - 30) - pPara->ySizeItem) / 2; //算出需要计算的半径要考虑不能绘制到窗口之外去了。
      pPara->mx          = pPara->xSizeWindow / 2;	
	  pPara->my			 = (pPara->ySizeWindow - 30) / 2; //窗口中心的位置,这里-30是为了留出显示文字说明的空间
	  pPara->FinalMove	 = 1;
    }
    //
    // Calculate current positions of items
    //
    a1000 = (pPara->Pos * 3600) / pPara->NumItems;// 每次移动一下就是100就是360° 再根据实际的图标个数来移动对应的角度
	aStep = (360000 / pPara->NumItems);
    for (i = 0; i < pPara->NumItems; i++) {
      a = 90000 + a1000 + i * aStep;//从90° 的地方开始每次根据图标个数均匀分配的值加上移动量来改变每一个图标的位置
      SinHQ = GUI__SinHQ(a);
      CosHQ = GUI__CosHQ(a);
      pPara->pItemInfo[i].Index = i; //此处的Index与每一个坐标对应也与每一个图标对应  
      pPara->pItemInfo[i].xPos  = pPara->mx - ((CosHQ * pPara->rx) >> 16); //(x1=r*cosA+x0)这里+0以零点先把位置算出来再以中点来移动
      pPara->pItemInfo[i].yPos  = pPara->my + ((SinHQ * pPara->ry) >> 16);//计算出每一个图片的当前位置(y1=r*sinA+y0);
    }//>>16和GUI__CosHQ这两个函数有关,可能是为了去除他的小数部分。
    //
    // Bubble sort items to be able to draw background items first
    //
      for (i = 0; i < (pPara->NumItems - 1); i++) { //pItemInfo数组下标的意义是把Y坐标的最大值排序到末尾
        if (pPara->pItemInfo[i].yPos > pPara->pItemInfo[i + 1].yPos) {
          ItemInfo                = pPara->pItemInfo[i];
          pPara->pItemInfo[i]     = pPara->pItemInfo[i + 1];
          pPara->pItemInfo[i + 1] = ItemInfo;
        }
      }
  //这里应当只需要排一次 因为我们只需要知道最大的Y值便可以知道当前选中了哪一个。
    //
    // Draw items
    //
    for (i = 0; i < pPara->NumItems; i++) {
      GUI_DrawBitmap((pPara->pBitmapItem + pPara->pItemInfo[i].Index)->pBitmap, pPara->pItemInfo[i].xPos - pPara->xSizeItem / 2, pPara->pItemInfo[i].yPos - pPara->ySizeItem / 2);
    }//显示图片且显示在x,y的中间位置
    //
    // Draw item text only after final move
    //
    if (pPara->FinalMove) {
      GUI_SetTextMode(GUI_TM_TRANS);
      GUI_SetFont(GUI_FONT_13_ASCII);
      GUI_SetColor(GUI_WHITE);
      GUI_DispStringHCenterAt((pPara->pBitmapItem + pPara->pItemInfo[pPara->NumItems - 1].Index)->pExplanation, pPara->xSizeWindow / 2, pPara->ySizeWindow - 15);
    }
    //
    // Draw frame surround the current item
    //
    GUI_SetColor(GUI_RED);
    GUI_DrawBitmap(&_bmRect_60x60, (pPara->xSizeWindow - _bmRect_60x60.XSize) / 2, pPara->my + pPara->ry - _bmRect_60x60.YSize / 2);
    break;
  }
}

3.4.3 代码说明

在上面的回调函数处理中可以看到,在收到WM_PAINT绘制指令后,如果没有计算过相应的数据就先计算出了PAPA结构体中的一部分数据,接下来的这两行代码比较容易理解

a1000 = (pPara->Pos * 3600) / pPara->NumItems;// 每次移动一下就是100就是360° 再根据实际的图标个数来移动对应的角度
	aStep = (360000 / pPara->NumItems);

第一行代码算出a1000的值,它算出当前这次计算时的初始角度(没有移动前第一次应当是0)。可以看到他的数值与Pos和NumItems有关,Pos保存的是操作屏幕的时候“手势”所移动的距离,在下面会讲到。NumItems在前面讲到过是图标元素的个数。
简单说明一下:GUI__SinHQ(a)所需传入的值是需要*1000的目的是为了获取更高的精度,Pos的值设置的是100像素的栅格自动吸附对齐的(在下面会有说明)所以这里可以看成是pPara->Pos *3.6 移动完一个100的距离刚好是360°,如果有多个元素再/pPara->NumItems,每一次的移动就对应着一个图标的完整移动。

第二行代码本次图标与上一次个图标所相差的角度。

接下来的代码就容易理解了。

a = 90000 + a1000 + i * aStep;

每个图标的角度等于初始角度+90°的自定义角度+本次图标的偏移角度。这里自定义90°是为了让第一个图标从窗口的最下方的中间开始显示。
还需要注意的就是手势移动的时候 往左移得到的值是-往右移得到的数值是 + 。但当角度增大的时候我们算出来的坐标是顺时针移动而减小的时候是逆时针移动,这与我们的操作习惯相反。

pPara->pItemInfo[i].xPos  = pPara->mx - ((CosHQ * pPara->rx) >> 16); 
pPara->pItemInfo[i].yPos  = pPara->my + ((SinHQ * pPara->ry) >> 16);

于是这里对X轴计算出来后对他反相就可以使之与我们的习惯匹配。
接下来我们找出Y值的最大值,意义是找出哪一个在原点位置,当我们移动图标的时候就可以知道是哪一个在原点位置表示我们所选定的图标。
然后再把每一个图标根据上面算出来的位置给绘制到窗口上,如果“手势移动”已结束了那么就显示出来对应图标的说明文字最后绘制一个选定框的图像到初始位置,以便清晰的看懂是哪个图标被选中了。
以上便是整个绘制过程了。

3.5创建窗口回调函数用于处理手势操作

3.5.1WM_MOTION事件和相应结构体成员及所需要处理的内容详细说明

处理WM_MOTION事件 pInfo = (WM_MOTION_INFO *)pMsg->Data.p; 把Data.p强转为WM_MOTION_INFO类型的结构体,再根据其Cmd成员,来处理WM_MOTION_INIT,WM_MOTION_MOVE,WM_MOTION_GETPOS这几种命令。
1.WM_MOTION_INIT:
If a PID move has been detected by the WM it first checks if there is any visible window available under the PID position which is already ’moveable’. This makes it possible to achieve moving operations for windows which are partially or totally covered
by child windows. If the WM does not find an already moveable window it sends the
command to the ’top window’ of the PID position.
如果PID移动被窗口管理器(WM)所检测到,首先它会检测在PID位置下是否有可见有用的并且已经是可移动的窗口。这使得实现部分或全部被子窗口覆盖的移动操作成功可能。如果窗口管理器没有找到一个“已可移动”的窗口它就会把命令发送到PID位置的顶层窗口。

If the window is not already ’moveable’ when receiving this command the element
Flags of the WM_MOTION_INFO structure can be used to enable motion support. The
creation flags explained earlier can be used here to achieve automatic motion support. The Flags element simply needs to be OR-combined with the desired flag(s)
如果窗口还没有“已可移动”,当收到这个命令时WM_MOTION_INFO结构体的Flags成员可以被用来使能motion support。早些时候对创建标志的解释可以被用在这里实现自动运动支持。结构体中的Flags 成员可以根据你所期望的标志简单的“或”在一起。
2.WM_MOTION_INIT and custom motion support:
Custom motion support means that the moving operations are not done automatically by the WM but by the callback routine of the window. This can be useful if for
example radial motions are required. To achieve custom motion support the Flags
element needs to be OR-combined with the flag WM_MOTION_MANAGE_BY_WINDOW.

自定义的运动支持意味着 移动操作不是由窗口管理器自动的完成,而是由窗口的回调程序,举个例子:如果需要径向移动,这将是非常有用的。实现自定义运动功能需要让Flags成员“或”上WM_MOTION_MANAGE_BY_WINDOW标志。
3.WM_MOTION_MOVE:
Sent to a window with custom motion support enabled. The elements dx and dy of
the WM_MOTION_INFO structure can be used to achieve the custom moving operation.

此命令发送到启动了自定义运动支持的窗口中。结构体WM_MOTION_INFO成员dx和dy可以被用来实现自定义移动操作。
4.WM_MOTION_GETPOS:
Sent to a window with custom motion support enabled. The task of the callback routine here is returning the current position. This needs to be done with the elements
xPos and yPos of the WM_MOTION_INFO structure.
此命令发送到启动了自定义运动支持的窗口中,回调函数在这里的任务就是返回当前的位置,结构体WM_MOTION_INFO成员xPos and yPos需要在这里被处理完成。

5.Snapping:
The elements SnapX and SnapY of the WM_MOTION_INFO structure can be used to
achieve snapping. These values determine a kind of grid for snapping. This means
that the deceleration of the movement operation will stop exactly on a grid position.
Also if there currently is no movement and the window is only released it will snap
into the next grid position.

结构体WM_MOTION_INFO成员 SnapX and SnapY可以被用来实现捕获。这些值用来确定捕获栅格,这意味着移动操作的减速将恰好停留在一个栅格的位置。并且如果当前没有移动并且窗口仅被释放它将会捕捉到下一个栅格位置。(用力一滑松开,它会像惯性一样跑一段距离并减速下来)

3.5.2 代码实现

static void _cbMotion(WM_MESSAGE * pMsg) {
  WM_MOTION_INFO * pInfo;
  WM_HWIN          hWin;
  PARA           * pPara;
  GUI_RECT rec;
  hWin = pMsg->hWin;
  switch (pMsg->MsgId) {
  case WM_MOTION:
    WM_GetUserData(hWin, &pPara, sizeof(pPara));
    pInfo = (WM_MOTION_INFO *)pMsg->Data.p;
    switch (pInfo->Cmd) {
    case WM_MOTION_INIT:
      pInfo->Flags = WM_CF_MOTION_X | WM_MOTION_MANAGE_BY_WINDOW; //
      pInfo->SnapX = 100;//100的栅格
      break;
    case WM_MOTION_MOVE:
      pPara->FinalMove = pInfo->FinalMove;
      pPara->Pos += pInfo->dx;
	  
      if (pPara->Pos > pPara->NumItems * 100) {//正转或反转超过了就从原点开始
        pPara->Pos -= pPara->NumItems * 100;
      }
      if (pPara->Pos < 0) {
        pPara->Pos += pPara->NumItems * 100;
      }
      WM_Invalidate(WM_GetParent(hWin));
      break;
    case WM_MOTION_GETPOS:
      pInfo->xPos = pPara->Pos;  //每次最终的xPos都需要回送给系统做处理
      break;
    }
    break;
  }
}

3.5.3代码说明

在上面的代码中,首先取出了自定义的用户数据然后把Data.p强转为WM_MOTION_INFO类型的结构体再处理Cmd的消息。
在WM_MOTION_INIT 中我们设置了X轴的运动功能,并且设置了运动功能由用户程序自定义管理,并且设置了100的栅格。移动将会被自动吸附到栅格中。
在WM_MOTION_MOVE 所得到的信息主要就是移动量和是否完成移动的消息。
把这个移动量赋值给自定义PAPA结构体的成员Pos.前面讲过会把这个位置经过计算转换成相应的角度信息,使得这里移动一个栅格图标的位置刚好也移动到一个图标的位置,利用栅格的自动捕捉的特性使图标的位置也具有捕捉的效果。
把是否移动完成赋值给用户参数中的pPara->FinalMove以便在绘制的时候收到移动完成的消息后显示图标的说明。
把得到的位置值进行确认是否超过了最大值或最小值,并做出处理。
最后我们再使桌面窗口无效化使其重绘,这里应当直接重绘绘制图标的窗口。
在WM_MOTION_GETPOS中我们按照要求把最xPos的值交由管理器处理。

四、结语

参考文档主要就是emwin的使用手册,然后就是安富莱和正点原子的emwin开发手册。还有一些小技巧小工具都放在文章的链接中了。
文中的翻译可能不太精确,详细的内容请参照emwin手册。
本文的源代码在官方的仿真文件里WM_RadialMenu.c中可以找到完整的代码。
效果如图:
自定义处理

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值