[Cocos2d-x] マウスオーバー時にボタンを変化させる

## Cocos2d-xでマウスオーバー時にボタンを変化させたい
Cocos2d-x(3.4) では、`Menu`クラスを使うことで簡単にボタンを生成することができる。

```cpp
auto button = MenuItemImage::create(
    "button_normal.png",
    "button_pressed.png",
    "button_disabled.png",
    [](Ref* ref){
        // do anything if clicking
});
auto menuButton = Menu::create(button, NULL);
auto winSize = Director::getInstance()->getWinSize();
menuButton->setPosition(Vec2(winSize.width / 2.0, winSize.height / 2.0));
this->addChild(menuButton);
```
スマホ向けアプリを開発する際には便利な機能ではあるが、マウスを使用するPC向けのアプリには若干機能が不足しているように思う。
MenuItemImage::createの引数は、スマホ対象の場合、
「ボタンに触れていない場合の画像」「ボタンをタップしている最中の画像」「ボタン非活性時の画像」「ボタンから手を離した際の処理」であるが、
PC対象の場合は、
「マウスボタンを押していない場合の画像」「ボタンを左クリックしている最中の画像」「ボタン非活性時の画像」「マウス左ボタンを離した際の処理」となる。

つまり、マウスオーバー時の処理を指定することができない。
これはPC向けのゲームを作成するにあたっては致命的となる。

## マウスオーバー時処理の実装
（私の場合はボタンを非活性にすることはないと思ったので）非活性時の画像をマウスオーバー時の画像に充てることにした。



2015.03.29 ボタンを離した際の処理に不備があったので修正

```cpp:MouseOverMenuItem.h
//  MouseOverMenuItem.h
//  MouseOver
//
//  Created by Baris Atamer on 3/15/15.
//  Modified by reah_at
#ifndef __MouseOver__MouseOverMenuItem__
#define __MouseOver__MouseOverMenuItem__

#include "cocos2d.h"

USING_NS_CC;

class MouseOverMenuItem : public MenuItemImage
{
public:

	~MouseOverMenuItem();

	static MouseOverMenuItem * create(const std::string &normalImage, const std::string &pressedImage, const std::string &focusImage);
	void setCursorInCallback(const std::function<void(Ref*)> &callback);
	void setButtonPressedCallback(const std::function<void(Ref*)> &callback);
	void setButtonReleasedCallback(const std::function<void(Ref*)> &callback);

protected:
	EventListenerMouse* mouseListener;

private:
	void onMouseMove(Event *event);
	void onMouseDown(Event *event);
	void onMouseUp(Event *event);
	void setMouseListener();
	bool isMousePressing = false;
	// カーソルがボタンに乗った際のコールバック
	std::function<void(Ref*)> cursor_in_callback = nullptr;
	// カーソルがボタンに乗ったとき、初回のみコールバックを動作させる
	bool isCursorInCallbackEnable = true;
	// ボタンを押したとき（離した時ではない）のコールバック
	std::function<void(Ref*)> button_pressed_callback = nullptr;
	// ボタンを離した時のコールバック
	std::function<void(Ref*)> button_released_callback = nullptr;
};

#endif /* defined(__MouseOver__MouseOverMenuItem__) */
```

```cpp:MouseOverMenuItem.cpp
//  MouseOverMenuItem.cpp
//  MouseOver
//
//  Created by Baris Atamer on 3/15/15.
//  Modified by reah_at
#include "MouseOverMenuItem.h"

MouseOverMenuItem::~MouseOverMenuItem()
{
	_eventDispatcher->removeEventListener(mouseListener);
}

MouseOverMenuItem* MouseOverMenuItem::create(const std::string &normalImage, const std::string &pressedImage, const std::string &focusImage)
{
	MouseOverMenuItem *ret = new (std::nothrow) MouseOverMenuItem();

	if (ret && ret->initWithNormalImage(normalImage, pressedImage, focusImage, nullptr))
	{
		ret->setMouseListener();
		ret->autorelease();
		return ret;
	}
	CC_SAFE_DELETE(ret);
	return nullptr;
}

// マウスオーバー時処理
void MouseOverMenuItem::onMouseMove(Event *event)
{
	EventMouse* e = (EventMouse*)event;
	Size visibleSize = Director::getInstance()->getVisibleSize();
	Vec2 locationInNode = convertToNodeSpace(Vec2(e->getCursorX(), e->getCursorY()));

	Rect r = Rect(0, 0, getContentSize().width, getContentSize().height);

	// マウスオーバー時
	if (!isMousePressing){
		if (r.containsPoint(locationInNode)){
			selected();
			// マウスオーバー時のコールバックがセット済み、かつ一度も実行されていない場合
			if (cursor_in_callback && isCursorInCallbackEnable){
				cursor_in_callback(this);
				isCursorInCallbackEnable = false;
			}
		}
		else{
			unselected();
			isCursorInCallbackEnable = true;
		}
	}
}

// ボタン押下時処理（離した時ではない）
void MouseOverMenuItem::onMouseDown(Event* event){
	EventMouse* e = (EventMouse*)event;
	Size visibleSize = Director::getInstance()->getVisibleSize();
	Vec2 locationInNode = convertToNodeSpace(Vec2(e->getCursorX(), e->getCursorY()));

	Rect r = Rect(0, 0, getContentSize().width, getContentSize().height);

	if (r.containsPoint(locationInNode)){
		setEnabled(false);
		if (button_pressed_callback) button_pressed_callback(this);
		isMousePressing = true;
	}
	
}

// ボタンを離したとき
void MouseOverMenuItem::onMouseUp(Event* event){
	EventMouse* e = (EventMouse*)event;
	Size visibleSize = Director::getInstance()->getVisibleSize();
	Vec2 locationInNode = convertToNodeSpace(Vec2(e->getCursorX(), e->getCursorY()));

	Rect r = Rect(0, 0, getContentSize().width, getContentSize().height);

	if (r.containsPoint(locationInNode)){
	    setEnabled(true);
	    isMousePressing = false;
	    if (button_released_callback) button_released_callback(this);
	}
}


void MouseOverMenuItem::setCursorInCallback(const std::function<void(Ref*)> &callback){
	cursor_in_callback = callback;
}
void MouseOverMenuItem::setButtonPressedCallback(const std::function<void(Ref*)> &callback){
	button_pressed_callback = callback;
}
void MouseOverMenuItem::setButtonReleasedCallback(const std::function<void(Ref*)> &callback){
	button_released_callback = callback;
}

void MouseOverMenuItem::setMouseListener()
{
	mouseListener = EventListenerMouse::create();
	mouseListener->onMouseMove = CC_CALLBACK_1(MouseOverMenuItem::onMouseMove, this);
	mouseListener->onMouseDown = CC_CALLBACK_1(MouseOverMenuItem::onMouseDown, this);
	mouseListener->onMouseUp   = CC_CALLBACK_1(MouseOverMenuItem::onMouseUp, this);
	
	_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);
}
```

```cpp:使用方法
	auto button = MouseOverMenuItem::create("button_normal.png", "button_pressed.png", "button_focus.png");
	button->setCursorInCallback([](Ref* ref){
		log("cursor enter callback");
	});
	button->setButtonPressedCallback([](Ref* ref){
		log("button pressed callback");
	});
	button->setButtonReleasedCallback([](Ref* ref){
		log("button released callback");
	});

	button->setPosition(Vec2(winSize.width*.5, winSize.height*.5));

	// create menu, it's an autorelease object
	auto menu = Menu::create(button, NULL);
	menu->setPosition(Vec2::ZERO);
	this->addChild(menu, 1);
```

使用時には、画像を先にセットしておき、後からそれぞれのコールバック関数をセットする。

本来使用するべきコールバック関数格納用の変数が使われないなど、いろいろと問題はある。
そもそもPC向けを前提としているため、スマホでの動作確認はしていない。
とはいえ、この方法でかなり楽にボタンを生成することができるのではないだろうか。


最後に、stackoverflowにて方法を示してくださったBaris Atamer氏に感謝したい。
http://stackoverflow.com/questions/29047615/cocos2d-x-change-menuitemimage-on-cursor-focus