SourceCode/Bond/Servo/ServoDlg.cpp
@@ -6,11 +6,20 @@
#include "Servo.h"
#include "ServoDlg.h"
#include "afxdialogex.h"
#include "Common.h"
#include "Log.h"
#include "SecsTestDlg.h"
#include <chrono>
#include <thread>
#include <cmath>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// Image
#define IMAGE_ROBOT            2
#define INDICATE_BONDER1      1
#define INDICATE_BONDER2      2
@@ -68,9 +77,11 @@
   : CDialogEx(IDD_SERVO_DIALOG, pParent)
{
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
   m_crBkgnd = RGB(255, 255, 255);
   m_crBkgnd = APPDLG_BACKGROUND_COLOR;
   m_hbrBkgnd = nullptr;
   m_bShowLogWnd = FALSE;
   m_bIsRobotMoving = FALSE;
   m_pLogDlg = nullptr;
}
void CServoDlg::DoDataExchange(CDataExchange* pDX)
@@ -88,6 +99,22 @@
   ON_WM_CTLCOLOR()
   ON_WM_DESTROY()
   ON_BN_CLICKED(IDC_BUTTON_LOG, &CServoDlg::OnBnClickedButtonLog)
   ON_WM_SIZE()
   ON_WM_CLOSE()
   ON_MESSAGE(ID_MSG_LOGDLG_HIDE, &CServoDlg::OnLogDlgHide)
   ON_WM_MOVING()
   ON_WM_MOVE()
   ON_COMMAND(ID_MENU_FILE_EXIT, &CServoDlg::OnMenuFileExit)
   ON_UPDATE_COMMAND_UI(ID_MENU_FILE_EXIT, &CServoDlg::OnUpdateMenuFileExit)
   ON_COMMAND(ID_MENU_FILE_SETTINGS, &CServoDlg::OnMenuFileSettings)
   ON_UPDATE_COMMAND_UI(ID_MENU_FILE_SETTINGS, &CServoDlg::OnUpdateMenuFileSettings)
   ON_COMMAND(ID_MENU_FILE_SECSTEST, &CServoDlg::OnMenuFileSecsTest)
   ON_UPDATE_COMMAND_UI(ID_MENU_FILE_SECSTEST, &CServoDlg::OnUpdateMenuFileSecsTest)
   ON_COMMAND(ID_MENU_WND_LOG, &CServoDlg::OnMenuWndLog)
   ON_UPDATE_COMMAND_UI(ID_MENU_WND_LOG, &CServoDlg::OnUpdateMenuWndLog)
   ON_COMMAND(ID_MENU_HELP_ABOUT, &CServoDlg::OnMenuHelpAbout)
   ON_WM_INITMENUPOPUP()
   ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
@@ -123,12 +150,21 @@
   SetIcon(m_hIcon, FALSE);      // 设置小图标
                           // 菜单
   CMenu menu;
   menu.LoadMenu(IDR_MENU_APP);
   SetMenu(&menu);
   // ͼʾ
   m_pGraph = CServoGraph::Hook(GetDlgItem(IDC_SERVO_GRAPH1)->GetSafeHwnd());
   CString strPath;
   strPath.Format(_T("%s\\res\\Servo001.bmp"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
   m_pGraph->AddImage(1, (LPTSTR)(LPCTSTR)strPath, 0, 0);
   strPath.Format(_T("%s\\res\\Robot001.bmp"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
   m_pGraph->AddImage(IMAGE_ROBOT, (LPTSTR)(LPCTSTR)strPath, 170, 270);
   // 添加指示器
   // Bonder
@@ -177,10 +213,10 @@
   // Robot
   m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM1, 620, 294, 48, RGB(22, 22, 22),
   m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM1, 190, 294, 48, RGB(22, 22, 22),
      RGB(255, 127, 39), RGB(0, 176, 80));
   m_pGraph->SetBoxText(INDICATE_ROBOT_ARM1, "5", "Robot");
   m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM2, 673, 294, 48, RGB(22, 22, 22),
   m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM2, 243, 294, 48, RGB(22, 22, 22),
      RGB(255, 127, 39), RGB(0, 176, 80));
   m_pGraph->SetBoxText(INDICATE_ROBOT_ARM2, "6", "Robot");
@@ -203,6 +239,20 @@
   m_pGraph->SetBoxText(INDICATE_MEASUREMENT, "13", "Measurement");
   // 调整初始窗口位置
   CRect rcWnd;
   GetWindowRect(&rcWnd);
   int width = GetSystemMetrics(SM_CXSCREEN);
   int height = GetSystemMetrics(SM_CYSCREEN);
   MoveWindow((width - rcWnd.Width()) / 2, 0, rcWnd.Width(), rcWnd.Height(), TRUE);
   // model init
   theApp.m_model.init();
   UpdateLogBtn();
   Resize();
   return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}
@@ -255,7 +305,123 @@
   return static_cast<HCURSOR>(m_hIcon);
}
void CServoDlg::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
   ASSERT(pPopupMenu != NULL);
   CCmdUI state;
   state.m_pMenu = pPopupMenu;
   ASSERT(state.m_pOther == NULL);
   ASSERT(state.m_pParentMenu == NULL);
   HMENU hParentMenu;
   if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu)
      state.m_pParentMenu = pPopupMenu;
   else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)
   {
      CWnd* pParent = this;
      if (pParent != NULL &&
         (hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL)
      {
         int nIndexMax = ::GetMenuItemCount(hParentMenu);
         for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
         {
            if (::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu)
            {
               state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
               break;
            }
         }
      }
   }
   state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
   for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
   state.m_nIndex++)
   {
      state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
      if (state.m_nID == 0)
         continue;
      ASSERT(state.m_pOther == NULL);
      ASSERT(state.m_pMenu != NULL);
      if (state.m_nID == (UINT)-1)
      {
         state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
         if (state.m_pSubMenu == NULL ||
            (state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
            state.m_nID == (UINT)-1)
         {
            continue;
         }
         state.DoUpdate(this, TRUE);
      }
      else
      {
         state.m_pSubMenu = NULL;
         state.DoUpdate(this, FALSE);
      }
      UINT nCount = pPopupMenu->GetMenuItemCount();
      if (nCount < state.m_nIndexMax)
      {
         state.m_nIndex -= (state.m_nIndexMax - nCount);
         while (state.m_nIndex < nCount &&
            pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
         {
            state.m_nIndex++;
         }
      }
      state.m_nIndexMax = nCount;
   }
}
void CServoDlg::OnMenuFileSettings()
{
}
void CServoDlg::OnUpdateMenuFileSettings(CCmdUI* pCmdUI)
{
   pCmdUI->Enable(TRUE);
}
void CServoDlg::OnMenuFileSecsTest()
{
   CSecsTestDlg dlg;
   dlg.DoModal();
}
void CServoDlg::OnUpdateMenuFileSecsTest(CCmdUI* pCmdUI)
{
   pCmdUI->Enable(TRUE);
}
void CServoDlg::OnMenuWndLog()
{
   OnBnClickedButtonLog();
}
void CServoDlg::OnUpdateMenuWndLog(CCmdUI* pCmdUI)
{
   pCmdUI->SetCheck(m_bShowLogWnd);
}
void CServoDlg::OnMenuFileExit()
{
   PostMessage(WM_CLOSE);
}
void CServoDlg::OnUpdateMenuFileExit(CCmdUI* pCmdUI)
{
   pCmdUI->Enable(TRUE);
}
void CServoDlg::OnMenuHelpAbout()
{
   CAboutDlg dlgAbout;
   dlgAbout.DoModal();
}
void CServoDlg::OnBnClickedOk()
{
@@ -290,6 +456,12 @@
{
   CDialogEx::OnDestroy();
   if (m_pLogDlg != nullptr) {
      m_pLogDlg->DestroyWindow();
      delete m_pLogDlg;
      m_pLogDlg = nullptr;
   }
   if (m_hbrBkgnd != nullptr) {
      ::DeleteObject(m_hbrBkgnd);
   }
@@ -298,10 +470,198 @@
void CServoDlg::OnBnClickedButtonLog()
{
   m_bShowLogWnd = !m_bShowLogWnd;
   m_btnLog.SetFrameColor(BS_NORMAL, BTN_JOG_FRAME_NORMAL);
   m_btnLog.SetFrameColor(BS_HOVER, BTN_JOG_FRAME_HOVER);
   m_btnLog.SetFrameColor(BS_PRESS, BTN_JOG_FRAME_PRESS);
   m_btnLog.SetBkgndColor(BS_NORMAL, BTN_JOG_BKGND_NORMAL);
   m_btnLog.SetBkgndColor(BS_HOVER, BTN_JOG_BKGND_HOVER);
   m_btnLog.SetBkgndColor(BS_PRESS, BTN_JOG_BKGND_PRESS);
   if (m_pLogDlg == nullptr) {
      m_pLogDlg = new CLogDlg();
      m_pLogDlg->Create(IDD_DIALOG_LOG, this);
      CRect rcWnd;
      GetWindowRect(&rcWnd);
      m_pLogDlg->MoveWindow(rcWnd.left, rcWnd.bottom - 8, rcWnd.Width(), 200);
   }
   ASSERT(m_pLogDlg);
   m_pLogDlg->ShowWindow(m_bShowLogWnd ? SW_SHOW : SW_HIDE);
   UpdateLogBtn();
}
void CServoDlg::UpdateLogBtn()
{
   m_btnLog.SetFrameColor(BS_NORMAL, BTN_LOG_FRAME_NORMAL);
   m_btnLog.SetFrameColor(BS_HOVER, BTN_LOG_FRAME_HOVER);
   m_btnLog.SetFrameColor(BS_PRESS, BTN_LOG_FRAME_PRESS);
   m_btnLog.SetBkgndColor(BS_NORMAL, m_bShowLogWnd ? BTN_LOG_BKGND_PRESS : BTN_LOG_BKGND_NORMAL);
   m_btnLog.SetBkgndColor(BS_HOVER, BTN_LOG_BKGND_HOVER);
   m_btnLog.SetBkgndColor(BS_PRESS, BTN_LOG_BKGND_PRESS);
   m_btnLog.Invalidate();
}
void CServoDlg::UpdateRobotPosition(float percentage)
{
   // 限制百分比范围在 [0, 1] 之间
   if (percentage < 0.0f) percentage = 0.0f;
   if (percentage > 1.0f) percentage = 1.0f;
   // 根据百分比计算目标 X 坐标
   int startX = m_pGraph->GetImage(IMAGE_ROBOT)->x;
   int endX = static_cast<int>(170 + percentage * (700 - 170));
   int arm1Offset = 20;  // 从图片到ARM1的偏移
   int arm2Offset = 73;  // 从图片到ARM2的偏移
   // 计算移动所需的时间
   int distance = abs(endX - startX);
   int duration = static_cast<int>((distance / 100.0) * 1000);
   auto startTime = std::chrono::steady_clock::now();
   auto endTime = startTime + std::chrono::milliseconds(duration);
   // 开始移动,设置标记
   m_bIsRobotMoving = TRUE;
   // 开始平滑移动
   while (std::chrono::steady_clock::now() < endTime) {
      auto currentTime = std::chrono::steady_clock::now();
      float progress = std::chrono::duration<float, std::milli>(currentTime - startTime).count() / duration;
      progress = min(progress, 1.0f);
      // 根据进度计算当前位置
      int currentX = static_cast<int>(startX + progress * (endX - startX));
      m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, currentX, 270);
      m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, currentX + arm1Offset, 294);
      m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, currentX + arm2Offset, 294);
      // 刷新界面
      Invalidate();
      UpdateWindow();
      // 控制帧率约为 60 FPS
      std::this_thread::sleep_for(std::chrono::milliseconds(16));
   }
   // 确保最后位置精确到目标位置
   m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, endX, 270);
   m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, endX + arm1Offset, 294);
   m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, endX + arm2Offset, 294);
   // 界面重绘
   Invalidate();
   // 动画结束,设置标记
   m_bIsRobotMoving = FALSE;
}
void CServoDlg::RotateRobot(float angleInDegrees)
{
   // 将角度转换为弧度
   float angleInRadians = static_cast<float>(std::acos(-1)) / 180.0f * angleInDegrees;
   // 获取机器人图片的当前坐标和中心
   auto* pImage = m_pGraph->GetImage(IMAGE_ROBOT);
   if (!pImage) return;
   // 更新 Rotate 图片的角度,确保角度保持在 [0, 360) 范围内
   m_pGraph->UpdateImageAngle(IMAGE_ROBOT, static_cast<float>(fmod(pImage->angle + angleInDegrees + 360, 360)));
   int cx = pImage->x + pImage->bmWidth / 2;  // 图片中心 X
   int cy = pImage->y + pImage->bmHeight / 2; // 图片中心 Y
   // 旋转指示框的坐标
   auto* pRobot1 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM1);
   auto* pRobot2 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM2);
   if (pRobot1 && pRobot2) {
      int newArmX1 = pImage->x + 20;
      int newArmY1 = 294;
      int newArmX2 = pImage->x + 73;
      int newArmY2 = 294;
      if (angleInDegrees != 0.0f) {
         // 计算指示框1的新坐标
         newArmX1 = static_cast<int>(cx + (pRobot1->x - cx) * cos(angleInRadians) - (pRobot1->y - cy) * sin(angleInRadians));
         newArmY1 = static_cast<int>(cy + (pRobot1->x - cx) * sin(angleInRadians) + (pRobot1->y - cy) * cos(angleInRadians));
         // 计算指示框2的新坐标
         newArmX2 = static_cast<int>(cx + (pRobot2->x - cx) * cos(angleInRadians) - (pRobot2->y - cy) * sin(angleInRadians));
         newArmY2 = static_cast<int>(cy + (pRobot2->x - cx) * sin(angleInRadians) + (pRobot2->y - cy) * cos(angleInRadians));
      }
      // 更新指示框的位置
      m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, newArmX1, newArmY1);
      m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, newArmX2, newArmY2);
   }
   // 强制重绘界面
   Invalidate();
}
void CServoDlg::OnSize(UINT nType, int cx, int cy)
{
   CDialogEx::OnSize(nType, cx, cy);
   if (GetDlgItem(IDC_SERVO_GRAPH1) == nullptr) return;
   Resize();
}
void CServoDlg::Resize()
{
   CRect rcClient, rcItem;
   CWnd* pItem = nullptr;
   int x, y;
   x = 0;
   y = 0;
   pItem = GetDlgItem(IDC_SERVO_GRAPH1);
   pItem->GetClientRect(&rcItem);
   pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
   y += rcItem.Height();
   y += 8;
   x = 8;
   pItem = GetDlgItem(IDC_BUTTON_LOG);
   pItem->GetClientRect(&rcItem);
   pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
}
void CServoDlg::OnClose()
{
   // TODO: 在此添加消息处理程序代码和/或调用默认值
   CDialogEx::OnClose();
}
LRESULT CServoDlg::OnLogDlgHide(WPARAM wParam, LPARAM lParam)
{
   m_bShowLogWnd = FALSE;
   UpdateLogBtn();
   LOGE("OnLogDlgHide");
   return 0;
}
void CServoDlg::OnMoving(UINT fwSide, LPRECT pRect)
{
   CDialogEx::OnMoving(fwSide, pRect);
}
void CServoDlg::OnMove(int x, int y)
{
   if (m_pLogDlg != nullptr && !m_pLogDlg->IsZoomed()) {
      CRect rcWnd;
      GetWindowRect(&rcWnd);
      m_pLogDlg->MoveWindow(rcWnd.left, rcWnd.bottom - 8, rcWnd.Width(), 200);
   }
   CDialogEx::OnMove(x, y);
}
BOOL CServoDlg::OnEraseBkgnd(CDC* pDC)
{
   // TODO: 在此添加消息处理程序代码和/或调用默认值
   if (m_bIsRobotMoving) {
      // 禁止刷新背景,避免闪烁
      return TRUE;
   }
   return CDialogEx::OnEraseBkgnd(pDC);
}