개발은 하는건가..

[MFC] 가상 조이스틱 패드 컨트롤 본문

C, C++, MFC

[MFC] 가상 조이스틱 패드 컨트롤

수동애비 2022. 10. 27. 14:06
반응형

카메라의 Pan Tilt 조정에  모바일 게임에서 사용되는 가상 조이스틱 패드 같은 컨트롤이 필요하여  만들어 보았다.

 

사용방법

// GDI+ 를 사용하므로 Application 클래스에서 GDI+ 를 사용할 수 있도록 초기화 한다.

// 리소스 편집기에서 Static  컨트롤을 올리고 컨트롤 변수를 생성하여 CPantTiltCtrl 로 서브클래싱한다.

//CStatic			m_PanTiltCtrl;
CPanTiltCtrl	m_PanTiltCtrl;

// 컨트롤 변수를 통해 초기화 함수 호출
m_PanTiltCtrl.InitControl(GetAppImagePath(), this);
m_PanTiltCtrl.SetBackgroundColor(RGB(32, 35, 54));

 

컨트롤에서 사용되는 2개의 png 파일이 필요하다.  
InitControl() 함수의 첫번째 파라메터는 이미지 파일이 있는 경로를 지정해 준다.

BG_PTZ_PANEL.png  - 컨트롤 배경 판넬용 이미지

IMG_PTZ_JOG.png - 컨트롤 스틱 이미지

 

헤더 파일

#pragma once
#include <afxwin.h>


#define RESPONSE_SENSIT		40

#define TMR_TILT_ACTION		1000
#define TMR_PAN_ACTION		2000

#define MSG_PTZ_CTRL_B		WM_USER + 4002

#define ACTB_TILT_NONE		100
#define ACTB_TILT_UP		101
#define ACTB_TILT_DOWN		102
#define ACTB_TILT_STOP		103

#define ACTB_PAN_NONE		200
#define ACTB_PAN_LEFT		201
#define ACTB_PAN_RIGHT		202
#define ACTB_PAN_STOP		203

#define PI					3.1415


class CPanTiltCtrl :public CStatic
{
public:
	void			InitControl(CString strImagePath, CWnd *pParent);

	void			EnableControl(BOOL bEnabled);	
	void			SetBackgroundColor(COLORREF clr);
	CString			ActionToString(UINT act);

	inline BOOL		IsEnableControl() { return m_bEnabled; }

private:
	BOOL			m_bInit = FALSE;
	BOOL			m_bEnabled = TRUE;
	
	Gdiplus::Image	*m_pBaseImage = NULL;
	Gdiplus::Image	*m_pJogImage = NULL;
	CWnd			*m_pParent = NULL;
	
	int				m_nPosX = 0, m_nPosY = 0;
	int				m_nCenterX = 0, m_nCenterY = 0;
	int				m_nWidth = 0, m_nHeight = 0;
	int				m_nMoveMode = -1;
		
	BOOL			m_bDragging = FALSE;
	
	int				m_nTiltActionType = ACTB_TILT_NONE;
	int				m_nTiltStrength = 0;

	int				m_nPanActionType = ACTB_PAN_NONE;
	int				m_nPanStrength = 0;

	int				m_nPrevTiltActionType = 0;
	int				m_nPrevTiltStrength = 0;

	int				m_nPrevPanActionType = 0;
	int				m_nPrevPanStrength = 0;

	int				m_nDeltaX = 0;
	int				m_nDeltaY = 0;


	COLORREF		m_clBgColor = RGB(0, 0, 0);

	
	void			UpdateCurrentPos();
	void			DrawImage(Gdiplus::Graphics *pGrs, Gdiplus::Image *pImg, int x, int y, float alpha);
	double			GetDistancePoints(POINT *p1, POINT *p2);

	void			NotifyEvent(int actType, int strength = 0);
	

public:
	DECLARE_MESSAGE_MAP()
	virtual void	PreSubclassWindow();

	afx_msg int		OnCreate(LPCREATESTRUCT lpCreateStruct);		
	afx_msg void	OnDestroy();
	afx_msg void	OnNcPaint();
	afx_msg BOOL	OnEraseBkgnd(CDC* pDC);
	afx_msg void	OnPaint();
	afx_msg void	OnSize(UINT nType, int cx, int cy);
	afx_msg void	OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void	OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void	OnMouseMove(UINT nFlags, CPoint point);	
	afx_msg void	OnTimer(UINT_PTR nIDEvent);
};

 

cpp 파일

#include "../pch.h"
#include "PanTiltCtrl.h"


const CRect gRectCenter(100, 101, 146, 146);

BEGIN_MESSAGE_MAP(CPanTiltCtrl, CWnd)
	ON_WM_CREATE()	
	ON_WM_DESTROY()
	ON_WM_NCPAINT()
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_SIZE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_TIMER()
END_MESSAGE_MAP()


int CPanTiltCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	return 0;
}


void CPanTiltCtrl::PreSubclassWindow()
{
	DWORD dwStyle = GetStyle();
	::SetWindowLong(GetSafeHwnd(), GWL_STYLE, dwStyle | SS_NOTIFY);

	ModifyStyleEx(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE, NULL, SWP_FRAMECHANGED);

	CStatic::PreSubclassWindow();

}


void CPanTiltCtrl::InitControl(CString strImagePath, CWnd *pParent)
{
	if (m_bInit == TRUE) {
		return;
	}

	m_pParent = pParent;

	m_pBaseImage = Gdiplus::Image::FromFile(strImagePath + _T("\\BG_PTZ_PANEL.png"));
	m_pJogImage = Gdiplus::Image::FromFile(strImagePath + _T("\\IMG_PTZ_JOG.png"));
	
	if (m_pBaseImage != NULL && m_pBaseImage->GetLastStatus() == Gdiplus::Ok) {
		UINT w = m_pBaseImage->GetWidth();
		UINT h = m_pBaseImage->GetHeight();
		SetWindowPos(NULL, 0, 0, w + 1, h + 1, SWP_NOMOVE);
	}

	m_bInit = TRUE;
}

void CPanTiltCtrl::EnableControl(BOOL bEnabled)
{
	m_bEnabled = bEnabled;
	Invalidate();
}


void CPanTiltCtrl::OnDestroy()
{	
	CWnd::OnDestroy();

}


void CPanTiltCtrl::OnNcPaint()
{
	// Nop
}

void CPanTiltCtrl::DrawImage(Gdiplus::Graphics *pGrs, Gdiplus::Image *pImg, int x, int y, float alpha)
{
	const Gdiplus::ColorMatrix colorMatrix = {
		1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 0.0f, alpha, 0.0f,
		0.0f, 0.0f, 0.0f, 0.0f, 1.0f
	};

	Gdiplus::ImageAttributes imageAtt;
	imageAtt.SetColorMatrix(&colorMatrix,
		Gdiplus::ColorMatrixFlagsDefault,
		Gdiplus::ColorAdjustTypeBitmap);

	pGrs->DrawImage(
		pImg,
		Gdiplus::Rect(x, y, pImg->GetWidth(), pImg->GetHeight()),  
		0, 0, pImg->GetWidth(), pImg->GetHeight(),                 
		Gdiplus::UnitPixel,
		&imageAtt);
}


BOOL CPanTiltCtrl::OnEraseBkgnd(CDC* pDC)
{		
	CMemDC memDC(*pDC, this);

	pDC = &memDC.GetDC();
	
	pDC->FillSolidRect(CRect(0, 0, m_nWidth, m_nHeight), m_clBgColor);

	Gdiplus::Graphics grs(pDC->GetSafeHdc());

	if (m_pBaseImage != NULL) {
		if (m_bEnabled == TRUE) {
			DrawImage(&grs, m_pBaseImage, 0, 0, 1.0f);

			if (m_pJogImage != NULL && m_pJogImage->GetLastStatus() == Gdiplus::Ok) {
				int posX = m_nCenterX + m_nPosX - (m_pJogImage->GetWidth() / 2);
				int posY = m_nCenterY + m_nPosY - (m_pJogImage->GetHeight() / 2);

				grs.DrawImage(m_pJogImage,
					Gdiplus::Rect(posX, posY, m_pJogImage->GetWidth(), m_pJogImage->GetHeight()),
					0, 0, m_pJogImage->GetWidth(), m_pJogImage->GetHeight(),
					Gdiplus::UnitPixel);
			}
		}
		else {
			DrawImage(&grs, m_pBaseImage, 0, 0, 0.7f);
		}
	}
		
	return FALSE;
}


void CPanTiltCtrl::OnPaint()
{
	CPaintDC dc(this);
}


void CPanTiltCtrl::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	m_nCenterX = cx / 2;
	m_nCenterY = cy / 2;

	m_nWidth = cx;
	m_nHeight = cy;
}


void CPanTiltCtrl::UpdateCurrentPos()
{
	InvalidateRect(CRect(0, 0, m_nWidth, m_nHeight), TRUE);

}


void CPanTiltCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	if (m_bEnabled == FALSE) {
		return;
	}

	if (gRectCenter.PtInRect(point) == TRUE) {
		SetCapture();
		m_bDragging = TRUE;

		m_nDeltaX = point.x - m_nCenterX;
		m_nDeltaY = point.y - m_nCenterY;

		TRACE(_T("Down :: CenterIn \n"));
	}

	CWnd::OnLButtonDown(nFlags, point);
}


void CPanTiltCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
	if (m_bDragging == FALSE) {
		CWnd::OnMouseMove(nFlags, point);
		return;
	}

	KillTimer(TMR_TILT_ACTION);
	KillTimer(TMR_PAN_ACTION);
	
	if (m_nTiltActionType != ACTB_TILT_NONE) {		
		NotifyEvent(ACTB_TILT_STOP);
	}
	
	if (m_nPanActionType != ACTB_PAN_NONE) {		
		NotifyEvent(ACTB_PAN_STOP);
	}
	
	m_nPosX = 0;
	m_nPosY = 0;
	
	m_nTiltActionType = ACTB_TILT_NONE;
	m_nTiltStrength = 0;

	m_nPanActionType = ACTB_PAN_NONE;
	m_nPanStrength = 0;

	m_nPrevTiltActionType = 0;
	m_nPrevTiltStrength = 0;

	m_nPrevPanActionType = 0;
	m_nPrevPanStrength = 0;

	m_nDeltaX = 0;
	m_nDeltaY = 0;

	m_bDragging = FALSE;

	UpdateCurrentPos();

	ReleaseCapture();
	

	CWnd::OnLButtonUp(nFlags, point);
}


void CPanTiltCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
	if (m_bDragging == FALSE) {
		CWnd::OnMouseMove(nFlags, point);
		return;
	}

	if (point.x < 0 || point.x > m_nWidth || point.y < 0 || point.y > m_nHeight) {
		CWnd::OnMouseMove(nFlags, point);
		return;
	}

	point.x -= m_nDeltaX;
	point.y -= m_nDeltaY;

	const int RADIUS = 90;
	const int STOP_AREA_WIDTH = 8;

	double ptDistance = GetDistancePoints(&CPoint(m_nCenterX, m_nCenterY), &point);

	if (ptDistance > RADIUS) {
		// 포인팅 지점의 각도 계산		
		int relX, relY;
		relX = point.x - m_nCenterX;
		relY = point.y - m_nCenterY;

		double ptDeg = atan2(relX, relY) * 180 / PI;

		//TRACE(_T("Degree x=%d, y=%d, %.1f \n"), relX, relY, ptDeg);

		// 해당 각도로 원점으로 부터 반경 거리에 위치한 좌표 계산
		double radian = ptDeg * PI / 180.0f;
		double dx = sin(radian) * RADIUS;
		double dy = cos(radian) * RADIUS;

		m_nPosX = (int)dx;
		m_nPosY = (int)dy;		
	}
	else {
		m_nPosY = point.y - m_nCenterY;
		m_nPosX = point.x - m_nCenterX;
	}

	

	// TILT ACTION 판별
	if (abs(m_nPosY) < STOP_AREA_WIDTH) {
		if (m_nTiltActionType != ACTB_TILT_NONE) {
			m_nTiltActionType = ACTB_TILT_STOP;
			m_nTiltStrength = 0;
			
			SetTimer(TMR_TILT_ACTION, RESPONSE_SENSIT, NULL);
		}
	}
	else {
		m_nTiltStrength = ((abs(m_nPosY) - STOP_AREA_WIDTH) * 10 / RADIUS) + 1;
		m_nTiltStrength = min(10, m_nTiltStrength);

		if (m_nPosY < 0) {			
			m_nTiltActionType = ACTB_TILT_UP;			
		}
		else if (m_nPosY > 0) {			
			m_nTiltActionType = ACTB_TILT_DOWN;
		}

		SetTimer(TMR_TILT_ACTION, RESPONSE_SENSIT, NULL);
	}

	   

	// PAN ACTION 판별	
	if (abs(m_nPosX) < STOP_AREA_WIDTH) {
		if (m_nPanActionType != ACTB_PAN_NONE) {
			m_nPanActionType = ACTB_PAN_STOP;
			m_nPanStrength = 0;
			
			SetTimer(TMR_PAN_ACTION, RESPONSE_SENSIT, NULL);
		}
	}
	else {
		m_nPanStrength = ((abs(m_nPosX) - STOP_AREA_WIDTH) * 10 / RADIUS) + 1;
		m_nPanStrength = min(10, m_nPanStrength);

		if (m_nPosX < 0) {
			m_nPanActionType = ACTB_PAN_LEFT;
		}
		else if (m_nPosX > 0) {
			m_nPanActionType = ACTB_PAN_RIGHT;
		}

		SetTimer(TMR_PAN_ACTION, RESPONSE_SENSIT, NULL);
	}
	

	UpdateCurrentPos();

	CWnd::OnMouseMove(nFlags, point);
}


void CPanTiltCtrl::OnTimer(UINT_PTR nIDEvent)
{
	KillTimer(nIDEvent);

	if (nIDEvent == TMR_TILT_ACTION) {
		NotifyEvent(m_nTiltActionType, m_nTiltStrength);
	}
	else if (nIDEvent == TMR_PAN_ACTION) {
		NotifyEvent(m_nPanActionType, m_nPanStrength);
	}


	CStatic::OnTimer(nIDEvent);
}


void CPanTiltCtrl::NotifyEvent(int actType, int strength)
{
	// TILT_ACTION 일 경우 중복 이벤트 방지
	if ((int)(actType / ACTB_TILT_NONE) == 1) {
		if (m_nPrevTiltActionType == actType && m_nPrevTiltStrength == strength) {
			return;
		}

		m_nPrevTiltActionType = actType;
		m_nPrevTiltStrength = strength;
	}

	// PAN_ACTION 일 경우 중복 이벤트 방지
	if ((int)(actType / ACTB_PAN_NONE) == 1) {
		if (m_nPrevPanActionType == actType && m_nPrevPanStrength == strength) {
			return;
		}

		m_nPrevPanActionType = actType;
		m_nPrevPanStrength = strength;
	}

	
	if (m_pParent != NULL) {
		m_pParent->PostMessageW(MSG_PTZ_CTRL_B, actType, strength);
	}


#ifdef _DEBUG	
	if (actType == ACTB_TILT_UP) {
		TRACE(_T("ActionType=%d(TILT_UP) Strength=%d\n"), actType, strength);
	}
	else if (actType == ACTB_TILT_DOWN) {
		TRACE(_T("ActionType=%d(TILT_DOWN) Strength=%d\n"), actType, strength);
	}
	else if (actType == ACTB_TILT_STOP) {
		TRACE(_T("ActionType=%d(TILT_STOP) \n"), actType);
	}
	else if (actType == ACTB_PAN_LEFT) {
		TRACE(_T("ActionType=%d(PAN_LEFT) Strength=%d\n"), actType, strength);
	}
	else if (actType == ACTB_PAN_RIGHT) {
		TRACE(_T("ActionType=%d(PAN_RIGHT) Strength=%d\n"), actType, strength);
	}
	else if (actType == ACTB_PAN_STOP) {
		TRACE(_T("ActionType=%d(PAN_STOP) \n"), actType);
	}
#endif
}


void CPanTiltCtrl::SetBackgroundColor(COLORREF clr)
{
	m_clBgColor = clr;
	Invalidate();
}


CString CPanTiltCtrl::ActionToString(UINT act)
{
	if (act == ACTB_TILT_UP) {
		return _T("TILT_UP");
	}
	else if (act == ACTB_TILT_DOWN) {
		return _T("TILT_DOWN");
	}
	else if (act == ACTB_PAN_LEFT) {
		return _T("PAN_LEFT");
	}
	else if (act == ACTB_PAN_RIGHT) {
		return _T("PAN_RIGHT");
	}
	else if (act == ACTB_TILT_STOP) {
		return _T("TILT_STOP");
	}
	else if (act == ACTB_PAN_STOP) {
		return _T("PAN_STOP");
	}

	return _T("");
}



double CPanTiltCtrl::GetDistancePoints(POINT *p1, POINT *p2)
{
	double distance;

	distance = sqrt(pow(p1->x - p2->x, 2) + pow(p1->y - p2->y, 2));

	//TRACE(_T("Distance point = %.3f\n"), distance);

	return distance;
}
Comments