UI/PopupManager.js

/**
* Менеджер текста при наведении на элементы игры.
* @class
* @extends {external:Phaser.Group}
*/
UI.PopupManager = function(){
	Phaser.Group.call(this, game, null, 'popupManager');

	/**
	* Таймер задержки до показа текста.
	* @type {number}
	*/
	this.delay = null;

	/**
	* Задержка до показа текста.
	* @type {Number}
	*/
	this.delayTime = 200;

	/**
	* Отступ от краев плашки до текста.
	* @type {Number}
	*/
	this.margin = 10;

	/**
	* Отступ от курсора/элемента.
	* @type {Number}
	*/
	this.offset = Phaser.Device.desktop ? 5 : 50;

	/**
	* Показывается ли текст.
	* @type {Boolean}
	*/
	this.showing = false;

	/**
	* BitmapData фона.
	* @type {Phaser.BitmapData}
	*/
	this._bitmapData = game.make.bitmapData();

	/**
	* Плашка/фон текста.
	* @type {Phaser.Image}
	*/
	this.background = game.make.image(0, 0, this._bitmapData);
	this.add(this.background);

	/**
	* Текст.
	* @type {Phaser.Text}
	*/
	this.text = game.add.text(0, 0, '', {fill: 'black', font: '18px Exo, Helvetica', wordWrap: true, wordWrapWidth: 250, align: 'center'}, this);
	this.text.anchor.set(0.5, 0.5);
	this.text.setShadow(1, 1, 'rgba(0,0,0,0.5)', 3);

	this.visible = false;

	/**
	* Элемент, у которого есть `PopupComponent`, который вызвал показ вплывающего текста.
	* @type {any}
	*/
	this.overElement = null;

	/**
	* Элемент, над которым находится курсор.
	* @type {DisplayObject}
	*/
	this.overArea = null;

	/**
	* Функция получения текста или статичный текст.
	* @type {(function|string)}
	*/
	this.overTextGetter = null;

	/**
	* Позиция показа текста.  
	* Может быть `'left', 'right', 'top', 'bottom', 'middle'`.  
	* Отсутствие значение приведет к выводу текста над\под курсором.
	* @type {string}
	*/
	this.overPlacement = null;

	/**
	* Сигнал, отправляемый, когда над элементом с `PopupComponent` проносится курсор.
	* @type {Phaser.Signal}
	*/
	this.onHoverOver = new Phaser.Signal();

	/**
	* Сигнал, отправляемый, когда курсор уходит с элемента с `PopupComponent`.
	* @type {Phaser.Signal}
	*/
	this.onHoverOut = new Phaser.Signal();

	this.onHoverOver.add(this._hoverOver.bind(this));
	this.onHoverOut.add(this._hoverOut.bind(this));
};

extend(UI.PopupManager, Phaser.Group);

/**
* Запускает таймер до показа текста при наведении на элемент.  
* Вызывается при отправке сигнала {@link UI.PopupManager#onHoverOver|onHoverOver}.
* @param {any}               el         {@link UI.PopupComponent#overElement|overElement}
* @param {DisplayObject}     area       {@link UI.PopupComponent#overArea|overArea}
* @param {(function|string)} textGetter {@link UI.PopupComponent#overTextGetter|overTextGetter}
* @param {string}            placement  {@link UI.PopupComponent#overPlacement|overPlacement}
* @param {boolean}           now        показывает текст без задержки
*/
UI.PopupManager.prototype._hoverOver = function(el, area, textGetter, placement, now){
	if(this.overElement == el){
		return;
	}
	this.overElement = el;
	this.overArea = area;
	this.overTextGetter = textGetter;
	this.overPlacement = placement;
	this._resetDelay();
	if(now){
		this._showPopup();
	}
	else{
		this.delay = setTimeout(this._showPopup.bind(this), this.delayTime);
	}
};

/** 
* Убирает текст при наведении или отменяет запланированный показ текста.
* Вызывается при отправке сигнала {@link UI.PopupManager#onHoverOut|onHoverOut}.
* @param  {any} el {@link UI.PopupComponent#overElement|overElement}
*/
UI.PopupManager.prototype._hoverOut = function(el){
	if(!this.overElement || el && el != this.overElement){
		return;
	}
	if(this.showing){
		this.showing = false;
		this.visible = false;
	}
	this.overElement = null;
	this.overArea = null;
	this.overTextGetter = null;
	this.overPlacement = null;
	this._resetDelay();
};

/** Обновляет и показывает попап с текстом */
UI.PopupManager.prototype._showPopup = function(){
	this.showing = true;
	var text = this._getText(true);
	this._updateText(text);
	this.updatePosition();
};

/**
* Получает текст из `overTextGetter`.
* @param  {boolean} anyway передается в `overTextGetter`, сообщает, что нужно вернуть текст, даже если он не изменился
* @return {string} Возвращает текст.
*/
UI.PopupManager.prototype._getText = function(anyway){
	if(typeof this.overTextGetter == 'function'){
		return this.overTextGetter.call(this.overElement, anyway);
	}
	else{
		return this.overTextGetter;
	}
};

/** 
* Обновляет текст и фон текста.
* @param  {string} text новый текст
*/
UI.PopupManager.prototype._updateText = function(text){
	this.text.setText(text, true);
	Menu.drawPanel(
		this._bitmapData, 
		this.text.width + this.margin*2, 
		this.text.height + this.margin*2, 
		0, 
		0, 
		'grey'
	);

	this.text.x = this.background.width/2;
	this.text.y = this.background.height/2 + 3;
};

/** Обновляет позицию текста в соответствии с позицией курсора. */
UI.PopupManager.prototype.updatePosition = function(){
	if(!this.showing){
		return;
	}
	if(!ui.cursor.inGame || !this.overElement){
		this._hoverOut();
		return;
	}
	if(!this.visible){
		this.visible = true;
	}
	var text = this._getText();
	if(text){
		this._updateText(text);
	}
	var position = this._getPopupPosition();

	this.x = position.x;
	this.y = position.y;
};

/**
* Возвращает позицию всплывающего текста в зависимости от `overPlacement`.
* @return {object} `{x, y}`
*/
UI.PopupManager.prototype._getPopupPosition = function(){
	if(!this.overElement){
		return {};
	}
	var popupArea = this.overArea;
	var x, y;
	var ax = popupArea.parent.worldPosition.x + popupArea.x - popupArea.anchor.x*popupArea.width;
	var ay = popupArea.parent.worldPosition.y + popupArea.y - popupArea.anchor.y*popupArea.height;
	switch(this.overPlacement){

		case 'right':
		x = ax + popupArea.width + this.offset;
		y = ay + popupArea.height/2 - this.background.height/2;
		break;

		case 'left':
		x = ax - this.background.width - this.offset;
		y = ay + popupArea.height/2 - this.background.height/2;
		break;

		case 'top':
		x = ax + popupArea.width/2 - this.background.width/2;
		y = ay - this.background.height - this.offset;
		break;

		case 'bottom':
		x = ax + popupArea.width/2 - this.background.width/2;
		y = ay + popupArea.height + this.offset;
		break;

		case 'middle':
		x = ax + popupArea.width/2 - this.background.width/2;
		y = ay + popupArea.height/2 - this.background.height/2;
		break;

		default:
		x = Math.min(game.input.x - this.background.width/2, game.screenWidth - this.background.width);
		y = Math.min(game.input.y - this.background.height - this.offset, game.screenHeight - this.background.height);
		if(y < 0){
			y = Math.max(game.input.y + ui.cursor.height + this.offset, 0);
		}

		break;
	}
	x = Math.max(Math.min(x, game.screenWidth), 0);
	y = Math.max(Math.min(y, game.screenHeight), 0);
	return {x: x, y: y};
};

/** Вызывается игрой, обновляет позицию. */
UI.PopupManager.prototype.update = function(){
	this.updatePosition();
};

/** Отменяет запланированный показ текста. */
UI.PopupManager.prototype._resetDelay = function(){
	if(this.delay){
		clearTimeout(this.delay);
		this.delay = null;
	}
};

//@include:PopupComponent