Source: lib/sys/input.js

import * as ECS from '../../packages/ecs-js/index';
import {x} from '../xapp/xworld';
import {XError} from '../xutils/xcommon';

const any = ['Input'];

export
/**Key flags.
 * @enum {int}
 * */
const KeyFlag = {
	/** ctrl key down */
	ctrl: 1 << 0,
	/** shift key down */
	shift: 1 << 1,
	/** alt key down */
	alt: 1 << 2,
}

/**
 * <h4>User inputs handler.</h4>
 * This sybsystem registers Input compoments, listening to all user inputs and
 * interpret events into UserCmds.
 *
 * <h5>1. Commands Handling Process</h5>
 * This class is always updated by x-visual frame. Each time updated, the buffering
 * UI events will be converted to commands and saved in xview's command queue.
 * The next time updating, the command queue will be cleared.
 *
 * In x-visual 0.2, the xview is changed to a global accessable singleton.
 * The command queue must not been modified other than by Input.
 *
 * Xview is also exported as xworld property, xv.xworld.xview.
 *
 * <h5>2. About Gpu Picking</h5>
 * If there is a GpuPickable component in an entity, his class also put mouse
 * position into it.
 *
 * If GPU picking is not needed, user should provide x parameter of constructor
 * a dummy mouse event handler. (TODO test)
 *
 * @class Input
 */
export default class Input extends ECS.System {
	/**
	 * Create Input handler of 'xview'.
	 * @param {ECS} ecs
	 * @param {object} x xworld.x
	 * @constructor Input
	 */
	constructor(ecs, x, view) {
		super(ecs);
		var wind = x.window;
		// this.xview = ecs.getEntity('xview')
		this.xview = view;
		view.Input = this;
		if (typeof this.xview !== 'object') {
			console.error('Sys.Input need an xview to work!')
			return
		}

		var qcmd = []
		var interpretor = typeof x.interpretor === 'function'?
						x.inerpretor : this.interpret;

		if (wind && wind.addEventListener) {
			var me = this;
			wind.addEventListener('keydown', (e) => {
				me.dirty = true;
				var cmd = interpretor('key', e);
				qcmd.push(cmd);
			});

			var mousehandler = (e) => {
				me.dirty = true;
				if (x.picker) {
					// shouldn't reach here befor v1.0
					var cmd = x.picker('mouse', e)
				}
				else {
					var cmd = interpretor('mouse', e);
				}
				if (cmd)
					qcmd.push(cmd);
			};
			wind.addEventListener('mousemove', mousehandler);
			wind.addEventListener('mouseup', mousehandler);
		}
		else {
			if (x.log >= 5)
			console.warn('[5] Sys.Input: window object is not correct, testing?');
		}

		this.qcmd = qcmd;
		this.dirty = false; // waiting inputs
	}

	update(tick, entities) {
		var v = this.xview; // for shorter expression
		var cmds = this.xview.cmds;
		if (cmds.length > 0 && v.tick < tick) {
			cmds.splice(0, cmds.length);
			v.tick = tick;
			v.flag = 0;
		}

		if (!this.dirty) return;

		if (this.qcmd.length > 0) {
			v.keflag = 0;
			this.qcmd.forEach(function(cmd, cx) {
				cmds.push(cmd);
				v.keflag |= cmd.flag;
			});
			v.flag = cmds.length;

			this.qcmd.splice(0, this.qcmd.length);
		}
		this.dirty = false;
	}

	/** default input events interpretor.
	 * @param {string} code
	 * @param {MouseEvent} e
	 * @return {object} {code, cmd, client, e}<br>
	 * where<br>
	 * code = 'key' | 'mouse'<br>
	 * cmd = Cmd | MouseEvent.type<br>
	 * client: [x, y] only for mouse event<br>
	 * e: MouseEvent, only for mouse event<br>
	 * @function
	 */
	interpret(code, e) {
		var cmd;
		if (code === 'key') {
			cmd = {code, 'key': e.keyCode, e};
			// numbers
			if (0x30 <= e.keyCode && e.keyCode <= 0x3a )
				cmd.cmd = e.keyCode - 0x30;
			//
			else if (e.keyCode === 0x57 || e.keyCode === 0x77) // w
				cmd.cmd = 'up';
			else if (e.keyCode === 0x41 || e.keyCode === 0x61) // a
				cmd.cmd = 'left';
			else if (e.keyCode === 0x53 || e.keyCode === 0x73) // s
				cmd.cmd = 'down';
			else if (e.keyCode === 0x44 || e.keyCode === 0x64) // d
				cmd.cmd = 'right';
			else if (e.keyCode === 0x58 || e.keyCode === 0x78) // x
				cmd.cmd = 'back';
			else if (e.keyCode === 0x43 || e.keyCode === 0x63) // c
				cmd.cmd = 'forth';
			else if (e.keyCode === 0x20)    // space
				cmd.cmd = 'fire';
			else if (e.keyCode === 0x09)    // tab
				cmd.cmd = 'tab';
			else if (e.keyCode === 20)      // CapsLock
				cmd.cmd = 'cap';
			else if (e.keyCode === 27)      // esc
				cmd.cmd = 'esc';
			else if (e.keyCode === 93)      // context menu
				cmd.cmd = 'menu';
			else if (e.keyCode === 92)      // meta
				cmd.cmd = 'meta';

			cmd.keyflag = 0;
			if (e.ctrlKey)
				cmd.keyflag |= KeyFlag.ctrl;     // 1 << 0;
			if (e.shiftKey)
				cmd.keyflag |= KeyFlag.shift;    // 1 << 1;
			if (e.altKey)
				cmd.keyflag |= KeyFlag.alt;      // 1 << 2;
		}
		else if (code === 'mouse') {
			if (e.type === 'mousemove'
				&& e.offsetX === offsetxy.x && e.offsetY === offsetxy.y)
				return true;
			else {
				offsetxy.x = e.offsetX;
				offsetxy.y = e.offsetY;
				cmd = { code,
						cmd: e.type,
						client: [e.offsetX, e.offsetY],
						e };
			}
		}
		else throw new XError(`Input(): what's here for code ${code}`);

		return cmd;
	}
}

const offsetxy = {};

Input.query = { any };