wGui - Bullseye Custom Control Tutorial

This is a tutorial that will show you how to make a simple custom control (widget) using wGui. For the purpose of this, we will be creating a bullseye control, which sends different messages depending upon where it gets hit.

The tutorial requires the SDL and FreeType 2 libraries

Project Setup - Win32 (Visual Studio .NET)


Project Setup - *nix

For a *nix system, you will need to create a makefile.

Here's a basic makefile, which will probably need to be modified for your system.


	INCLUDES = -I/usr/include/freetype2 -I../../../includes
	LIBS = -L../../../lib -lwGuid -lfreetype `sdl-config --libs`
	CPPFLAGS = -Wall -O0 -g -DDEBUG `sdl-config --cflags`

	BULLSEYE_OBJS = bullseye_tutorial.o

	.SUFFIXES:
	.SUFFIXES: .cpp .o

	.cpp.o:; g++ $(CPPFLAGS) $(INCLUDES) -c -o $@ $<

	all: BullseyeApp

	BUllseyeApp: $(BULLSEYE_OBJS)
		g++ $(CPPFLAGS) $(INCLUDES) -o BullseyeTutorial $(BULLSEYE_OBJS) $(LIBS)

	clean:
		rm -rf *~ *.o BullseyeTutorial
	

Makefile


Program Outline

The bullseye control, will be a simple picture of a bullseye which sends score messages when various areas of it are clicked.

Since our focus here is a custom control, we will be using 3 files here. One file for the custom control's header (bullseye.h), one for it's implementation (bullseye.cpp), and a final one to create a simple test application so we can see that it works (bullseye_testapp.cpp).

Almost all wGui controls derive from the wGui::CWindow class. Since this control is really just a picture with some hotspots though, we can derive from wGui::CPicture to take advantage of the work that's already done in there.


The header (bullseye.h)

Our header is going to be incredibly simple. We're making a class that publicly inherits from wGui::CPicture, and the only methods we need to provide is a constructor for our class, and an OnMouseButtonDown override. The result looks like this:


class CBullseye : public wGui::CPicture
{
public:
	CBullseye(const wGui::CRect& WindowRect, wGui::CWindow* pParent);
	virtual bool OnMouseButtonDown(wGui::CPoint Point, unsigned int Button);
};
	

bullseye.h


The implementation (bullseye.cpp)

Now we just need to implement the two methods we declared.
Our constructor is as simple as it could possibly be. We just need to pass on the appropriate parameters to the CPicture constructor.


CBullseye::CBullseye(const CRect& WindowRect, CWindow* pParent) : 
CPicture(WindowRect, pParent, "bullseye.bmp", true)
{ }
	

The implementation of OnMouseButtonDown is very straightforward. The target is a series of concentric circles, where the closer we are to the center, the more points we get. So the simplest way to figure out which area we landed in, is to figure out our distance from the center. This is done by applying Pythagorean Theorem. Once we've determined what area it's in (this isn't exact, but it's close enough), we assign a score and send a USER message to our parent with it.


bool CBullseye::OnMouseButtonDown(CPoint Point, unsigned int Button)  // virtual
{
 	bool bResult = false;
	
	if (! CWindow::OnMouseButtonDown(Point, Button) && m_bVisible && 
		GetClientRect().HitTest(Point) == CRect::RELPOS_INSIDE && Button == CMouseMessage::LEFT)
 	{
		CPoint RelativeDistance = Point - m_WindowRect.Center();
		int iDistance = sqrt(RelativeDistance.XPos() * RelativeDistance.XPos() + RelativeDistance.YPos() * RelativeDistance.YPos());
		int iScore = 0;
		if (iDistance < 24)
		{
			iScore = 50;  // 50 points for the center blue circle
		}
		else if (iDistance < 46)
		{
			iScore = 25;  // 25 points for the white ring
		}
		// 0 points for the red area
		CMessageServer::Instance().QueueMessage(new TIntMessage(CMessage::USER, m_pParentWindow, this, iScore));
		bResult = true;
 	}

	return bResult;
}
	

bullseye.cpp


The test application (bullseye_testapp.cpp)

This is actually our largest file, but it doesn't do anything out of the ordinary. See the Calc tutorial for details on making a wGui app.

The Application class and the Main function are as basic as can be, so we won't even bother covering them here. The View class is extremely simple as well. All we're going to do is create a view with a bullseye control and a read-only CEditBox control that we'll use to show the points, then register the view to receive USER messages. Finally we create our message handler, which takes the USER message, gets the score value from it, and puts it in the Edit Box.


class CBullseyeTestView : public CView
{
public:
	CBullseyeTestView(void);
	bool HandleMessage(CMessage* pMessage);

private:
	CBullseye* m_pBullseye;
	CEditBox* m_pScoreBox;
};


CBullseyeTestView::CBullseyeTestView(void) :
	CView(CRect(0, 0, 120, 150), "wGui Bullseye Custom Control Tutorial", false)
{
	CMessageServer::Instance().RegisterMessageClient(this, CMessage::USER);
	m_pBullseye = new CBullseye(CRect(10, 10, 110, 110), this);
	m_pScoreBox = new CEditBox(CRect(10, 120, 110, 140), this);
	m_pScoreBox->SetReadOnly(true);
	m_pScoreBox->SetWindowText("Ready");
}


bool CBullseyeTestView::HandleMessage(CMessage* pMessage)
{
	bool bHandled = false;

	if (pMessage)
	{
		switch (pMessage->MessageType())
		{
		case CMessage::USER:
			if (pMessage->Destination() == this && pMessage->Source() == m_pBullseye)
			{
				TIntMessage* pIntMessage = dynamic_cast(pMessage);
				m_pScoreBox->SetWindowText(stdex::itoa(pIntMessage->Value()) + " Points");
				bHandled = true;
			}
			break;
		default:
			bHandled = CView::HandleMessage(pMessage);
			break;
		}
	}

	return bHandled;
}
	

bullseye_testapp.cpp