Source: SpriteTextGui.js

/**
 * @module SpriteTextGui
 * @description Adds SpriteText settings folder into {@link https://github.com/anhr/dat.gui|dat.gui}.
 * @see {@link https://github.com/anhr/SpriteText|SpriteText}
 *
 * @author Andrej Hristoliubov. {@link https://anhr.github.io/AboutMe/|AboutMe}
 *
 * @copyright 2011 Data Arts Team, Google Creative Lab
 *
 * @license under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
*/

//import { dat } from '../dat/dat.module.js';//https://github.com/anhr/commonNodeJS
//import { dat } from 'https://raw.githack.com/anhr/commonNodeJS/master/dat/dat.module.js';

import { ScaleControllers } from '../ScaleController.js';//https://github.com/anhr/commonNodeJS
//import { ScaleControllers } from 'https://raw.githack.com/anhr/commonNodeJS/master/ScaleController.js';

import Cookie from '../cookieNodeJS/cookie.js';//https://github.com/anhr/commonNodeJS/tree/master/cookieNodeJS
//import Cookie from 'https://raw.githack.com/anhr/commonNodeJS/master/cookieNodeJS/cookie.js';

import three from '../three.js'
import Options from '../Options.js'

import { SpriteText } from './SpriteText.js';
//import { SpriteText } from 'https://raw.githack.com/anhr/commonNodeJS/master/SpriteText/SpriteText.js';

//Каждый экземляр SpriteTextGui должен иметь униканое cookieName.
//Иначе если пользователь сделает какое то изменеие в SpriteTextGui, то это изменеие отразится 
//в других экземлярах SpriteTextGui после перезанрузки веб страницы. В частности могут появиться ненужные органы управления
var _spriteTextGuiCount = 0;

/**
 * Adds SpriteText settings folder into dat.gui.
 * @param {THREE.Group|THREE.Scene|THREE.Sprite} group [THREE.Group]{@link https://threejs.org/docs/index.html?q=group#api/en/objects/Group} or [THREE.Scene]{@link https://threejs.org/docs/index.html?q=sce#api/en/scenes/Scene} of the <b>SpriteText</b> and of all child groups of the <b>SpriteText</b> for which these settings will have an effect.
 * Or Sprite returned from <a href="../../AxesHelper/jsdoc/index.html" target="_blank">new SpriteText(...)</a>.
 *
 * @param {Options} [options] See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
 * @param {object} [options.dat] use dat-gui JavaScript Controller Library. [dat.gui]{@link https://github.com/dataarts/dat.gui}.
 * @param {GUI} [options.dat.dat] [dat.GUI()]{@link https://github.com/dataarts/dat.gui}.
 * @param {boolean} [options.dat.cookie] false - do not save to cookie all user settings
 * @param {string} [options.dat.cookieName] Name of the cookie.
 * @param {boolean} [options.dat.guiSelectPoint] false - do not displays <b>GuiSelectPoint</b>.
 * @param {Function} [options.getLanguageCode=language code of your browser] Your custom getLanguageCode() function.
 * <pre>
 * returns the "primary language" subtag of the language version of the browser.
 * Examples: "en" - English language, "ru" Russian.
 * See the "Syntax" paragraph of RFC 4646 {@link https://tools.ietf.org/html/rfc4646#section-2.1|rfc4646 2.1 Syntax} for details.
 * You can import { getLanguageCode } from '../../commonNodeJS/master/lang.js';
 * </pre>
 * @param {object} [guiParams={}] Followed parameters is allowed. Default is no parameters
 * @param {GUI} [guiParams.folder] <b>SpriteTextGui</b> folder. See {@link https://github.com/anhr/dat.gui|dat.gui} for details
 * @param {GUI} [guiParams.parentFolder] parent folder, returned by {@link https://github.com/dataarts/dat.gui/blob/master/API.md#GUI+addFolder|gui.addFolder(name)}
 * @param {string} [guiParams.optionsSpriteText] See <b>options</b> of <a href="module-SpriteText.html" target="_blank">SpriteText</a>. Default is <b>group.userData.optionsSpriteText</b> or no options
 * @param {string} [guiParams.spriteFolder] sprite folder name. Default is lang.spriteText
 * @param {string} [guiParams.settings] See <b>options.settings</b> of <a href="../../jsdoc/ScaleController/module-ScaleController-ScaleController.html" target="_blank">ScaleControllers</a> settings
 * @returns {GUI} sprite folder
 */
export function SpriteTextGui( group, options, guiParams = {} ) {

//	guiParams = guiParams || {};
	options = new Options( options );
/*
	if ( !options.boOptions ) {

		options = new Options( options );

	}
*/
	const gui = guiParams.folder || options.dat.gui;
	if ( !gui || options.dat.spriteTextGui === false )
		return;
	const optionsSpriteText = guiParams.optionsSpriteText || group.userData.optionsSpriteText || {},
		THREE = three.THREE,
		dat = three.dat;//options.dat.dat;
//		options = guiParams.options || new Options();

	if ( Object.keys(optionsSpriteText).length === 0 ) console.warn( 'SpriteTextGui: optionsSpriteText is empty.' );
		
	if ( THREE.Color.NAMES[optionsSpriteText.fontColor] ) {

		const color = new THREE.Color( optionsSpriteText.fontColor );
		optionsSpriteText.fontColor = 'rgba(' + color.r * 255 + ',' + color.g * 255 + ',' + color.b * 255 + ',1)'

	}
	const optionsDefault = JSON.parse( JSON.stringify( optionsSpriteText ) );
	Object.freeze( optionsDefault );

	//Localization

	const lang = {

		spriteText: 'Sprite Text',
		spriteTextTitle: 'Settings for text that always faces towards the camera.',

		text: 'Text',
		textTitle: 'The text to be displayed on the sprite.',

		textHeight: 'Height',
		textHeightTitle: 'Text Height.',

		fontFace: 'Font Face',

		fontFaces: 'Font Faces',
		fontFaceTitle: 'Choose text font.',

		bold: 'Bold',

		italic: 'Italic',

		rotation: 'Rotation',
		rotationTitle: 'Sprite rotation',

		fontProperties: 'Font Properties',
		fontPropertiesTitle: 'Other font properties. The font property uses the same syntax as the CSS font property.',

		fontStyle: 'Font Style',
		fontStyleTitle: 'Text style being used when drawing text. Read only.',

		displayRect: 'Border',
		displayRectTitle: 'Display a border around the text.',
		borderColor: 'Border Color',
		backgroundColor: 'Background Color',
		borderRadius: 'Border Radius',
		borderThickness: 'Border Thickness',

		fontColor: 'Font Color',

		anchor: 'Anchor',
		anchorTitle: 'The text anchor point.',

		sizeAttenuation: 'Size Attenuation',
		sizeAttenuationTitle: 'Whether the size of the sprite is attenuated by the camera depth. (Perspective camera only.)',

		defaultButton: 'Default',
		defaultTitle: 'Restore default Sprite Text settings.',

	};
/*
	const _languageCode = guiParams.getLanguageCode === undefined ? 'en'//Default language is English
		: guiParams.getLanguageCode();
*/		
	switch ( options.getLanguageCode() ) {

		case 'ru'://Russian language

			lang.spriteText = 'Текстовый спрайт';//'Sprite Text'
			lang.spriteTextTitle = 'Настройки для текста, который всегда обращен к камере.';

			lang.text = 'Текст';
			lang.textTitle = 'Текст, который будет отображен в спрайте.';

			lang.textHeight = 'Высота';
			lang.textHeightTitle = 'Высота текста.';

			lang.fontFace = 'Имя шрифта';

			lang.fontFaces = 'Имена шрифтов';
			lang.fontFaceTitle = 'Выберите шрифта текста.';

			lang.bold = 'Жирный';

			lang.italic = 'Наклонный';

			lang.rotation = 'Вращение';
			lang.rotationTitle = 'Вращение текстового спрайта';

			lang.fontProperties = 'Дополнительно';
			lang.fontPropertiesTitle = 'Дополнительные свойства шрифта. Свойство шрифта использует тот же синтаксис, что и свойство шрифта CSS.';

			lang.fontStyle = 'Стиль шрифта';
			lang.fontStyleTitle = 'Стиль шрифта, используемый при рисовании текста. Не редактируется.';

			lang.displayRect = 'Рамка';
			lang.displayRectTitle = 'Отобразить рамку вокруг текста.';
			lang.borderColor = 'Цвет рамки';
			lang.backgroundColor = 'Цвет фона';
			lang.borderRadius = 'Зкругление углов';
			lang.borderThickness = 'Толщина рамки';

			lang.fontColor = 'Цвет шрифта';

			lang.anchor = 'Якорь';
			lang.anchorTitle = 'Точка привязки текста.';

			lang.sizeAttenuation = 'Размер';
			lang.sizeAttenuationTitle = 'Будет ли размер спрайта зависеть от расстояния до камеры. (Только перспективная камера.)';

			lang.defaultButton = 'Восстановить';
			lang.defaultTitle = 'Восстановить настройки текстового спрайта по умолчанию.';
			break;
		default://Custom language
			if ( ( guiParams.lang === undefined ) || ( guiParams.lang.languageCode != _languageCode ) )
				break;

			Object.keys( guiParams.lang ).forEach( function ( key ) {

				if ( lang[key] === undefined )
					return;
				lang[key] = guiParams.lang[key];

			} );

	}

	guiParams.spriteFolder = guiParams.spriteFolder || lang.spriteText;
/*	
	const cookieName = 'SpriteText' + ( guiParams.cookieName ? '_' + guiParams.cookieName : '' ),
		cookie = guiParams.cookie || new Cookie.defaultCookie(),
*/		
	_spriteTextGuiCount++;
	const cookieName = options.dat.getCookieName( 'SpriteText' + _spriteTextGuiCount ),
		cookie = options.dat.cookie,
		optionsGroup = optionsSpriteText.group;
	cookie.getObject( cookieName, optionsSpriteText, optionsSpriteText );
	optionsSpriteText.group = optionsGroup;
	if ( group instanceof THREE.Sprite !== true ) {

		if ( group.userData.optionsSpriteText === undefined )
			group.userData.optionsSpriteText = optionsSpriteText;
		else if ( guiParams.optionsSpriteText !== undefined ) console.warn( 'SpriteTextGui: duplicate group.userData.optionsSpriteText' );

	}

	//updateSpriteText function is repeatedly called during restore settings to default values.
	//See fSpriteText.userData.restore() function for details.
	//I have set to false before restoring and set to true again and called function once after restoring for resolving of problem.
	var boUpdateSpriteText = true;
	function updateSpriteText( noSave ) {

		if ( !boUpdateSpriteText )
			return;
		SpriteText.updateSpriteTextGroup( group );
		if ( group.userData.update )
			group.userData.update();// optionsSpriteText );

		if ( controllerFont !== undefined )
			controllerFont.setValue( optionsSpriteText.font );

		if ( !noSave )
			cookie.setObject( cookieName, optionsSpriteText );

	}

	if ( !guiParams.hasOwnProperty( 'parentFolder' ) )
		guiParams.parentFolder = gui;

	//Sprite folder
	const fSpriteText = guiParams.parentFolder.addFolder( guiParams.spriteFolder );
	dat.folderNameAndTitle( fSpriteText, guiParams.spriteFolder, lang.spriteTextTitle );

	//Sprite text height
	const textHeight = 'textHeight';
	if ( optionsSpriteText.hasOwnProperty( textHeight ) && ( optionsSpriteText[textHeight] !== undefined ) ) {

		ScaleControllers( fSpriteText, optionsSpriteText, textHeight, function () { updateSpriteText(); }, {

			text: lang.textHeight, textTitle: lang.textHeightTitle,
			getLanguageCode: guiParams.getLanguageCode,
			settings: guiParams.settings,

		} );

	}

	//font  face
	if ( optionsSpriteText.fontFace !== undefined ) {

		dat.controllerNameAndTitle(
			fSpriteText.add( optionsSpriteText, 'fontFace' ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.fontFace );

	}

	//font faces
	if ( optionsSpriteText.fontFaces !== undefined ) {

		dat.controllerNameAndTitle(
			fSpriteText.add( optionsSpriteText, 'fontFace', optionsSpriteText.fontFaces ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.fontFaces, lang.fontFaceTitle );

	}

	//bold
	if ( optionsSpriteText.hasOwnProperty( 'bold' ) ) {

		dat.controllerNameAndTitle(
			fSpriteText.add( optionsSpriteText, 'bold' ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.bold );

	}

	//italic
	if ( optionsSpriteText.hasOwnProperty( 'italic' ) ) {

		dat.controllerNameAndTitle(
			fSpriteText.add( optionsSpriteText, 'italic' ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.italic );

	}

	//rotation
	const rotation = 'rotation';
	if ( optionsSpriteText.hasOwnProperty( rotation ) ) {

		var min = 0,
			max = Math.PI * 2;
		dat.controllerNameAndTitle(
			fSpriteText.add( optionsSpriteText, rotation, min, max, ( max - min ) / 360 ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.rotation, lang.rotationTitle );

	}

	//font properties
	if ( optionsSpriteText.hasOwnProperty( 'fontProperties' ) ) {

		dat.controllerNameAndTitle(
			fSpriteText.add( optionsSpriteText, 'fontProperties' ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.fontProperties, lang.fontPropertiesTitle );

	}

	//font style
	if ( optionsSpriteText.hasOwnProperty( 'font' ) ) {

		var controllerFont = fSpriteText.add( optionsSpriteText, 'font' );
		controllerFont.__input.readOnly = true;
		dat.controllerNameAndTitle( controllerFont, lang.fontStyle, lang.fontStyleTitle );

	}

	//text rectangle
	if ( optionsSpriteText.hasOwnProperty( 'rect' ) ) {

		if ( optionsSpriteText.rect.displayRect === undefined ) optionsSpriteText.rect.displayRect = false;
		dat.controllerNameAndTitle( fSpriteText.add( optionsSpriteText.rect, 'displayRect' ).onChange( function ( value ) {

			updateSpriteText();
			fRect.domElement.style.display = optionsSpriteText.rect.displayRect ? 'block' : 'none';

		} ), lang.displayRect, lang.displayRectTitle );
		const fRect = fSpriteText.addFolder( lang.displayRect );//'Border'
		fRect.domElement.style.display = optionsSpriteText.rect.displayRect ? 'block' : 'none';

		//border thickness
		const borderThickness = 'borderThickness';
		if ( optionsSpriteText.rect.hasOwnProperty( borderThickness ) ) {

			dat.controllerNameAndTitle(
				fRect.add( optionsSpriteText.rect, borderThickness, 1, optionsSpriteText.rect.borderThickness * 30, 1 ).onChange( function ( value ) {

					updateSpriteText();

				} ), lang.borderThickness );

		}

		//border сolor
		const borderColor = 'borderColor';
		if ( optionsSpriteText.rect.hasOwnProperty( borderColor ) ) {

			dat.controllerNameAndTitle( fRect.addColor( optionsSpriteText.rect, borderColor ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.borderColor );

		}

		//background color
		const backgroundColor = 'backgroundColor';
		if ( optionsSpriteText.rect.hasOwnProperty( backgroundColor ) ) {

			dat.controllerNameAndTitle( fRect.addColor( optionsSpriteText.rect, backgroundColor ).onChange( function ( value ) {

				updateSpriteText();

			} ), lang.backgroundColor );

		}

		//border radius
		const borderRadius = 'borderRadius';
		if ( optionsSpriteText.rect.hasOwnProperty( borderRadius ) ) {

			dat.controllerNameAndTitle(
				fRect.add( optionsSpriteText.rect, borderRadius, 0, 100, 1 ).onChange( function ( value ) {

					updateSpriteText();

				} ), lang.borderRadius );

		}

	}

	//font сolor
	if ( optionsSpriteText.hasOwnProperty( 'fontColor' ) ) {

		dat.controllerNameAndTitle( fSpriteText.addColor( optionsSpriteText, 'fontColor' ).onChange( function ( value ) {

			updateSpriteText();

		} ), lang.fontColor );

	}

	//anchor. https://threejs.org/docs/index.html#api/en/objects/Sprite.center
	if ( optionsSpriteText.hasOwnProperty( 'center' ) ) {

		optionsSpriteText.center = SpriteText.getCenter( optionsSpriteText.center );

		//anchor folder
		const fAnchor = fSpriteText.addFolder( 'center' );
		dat.folderNameAndTitle( fAnchor, lang.anchor, lang.anchorTitle );

		//anchor x
		fAnchor.add( optionsSpriteText.center, 'x', 0, 1, 0.1 ).onChange( function ( value ) {

			updateSpriteText();

		} );

		//anchor y
		fAnchor.add( optionsSpriteText.center, 'y', 0, 1, 0.1 ).onChange( function ( value ) {

			updateSpriteText();

		} );

	}

	//size attenuation. Whether the size of the sprite is attenuated by the camera depth. (Perspective camera only.) Default is false.
	//See https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation
	const sizeAttenuation = 'sizeAttenuation';
	if ( optionsSpriteText.hasOwnProperty( sizeAttenuation ) && ( optionsSpriteText[sizeAttenuation] !== undefined ) ) {

		dat.controllerNameAndTitle( fSpriteText.add( optionsSpriteText, sizeAttenuation ).onChange( function ( value ) {

			updateSpriteText();

		} ), lang.sizeAttenuation, lang.sizeAttenuationTitle );

	}

	//default button
	fSpriteText.userData = {

		options: options,
		restore: function ( value ) {

			boUpdateSpriteText = false;
			function setValues( folder, key, optionsDefault ) {

				folder.__controllers.forEach( function ( controller ) {

					if ( controller.property !== key ) {

						if ( typeof optionsDefault[key] !== "object" )
							return;
						Object.keys( optionsDefault[key] ).forEach( function ( optionKey ) {

							if ( controller.property !== optionKey )
								return;
							controller.setValue( optionsDefault[key][optionKey] );

						} );
						return;

					}
					controller.setValue( optionsDefault[key] );

				} );

			}

			Object.keys( optionsDefault ).forEach( function ( key ) {

				setValues( fSpriteText, key, optionsDefault );
				if ( typeof optionsDefault[key] === "object" ) {

					Object.keys( optionsDefault[key] ).forEach( function ( keyObject ) {

						Object.keys( fSpriteText.__folders ).forEach( function ( keyFolder ) {

							setValues( fSpriteText.__folders[keyFolder], keyObject, optionsDefault[key] );

						} );

					} );

				}

				Object.keys( fSpriteText.__folders ).forEach( function ( keyFolder ) {

					if ( keyFolder !== key )
						return;
					Object.keys( optionsDefault[keyFolder] ).forEach( function ( key ) {

						setValues( fSpriteText.__folders[keyFolder], key, optionsDefault[keyFolder] );

					} );

				} );

			} );

			boUpdateSpriteText = true;
			updateSpriteText();

		}

	}
	const defaultParams = { defaultF: fSpriteText.userData.restore, };
	if ( optionsDefault === undefined ) console.error( 'SpriteTextGui: optionsDefault = ' + optionsDefault );
	dat.controllerNameAndTitle( fSpriteText.add( defaultParams, 'defaultF' ), lang.defaultButton, lang.defaultTitle );

	updateSpriteText( true );

	return fSpriteText;

};