const { statusCodes } = require("..");
const PhSim = require("..");
/**
* @file File for dealing with wFunctions.
* @module widgets/wFunction.js
* @author Mjduniverse
*
*/
/**
* A widget function is a function that used for the `WidgetFunction` widget.
* The "this" keyword in the body of function usually refers to the current instance of
* PhSim simulation or references an instance of {@link PhSim.DynObject}.
*
* To learn more, see the {@tutorial Widget Functions}
*
* @module wFunction
* @typedef {Function} WFunction
* @property {Function} _options - Options used to create WFunction
* @property {Function|Number} _ref
* @property {String} [_name] - WFunction name
* @property {Function} _bodyFunction - Body Function
* @property {String} _eventclass - Event class
*
*/
/**
* Array of widget functions
* @memberof PhSim
* @type {WFunctions[]}
*/
PhSim.prototype.wFunctions = [];
/**
* Create a widget function and push it to the wFunctions array.
* @function
* @memberof PhSim
* @param {String|Function} arg - content of function if string, function if function
* @param {Object} thisRef -
* @returns {WFunction}
*/
// Simple Event Reference Array
PhSim.prototype.wFunctionRefs = [];
/**
*
* @typedef {"key"} keyTriggerString
*
* The "key" trigger means that the simple event will execute if a key is pressed.
*/
/**
*
* @typedef {"sensor"|"sensor_global"} sensorTriggerString
*
* The "sensor" trigger means that the simple event will execute if the trigger object
* collides with an object that shares at least one of the sensor classes. However,
* the "sensor_global" trigger means that the function will execute if any two
* objects that share at least one sensor class collides.
*/
/**
*
* @typedef {"objclick"|"objclick_global"} objclickTriggerString
*
* The "objclick" trigger means that the simple event will execute if the mouse clicks on the trigger object.
* However, the "objclick_global" trigger means that the simple event will execute if the mouse clicks on any
* object in general.
*/
/**
* @typedef {"objMouseDown"|"objmousedown_global"} objMouseDownTriggerString
*
* The "objmousedown" trigger means that the simple event call is executed if the mouse
* is being pushed down on the object. The "objmousedown_global" trigger means that
* the simple event will execute if the mouse clicks on any object in general.
*/
/**
* @typedef {"firstslupdate"} firstslupdateTriggerString
*
* The "firstslupdate" trigger means that the simple event will execute during the first
* update of the simulation.
*/
/**
* @typedef {"objmouseup"|"objmouseup_global"} objmouseupTriggerString
*
* The "objmouseup" trigger means that the simple event will execute when the
* mouse is let go of while the mouse is over an object. The "objmouseup_global" trigger
* means that the simple event will execute if the mouse is let go of while it is
* over an object.
*/
/**
* @typedef {"objlink"} objlinkTriggerString
*
* The "objlink" trigger means that the simple event will execute if the trigger object
* is linked to another object by the objlink widget.
*/
/**
* @typedef {"afterslchange"} afterslchangeTriggerString
*
* The "afterslchange" trigger means that the simple event will execute after the
* superlayer changes.
*
*/
/**
* @typedef {"time"} timeTriggerString
*
* The "time" trigger means that the simple event will execute by some interval of time.
*/
/**
* @typedef {keyTriggerString|sensorTriggerString|objclickTriggerString|
* objMouseDownTriggerString|firstslupdateTriggerString|objmouseupTriggerString|
* objlinkTriggerString|afterslchangeTriggerString|timeTriggerString} wFunctionTrigger
*
*
* The simple event trigger string is a string defining {@link WFunctionOptions.trigger}
*/
/**
* Properties for a simple event.
* The simple event options is an Object that is used for the {@link PhSim#createWFunction} function.
* They are also used to configure various types of widgets. Especially widgets that utilize
* wFunctions.
*
* @typedef {Object} WFunctionOptions
* @property {KeyBoard} [key] - The event.key value for triggering a simple event.
* @property {Number} [time] - The time interval between a repeated event or a delay time for timeouts.
* @property {Number} [maxN] - The maximum number of times a repeated SimpleEvent can be executed.
* @property {PhSim.DynObject} [wFunctionObj] - An object being affected by the wFunction.
* @property {String} [name] - The name of the wFunction
*
*/
/**
* @callback WFunctionBody
* @this {PhSim.DynObject}
* @param {Event} e - event object
*/
/**
* Function used to generate {@link WFunction|WFunctions.}
* To learn more, see the {@tutorial Widget Functions} tutorial.
*
* @function
* @memberof PhSim
*
* @param {wFunctionTrigger} trigger - The type of SimpleEvent.
*
* @param {WFunctionBody|Number} wFunctionBody - The JavaScript function to be wrapped.
* If `wFunctionBody` is an integer `i`, the function body is deterimined by the
* `{@link PhSim#options.wFunctions}[i]`
*
* @param {WFunctionOptions} options - [The Simple Event Options Object]{@link WFunctionOptions}.
* @returns {WFunction} - The wFunction.
* @this {PhSim}
*
*/
PhSim.prototype.createWFunction = function(thisRef,wFunctionBody,options) {
var self = this;
if(typeof wFunctionBody === "number") {
wFunctionBody = this.wFunctions[wFunctionBody];
}
if(typeof wFunctionBody === "string") {
wFunctionBody = new Function("e",options.function)
}
/**
*
* New WFunction
*
* @inner
* @type {WFunction}
*/
var wFunction = function(event) {
try {
return wFunctionBody.apply(thisRef,event);
}
catch(e) {
self.callEventClass("wfunctionerror",self,e);
console.error(e);
}
}
wFunction._options = options;
wFunction._bodyFunction = wFunctionBody;
wFunction._thisRef = thisRef;
if(options._name) {
self.wFunctionNames[options._name] = wFunction;
}
if(options.trigger === "key") {
if(options.key) {
wFunction._ref = function(e) {
if( e.key.match( new RegExp("^" + options.key + "$","i") ) ) {
wFunction(e);
}
};
}
else {
wFunction._ref = function(e) {
wFunction(e);
}
}
wFunction._eventclass = "keydown";
}
else if(options.trigger === "sensor" || options.trigger === "sensor_global") {
if(options.trigger === "sensor") {
wFunction._ref = function(e) {
var m = self.inSensorCollision(thisRef)
if(m) {
wFunction(e);
}
}
}
else {
wFunction._ref = function(e) {
wFunction(e);
}
}
wFunction._eventclass = "collisionstart";
}
else if(options.trigger === "update") {
wFunction._ref = function() {
wFunction();
}
wFunction._eventclass = "beforeupdate";
}
else if(options.trigger === "objclick" || options.trigger === "objclick_global") {
var f;
if(options.trigger === "objclick") {
wFunction._ref = function(e) {
if(self.objMouseArr[self.objMouseArr.length - 1] === thisRef) {
wFunction(e);
}
};
}
else {
wFunction._ref = function(e) {
wFunction(e);
}
}
wFunction._eventclass = "objclick";
}
else if(options.trigger === "objmousedown" || options.trigger === "objmousedown_global") {
if(options.trigger === "objmousedown") {
wFunction._ref = function(e) {
if(self.objMouseArr[self.objMouseArr.length - 1] === thisRef) {
wFunction(e);
}
}
}
else {
wFunction._ref = function(e) {
wFunction(e);
}
}
wFunction._eventclass = "objmousedown";
}
else if(options.trigger === "firstslupdate") {
wFunction._ref = function(e) {
wFunction(e)
}
wFunction._eventclass = "firstslupdate";
}
else if(options.trigger === "objmouseup" || options.trigger === "objmouseup_global") {
if(options.trigger === "objmouseup") {
wFunction._ref = function(e) {
if(self.objMouseArr[self.objMouseArr.length - 1] === thisRef) {
wFunction(e);
}
};
}
else {
wFunction._ref = function(e) {
wFunction(e);
}
}
wFunction._eventclass = "objmouseup";
}
else if(options.trigger === "objlink") {
thisRef.objLinkFunctions = thisRef.objLinkFunctions || [];
thisRef.objLinkFunctions.push(wFunction);
wFunction._ref = f;
return wFunction;
}
else if(options.trigger === "afterslchange") {
wFunction._ref = function(e) {
wFunction(e);
}
wFunction._eventclass = "afterslchange";
}
else if(options.trigger === "time") {
var getFunction = function() {
var fn;
if(Number.isInteger(options.maxN)) {
fn = function() {
if(fn.__n === options.maxN) {
clearInterval(fn.__interN);
}
else {
if(!self.paused) {
wFunction();
fn.__n++;
}
}
}
fn.__n = 0;
}
else {
fn = function() {
if(!self.paused) {
wFunction();
}
}
}
fn.__phtime = options.time;
fn.__interN = null;
return fn;
}
var refFunc = getFunction();
// Making sure that the interval starts after the simulation has has it's first
// Update and not run at the moment the wFunction is created.
if(this.status === statusCodes.LOADED_SIMULATION) {
refFunc.__interN = setInterval(refFunc,refFunc.__phtime);
}
else if(this.status < statusCodes.LOADED_SIMULATION) {
self.on("firstslupdate",function(){
refFunc.__interN = setInterval(refFunc,refFunc.__phtime);
});
}
wFunction._ref = refFunc.__interN;
return wFunction;
}
else {
wFunction._ref = wFunction;
wFunction._eventclass = options.trigger;
}
if(typeof wFunction._ref === "function") {
wFunction._ref._wFunction = wFunction;
}
if(typeof wFunction._eventclass === "string") {
wFunction._listener = wFunction._ref;
self.on(wFunction._eventclass,wFunction._ref,{
"slEvent": true
});
}
// Return wFunction
return wFunction
}
/**
*
* Disable wFunction
*
* @memberof PhSim
* @function
* @param {WFunction} o - Reference created by {@link PhSim#createWFunction}.
*
*/
PhSim.prototype.disableWFunction = function(o) {
if(typeof o._eventclass === "string") {
this.off(o._eventclass,o._ref);
}
else if(o._options.trigger === "time") {
clearInterval(o._ref)
}
else if(o._options.trigger === "objlink") {
var i = o._thisRef.objLinkFunctions.indexOf(o)
o._thisRef.objLinkFunctions.splice(i,1);
}
if(o._name) {
delete this.wFunctionNames[o._name];
}
}
/**
*
* The `wFunction` widget is used to create wFunctions.
*
* @memberof PhSim
* @function
* @param {PhSim.DynObject|PhSim} o - Target object or simulation
* @param {WFunctionOptions} widget - wFunction options
*/
PhSim.Widgets.wFunction = function(o,widget) {
this.createWFunction(o,widget.function,widget);
}
PhSim.prototype.wFunctionNames = {}