Easy UI with commands in Mach5

Now that we have seen what the Command pattern is, let's look at how it is used in the Mach5 Engine. You will be surprised that there isn't much code here. That is because using the Command pattern is easy once you understand the code behind it. In this section, we will look at both the component responsible for the mouse click and the commands that are used within the engine.

Let's have a look at the M5Command class:

class M5Command 
{
public:
virtual ~M5Command(void) {}//Empty Virtual Destructor
virtual void Execute(void) = 0;
virtual M5Command* Clone(void) const = 0;
};

Here is the M5Command class used in the Mach5 Engine. As you can see, it looks almost identical to the Command class we used in the example. The only difference is that since we plan on using this within a component, it needs to have a virtual constructor. That way we can make a copy of it without knowing the true type.

The code for the UIButtonComponent class is as follows:

class UIButtonComponent: public M5Component 
{
public:
UIButtonComponent(void);
~UIButtonComponent(void);
virtual void Update(float dt);
virtual UIButtonComponent* Clone(void) const;
void SetOnClick(M5Command* pCommand);
private:
M5Command* m_pOnClick;
};

As you can see, our UI button is a component. This means that any game object has the potential to be clicked. However, this class is specifically designed to work with objects that are in screen space, which is how the operating system gives us the mouse coordinates. The rest of the code here looks like you might expect. As part of the UIButtonComponent class, we have a private M5Command. Although this class is simple, it will be worth it for us to go through and see what each method does:

UI Button Component::UI Button Component(void) : 
M5Component(CT_UIButtonComponent), m_pOnClick(nullptr)
{
}

The constructor is simple (as are most component constructors) since they are designed to be created via a factory. We set the component type and make sure to set the command pointer to null so we set ourselves up for safer code later:

UIButtonComponent::~UIButtonComponent(void) 
{
delete m_pOnClick;
m_pOnClick = 0;
}

The destructor is where that null pointer comes in handy. It is perfectly legal to delete a null pointer, so we know that this code will work, even if this component never receives a command:

void UIButtonComponent::Update(float) 
{
if (M5Input::IsTriggered(M5_MOUSE_LEFT))
{
M5Vec2 clickPoint;
M5Input::GetMouse(clickPoint);
if (M5Intersect::PointRect(clickPoint, m_pObj->pos,
m_pObj->scale.x, m_pObj->scale.y))
{
M5DEBUG_ASSERT(m_pOnClick != 0,
"The UIButton command is null"):
m_pOnClick->Execute();
}
}
}

The Update function is where we perform the test to see if the mouse click intersects the rectangle created by the object. As we mentioned before, this class could work with all objects, but to simplify the code we decided we would only use this class for screen space items. The code that is important in this decision is the GetMouse function. This function always returns coordinates in screen space. It would be possible to check if the object was in screen space or world space and convert the coordinates using the M5Gfx method ConvertScreenToWorld.

That null pointer comes in handy here as well. Since we know that the command pointer is valid or null, we can do a debug assert to test our code before we execute it:

UIButtonComponent* UIButtonComponent::Clone(void) const 
{
UIButtonComponent* pClone = new UIButtonComponent();
pClone->m_pObj = m_pObj;

if(pClone->m_pOnClick != nullptr)
pClone->m_pOnClick = m_pOnClick->Clone();

return pClone;
}

The Clone method looks like you might expect after reading Chapter 6, Creating Objects with the Prototype Pattern. This is one situation where we always need to test for null before using the command. We can't clone a null command and it is completely valid to clone this component, whether the command has been set or not:

void UIButtonComponent::SetOnClick(M5Command* pCommand) 
{
//Make sure to delete the old one
delete m_pOnClick;
m_pOnClick = pCommand;
}

The SetOnClick method allows us to set and reset the command that is associated with this component. Again, we don't need to test our command before deleting. We also don't need to test if the method parameter is non-null, because a null value is perfectly acceptable.

Even though we haven't done it for this class, this class could easily be expanded to include an OnMouseOver event that gets triggered when the mouse is inside the object rectangle but the mouse isn't clicked. A feature like this could have lots of uses for both UI and world objects. Implementing it would be as easy as swapping the two conditional statements in the Update function:

void UIButtonComponent::Update(float) 
{
M5Vec2 clickPoint;
M5Input::GetMouse(clickPoint);
if (M5Intersect::PointRect(clickPoint, m_pObj->pos,
m_pObj->scale.x, m_pObj->scale.y))
{
if (M5Input::IsTriggered(M5_MOUSE_LEFT))
{
//Do onClick Command
}
else
{
//Do onMouseOver Command
}
}
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.54.136