// 1. Functions
// 1.1. BugMessage
// 1.J. Javascript Only !
// 1.J.1. removeItem (in an array)
// 1.J.2. MyConsole
// 1.J.3. exit()
// 2. System
// 2.1. EventType
// 2.2. PEvent
// 2.3. ActionEvents
// 2.4. SystemEvents
// 3. Base Nodes
// 3.1. Observable
// 3.2. Observer
// 3.3. NodeType
// 3.4. Node
// 3.5. Node2D
// 3.6. PEngine
// 3.7. Responsive
// 3.8. RefSize
// 3.9. Components
// 3.9.1. ComponentType
// 3.9.2. Component
// 3.9.3. Component2D
// 3.9.4. Sprite2D
// 3.9.5. Rotation
// 3.9.6. Timer
// 3.9.7. Background
// 4. Physic Engine
// 5. GUI
// 5.1. Control
// 5.2. GUIcomponent
// 5.3. Themes
// 5.3.1. ThemeType
// 5.3.2. Theme
// 5.4. Focus
// 5.5. Languages
// 5.W. Widgets
// 5.W.x. a widget
// 5.W.x.1. the widget (extends Control)
// 5.W.x.2. GUIcomponent for FLAT
// 5.W.x.y. GUIcomponent for ...
// 5.W.1. Slider
// 5.W.1.1. Slider class
// 5.W.1.2. Slider GUIcomponent for FLAT
// 6. special
// 6.1. Preload
// 1. Functions
// 1.1. BugMessage
function bugMessage(message) {
// TODO ; popWindow with application freezed with Palert(String message)
alert(message);
}
// 1.J. Javascript Only !
// 1.J.1. removeItem (in an array)
function removeItem(arr, value) {
var index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
//return arr;
}
// 1.J.2. MyConsole
class MyConsole { // to have print() and println() in Javascript !
constructor() {
this.console=[];
this.justNewLine=true;
}
print(text) {
if (this.justNewLine) {
this.console.push(text);
this.justNewLine=false;
} else {
this.console[this.console.length-1]=this.console[this.console.length-1]+text;
this.justNewLine=false;
}
}
println(text) {
this.console.push(text);
this.justNewLine=false;
}
out() { //myConsole is dumped on the navigator console each frame by PEngine
for (let string of this.console) {
string=string+" "; // navigator console doesn't like empty strings !
print(string);
}
this.console=[];
}
}
// 1.J.3. exit()
function exit() { // for compatibility with Processing Java
remove(); // delete canvas and all P5js elements
}
// 2. System
// 2.1. EventType
const EventType = {
CLIC :
"CLIC",
CLICME:
"CLICME",
COLLISION :
"COLLISION",
FOCUS:
"FOCUS",
THEME:
"THEME"
}
// 2.2. PEvent
class PEvent {
constructor(eventType, source) {
this.eventType=eventType;
this.source=source;
}
}
// 2.3. ActionEvents
// cf Application TAB
// 2.4. SystemEvents
// singleton pengine.systemEvents
class SystemEvents { //implements Observable
constructor() {
this.listeners=[];
}
// Implements Observable
listen(node) {
this.listeners.push(node);
}
//void unListen(Node node);
notify(pEvent) {
for (let node of this.listeners) {
node.signal(pEvent);
}
}
// end Implements Observable
// mouse
clicEvent() {
this.notify(new PEvent(EventType.CLIC, this));
}
// keyboard
run() {
// In JS, key is a string in all cases. use of keyCode is ok (probably faster) but could be avoided
// N.B. DEL key doesn't work properly in JAVA version : do not use it !
if (keyIsPressed) {
print("["+key+"] <"+keyCode+">");
switch(keyCode) {
case 37 :
this.keyboard("LEFT");
break;
case 38 :
this.keyboard("UP");
break;
case 39 :
this.keyboard("RIGHT");
break;
case 40 :
this.keyboard("DOWN");
break;
case 45 :
this.keyboard("INSERT");
break;
case 36 :
this.keyboard("HOME");
break;
case 35 :
this.keyboard("END");
break;
case 17 :
this.keyboard("CTRL");
break;
case 16 :
this.keyboard("SHIFT");
break;
case 20 :
this.keyboard("MAJ");
break;
case 18 :
this.keyboard("ALT");
break;
case 27 :
this.keyboard("ESC"); // and key="Escape"
break;
case 225 :
this.keyboard("ALTGR");
break;
case 33 :
this.keyboard("PAGEUP");
break;
case 34 :
this.keyboard("PAGEDOWN");
break;
case 13 :
this.keyboard("ENTER");
break;
case 9 :
this.keyboard("TAB");
break;
case 46 :
this.keyboard("DEL");
break;
case 8 :
this.keyboard("BACKSPACE");
break;
default :
this.keyboard(key.toUpperCase());
}
}
}
keyboard(k) {
print(k);
}
} // End SystemEvents
function mouseReleased() {
pengine.systemEvents.clicEvent();
}
// 3. Base Nodes
// 3.1. Observable
//interface Observable {
// void listen(Node node);
// //void unListen(Node node);
// void notify(PEvent event);
//}
// 3.2. Observer
//interface Observer {
// void signal(PEvent event);
//}
// 3.3. NodeType
const NodeType = {
NULL:
"NULL",
PRELOAD:
"PRELOAD",
NODE :
"NODE",
NODE2D:
"NODE2",
TEST :
"TEST",
REFSIZE:
"REFSIZE",
RESPONSIVE:
"RESPONSIVE",
CONTROL:
"CONTROL",
// Widget Type :
WIDGETTEST:
"WIDGETTEST",
SLIDER:
"SLIDER"
}
// 3.4. Node
class Node { // implements Observable, Observer
// Parent is not necessary here in JS version : made if necessary in addChild()
// and if aNode.parent.nodeType==NodeType.ROOT parent isn't going to be used in the code
constructor(name) {
this.nodeType = NodeType.NODE;
this.name=name;
this.isDisabled = this.isFreezed = this.isToRemove =false;
this.isReady=false;
this.children = [];
// this.parent will be created when used !
this.isRoot=false;
// Components
this.components = [];
// implements Observable
this.listeners=[];
}
toString() {
let toReturn = this.name+"("+this.nodeType+")";
for (let component of this.components) {
toReturn+="\n COMPONENT : "+component.name+"("+component.componentType+")\n";
}
return toReturn;
}
_printTreeHelp(decal, toPrint) {
if (decal!=0) {
for (let i=0; i<decal; i++) {
pengine.myConsole.print("---->");
}
}
pengine.myConsole.print(toPrint);
pengine.myConsole.println(" ");
}
printTree() {
this._printTreeHelp(pengine.decalTree, this.toString());
pengine.decalTree+=1;
for (let child of this.children) {
child.printTree();
}
pengine.decalTree-=1;
}
// implements Observable
listen(node) {
this.listeners.push(node);
}
unListen(node) {
// TODO !
}
notify(pEvent) {
for (let node of this.listeners) {
node.signal(pEvent);
}
}
// implements Observer
signal(pEvent) {
for (let component of this.components) {
component.signal(this, pEvent);
}
}
// end implementations
addChild(child) {
this.children.push(child);
child.parent=this;
}
addComponent(component) { // D.P. Decorator
this.components.push(component);
return this;
}
_runReady() {
this.ready();
for (let child of this.children) {
child._runReady();
}
}
_runInMatrix() {
for (let child of this.children) {
if (!child.isDisabled && !child.isToRemove) {
child._runInMatrix();
}
}
}
_runOutMatrix(delta) {
this.process(delta);
for (let child of this.children) {
if (!child.isDisabled && !child.isToRemove && !this.isFreezed) {
child._runOutMatrix(delta);
}
}
}
ready() {
if (!this.isReady) {
for (let component of this.components) {
component.ready(this);
}
this.isReady=true;
}
}
process(delta) {
for (let component of this.components) {
component.process(this, delta);
}
}
toRemove() {
if (!this.isRoot) {
this.isToRemove=true;
}
}
_remove() {
if (!this.isRoot) {
if (this.isToRemove) {
removeItem(parent.children, this); // cf § 1.J.1.
}
}
}
setDisable(b) {
if (!this.isRoot) {
this.isDisabled = b;
}
}
setFreezed(b) { // b : Boolean
if (b==true || b==false) {
if (!this.isRoot) {
this.isFreezed=b;
for (let child of this.children) {
child.setFreezed(b);
}
} else {
bugMessage("setFreezed(b) : b must be boolean !/nb :"+b+" !");
}
}
}
}
// 3.5. Node2D
class Node2D extends Node {
constructor(name) {
super(name);
this.nodeType=NodeType.NODE2D;
this.position=createVector(0, 0);
this.rotation=0;
this.globalRotation=0;
this.scale=1;
this.globalScale=1;
this.globalPosition=this.position.copy();
this.size=createVector(10, 10); // globalSize = this.size.copy().mult(this.globalScale);
}
setPosition(x, y) {
this.position.set(x, y);
this.globalPosition=this.position.copy();
return this;
}
setSize(w, h) {
this.size.set(w, h);
return this;
}
topLeft() {
return createVector(-this.size.x/2, -this.size.y/2);
}
globalTopLeft() {
return this.globalPosition.copy().add(this.topLeft());
}
toString() {
//return super.toString()+", position = ("+this.position.x+","+this.position.y+") , rotation = "+this.rotation+", scale = "+this.scale;
let toReturn = this.name+"("+this.nodeType+"), position = ("+this.position.x+","+this.position.y+"), rotation = "+this.rotation+", scale = "+this.scale;
for (let component of this.components) {
toReturn+="\n COMPONENT : "+component.name+"("+component.componentType+")\n";
}
return toReturn;
}
addChild(child) {
if (child.nodeType==NodeType.NODE) {
bugMessage("Node2D.addChild("+child.name+") : children can't be Node type !");
} else {
super.addChild(child);
}
}
_runInMatrix() {
push();
translate(this.position.x, this.position.y);
rotate(this.rotation);
scale(this.scale);
this.display();
super._runInMatrix();
pop();
}
display() {
if (pengine.debug) {
push();
fill(255, 0, 0);
stroke(255, 0, 0);
circle(0, 0, 10);
textFont(font);
textSize(20);
strokeWeight(1);
text(this.name, 10, 10);
pop();
}
for (let component of this.components) {
component.display(this);
}
}
_runOutMatrix(delta) {
this.globalPosition = this.position;
this.globalRotation = this.rotation;
this.globalScale = this.scale;
if (!this.isRoot) {
if (this.parent.nodeType!=NodeType.NODE) {
this.globalRotation += this.parent.globalRotation;
this.globalScale *= this.parent.globalScale;
this.globalPosition = this.parent.globalPosition.copy().add(this.position.copy().rotate(this.parent.globalRotation).mult(this.parent.globalScale));
}
}
super._runOutMatrix(delta);
}
_parentGlobalPosition() {
if (this.parent.nodeType==NodeType.NODE) {
return createVector();
} else {
return this.parent.globalPosition;
}
}
setGlobalPosition(globalX, globalY) {
if (this.isRoot) {
this.position.set(globalX, globalY);
} else {
let globalPosition=createVector(globalX, globalY);
this.position.set(globalPosition.sub(this._parentGlobalPosition()));
}
}
setGlobalPositionX(globalX) {
this.setGlobalPosition(globalX, this.globalPosition.y);
}
setGlobalPositionY(globalY) {
this.setGlobalPosition(this.globalPosition.x, globalY);
}
}
// 3.6. PEngine
class PEngine {
constructor() {
this.root=new Node("root");
this.root.isRoot=true;
this.sceneParent=this.root;
//this.scenes=[];
this.isReady=false;
this.delta=0;
this.backgroudColor=color(0, 0, 0);
// Preload
this.preload=new Preload();
this.preloadPreviousScene=null;
// global variables
this.hasRefSize=false;
this.preload = new Preload();
//this.userLang; Voir // cd langages supra
this.decalTree=0;
this.debug=false;
this.systemEvents = new SystemEvents();
// Themes
this.theme=new Theme();
this.theme.addGUIcomponent(new SliderBASIC("SliderBASIC"));
// only for Javascript
this.myConsole=new MyConsole();
// languages
this.userLang= navigator.language; // fr, en ...
switch (this.userLang.substring(0, 2)) {
case 'fr':
this.userLang="fr";
break;
case 'en':
this.userLang="en";
break;
default:
this.userLang="en";
}
//alert(this.userLang);
// End global variables
}
setBackground(bg) {
this.backgroundColor=bg;
return this;
}
setResponsiveRefSize(hasResponsive, hasRefSize) { // D.P. Decorator
if (frameCount==0) {
if (hasResponsive) {
this.responsive = new Responsive(width/2, height/2);
this.root.addChild(this.responsive);
this.sceneParent=this.responsive;
}
if (hasRefSize) {
this.hasRefSize=true;
let rs = new RefSize(color(100, 100, 100));
this.sceneParent.addChild(rs);
this.sceneParent=rs;
}
} else {
bugMessage("pengine.setResponsiveRefSize() must not be called in draw() !");
exit();
}
return this;
}
setRefSizeColor(bgColor) { // no D.P. Decorator : to use AFTER setResponsiveRefsize()
if (this.hasRefSize) {
this.sceneParent.bgColor=bgColor;
} else {
bugMessage("PEngine.setRefSize() cannot change RefSize color : no RefSize !");
exit();
}
}
run() {
// delta computation
this.delta=deltaTime/1000;
// Reading keyboard
this.systemEvents.run();
if (this.root==undefined) {
bugMessage("PEngine : root doesn't exist !");
exit();
} else {
if (!this.isReady) {
this.ready();
this.isReady=true;
}
background(this.backgroundColor);
this.root._runOutMatrix(this.delta);
this.root._runInMatrix();
this.root._remove();
}
}
ready() {
this.preloadPreviousScene=this.getScene();
this.setScene(this.preload);
this.root._runReady();
}
printTree() {
this.myConsole.println(" ");
this.root.printTree();
this.myConsole.println(" ");
this.decalTree=0;
this.myConsole.out();
}
setScene(node2d) {
if (node2d.nodeType!=NodeType.NODE) {
this.sceneParent.children=[];
this.sceneParent.addChild(node2d);
node2d._runReady();
} else {
bugMessage("PEngine.setScene() : the scene must be a Node2D !");
exit();
}
}
getScene() {
if (this.sceneParent.children.length!=0) {
return this.sceneParent.children[0];
} else {
return null;
}
}
}
let pengine;
// 3.7. Responsive
class Responsive extends Node2D {
constructor() {
super("responsive");
this.nodeType=NodeType.RESPONSIVE;
this.startWidth=width;
this.startHeight=height;
this.position = createVector(width/2, height/2);
}
redim() {
// called when window size change :
//function windowResized() { // see P5js reference !
// pengine.responsive.redim();
//}
resizeCanvas(windowWidth, windowHeight);
this.position.set(width/2, height/2);
let percentage=width/this.startWidth;
if (this.startHeight*percentage<=height) {
this.scale=percentage;
} else {
this.scale=height/this.startHeight;
}
}
}
// 3.8. RefSize
class RefSize extends Node2D {
constructor(bgColor) {
super("RefSize");
this.nodeType=NodeType.REFSIZE;
this.bgColor=bgColor;
this.startWidth=width;
this.startHeight=height;
}
display() {
push();
rectMode(CENTER);
noStroke();
fill(this.bgColor);
rect(0, 0, this.startWidth, this.startHeight);
pop();
}
}
// 3.9. Components
// 3.9.1. ComponentType
const ComponentType = {
COMPONENT :
"COMPONENT",
COMPONENT2D:
"COMPONENT2D",
TEST :
"TEST",
PRINTTREEONCE:
"PRINTTREEONCE",
SPRITE2D:
"SPRITE2D",
ROTATION:
"ROTATION",
TIMER:
"TIMER",
GUICOMPONENT:
"SPRITGUICOMPONENTE2D"
}
// 3.9.2. Component
class Component { // almost abstract !
constructor(name) {
this.name=name;
this.componentType=ComponentType.COMPONENT;
}
ready(n) {
}
process(n, delta) {
}
signal(n, pEvent) {
}
input(n, pEvent) {
}
display(n) {
}
}
// 3.9.3. Component2D
class Component2D extends Component { // For compatibility between Java and JS code !
}
// 3.9.4. Sprite2D
class Sprite2D extends Component {
constructor(name, image) {
super(name);
this.componentType=ComponentType.SPRITE2D;
this.image=image;
}
ready(n) {
n.size.set(image.width, image.height);
}
display(n) {
push();
imageMode(CENTER);
image(this.image, 0, 0);
pop();
}
}
// 3.9.5. Rotation
class Rotation extends Component2D {
constructor(name, rotation) {
super(name);
this.componentType=ComponentType.ROTATION;
this.rotation=rotation;
}
process(n, delta) {
n.rotation+=this.rotation*delta;
}
}
// 3.9.6. Timer
class Timer extends Component2D {
constructor(name, time) {
super(name);
this.componentType=ComponentType.TIMER;
this.time=time; // seconds
this.isOn=false;
this.loop=0; // loop number (infinite if 0)
this._loopCount=0;
}
setOn() {
this.isOn=true;
if (frameCount!=0) {
this._start=millis()/1000; // seconds
this._loopCount=0;
}
return this;
}
setLoop(loop) {
this.loop=loop;
return this;
}
ready(n) {
if (this.isOn) {
this._start=millis()/1000;
}
}
process(n, delta) {
if (this.isOn && (millis()/1000) >= (this._start+this.time) ) {
n.signal(new PEvent(EventType.TIMER, n));
this._loopCount+=1;
if (this.loop==0 || this._loopCount<=this.loop) {
this._start=millis()/1000; // start Timer again
} else {
this.isOn=false;
}
}
}
//void signal(Node2D n, PEvent pEvent) {
// if (pEvent.eventType==EventType.TIMER && pEvent.source==this) {
// // DO something
// }
//}
}
// 3.9.7. Background
// TODO
class Background extends Component {
// color bColor,rsColor
// PImage bImage ... TODO !
constructor(name) {
super(name);
this.bColor=color(0, 0, 0); // black
this.rsColor=color(200, 200, 200); // gray
}
display(n) {
}
}
// 4. Physic Engine
// 5. GUI
// 5.1. Control
class Control extends Node2D {
constructor(name) {
super(name);
this.nodeType=NodeType.CONTROL;
}
ready() {
pengine.theme.listen(this);
this.themeComponent=pengine.theme.getGUIcomponent(this);
super.ready();
}
display() {
super.display();
if (this.themeComponent.themeType!=ThemeType.NULL) {
this.themeComponent.display(this);
} else {
if (pengine.debug) {
bugMessage("themeComponent.themeType is NULL in "+this.name+" ("+this.nodeType+") !");
}
}
}
signal(pEvent) {
super.signal(pEvent);
if (pEvent.eventType==EventType.THEME) {
this.themeComponent=pengine.theme.getGUIcomponent(this);
}
}
}
// 5.2. GUIcomponent
class GUIcomponent extends Component {
constructor(name) {
super(name);
this.componentType=ComponentType.GUICOMPONENT;
this.themeType=ThemeType.NULL;
this.nodeType=NodeType.NULL;
}
}
// 5.3. Themes
// 5.3.1. ThemeType
const ThemeType = {
NULL:
"NULL",
BASIC:
"BASIC",
FLAT:
"FLAT",
SF:
"SF"
}
// 5.3.2. Theme
class Theme { // implements Observable
constructor() {
this.currentTheme=ThemeType.FLAT;
// Pour pouvoir indiquer aux widgets un changement de thème :
this.widgets=[];
// La "réserve de components" :
this.components=[];
}
// implements Observable
listen(widget) {
this.widgets.push(widget);
}
notify(pEvent) {
for (let widget of this.widgets) {
widget.signal(pEvent);
}
}
// End implements Observable
addGUIcomponent(guiComponent) {
this.components.push(guiComponent);
}
getGUIcomponent(widget) {
let toReturn = new GUIcomponent();
for (let component of this.components) {
if (component.nodeType==widget.nodeType) {
if (component.themeType==this.currentTheme) {
return component;
} else {
if (component.themeType==ThemeType.BASIC) {
toReturn=component;
}
}
}
}
return toReturn;
}
setTheme(themeType) {
this.currentTheme=themeType;
this.notify(new PEvent(EventType.THEME, this));
}
} // End Theme
// 5.4. Focus
// 5.5. Languages
// 5.W. Widgets
// 5.W.1. Slider
// 5.W.1.1. Slider class
class Slider extends Control {
constructor(name) {
super(name);
this.nodeType=NodeType.SLIDER;
this.percent=0;
}
setPercent(percent) {
this.percent=percent;
return this;
}
}
// 5.W.1.2. Slider GUIcomponent for FLAT
class SliderBASIC extends GUIcomponent {
constructor(name) {
super(name);
this.name="sliderBASIC";
this.themeType=ThemeType.BASIC;
this.nodeType=NodeType.SLIDER;
}
display(node) {
push();
stroke(200);
strokeWeight(5);
noFill();
rectMode(CENTER);
rect(0, 0, node.size.x, node.size.y);
noStroke();
fill(0);
if (node.percent!=0) {
rectMode(CORNER);
rect(node.topLeft().x, node.topLeft().y, node.size.x*node.percent, node.size.y);
}
pop();
}
}
// 6. special
// 6.1. Preload
// Must be declared after Node2D class
function pLoadImage(file) {
pengine.preload.filesToLoad+=1;
return loadImage(file, pLoadFileOK, pLoadFileERROR);
}
function pLoadFont(file) {
pengine.preload.filesToLoad+=1;
return loadFont(file, pLoadFileOK, pLoadFileERROR);
}
function pLoadFileOK() {
pengine.preload.filesLoaded+=1;
}
function pLoadFileERROR(event) {
pengine.preload.filesLoaded+=1;
console.error('Error in Preload !', event);
}
let logoPengine, logoP5, hourglass;
function preload() {
logoPengine=loadImage("data/PEngine.png");
logoP5=loadImage("data/logoP5.png");
hourglass=loadImage("data/hourglass.png");
}
// singleton : pengine.preload
class Preload extends Node2D {
constructor() {
super();
this.nodeType=NodeType.PRELOAD;
this.filesToLoad=0;
this.filesLoaded=0;
this.done=false;
this.wait=false;
this.MAXTIME=1.5; // seconds
this.slider=new Slider("Preload Slider")
.setPercent(0)
.setPosition(0, 100)
.setSize(150, 30);
}
ready() {
this.addComponent(new Sprite2D("logo PEngine", logoPengine));
this.addChild(this.slider);
let star = new Node2D("Star").setPosition(50, -30)
.addComponent(new Sprite2D("logo Star", logoP5))
.addComponent(new Rotation("star rotation", 4));
star.scale=0.4;
this.addChild(star);
}
process() {
if (!this.wait) {
this.slider.percent=this.filesLoaded/this.filesToLoad;
if (this.filesToLoad==this.filesLoaded) {
this.wait=true;
// init hourglass here and hide slider
this.slider.isToRemove=true;
let hg = new Node2D("Hourglass").setPosition(0, 100)
.addComponent(new Sprite2D("Preload hourglass", hourglass))
.addComponent(new Rotation("hourglass rotation", 2));
hg.scale=0.8;
this.addChild(hg);
}
} else {
if ((millis()/1000)>this.MAXTIME) {
this.done=true;
}
}
if (this.done) {
pengine.setScene(pengine.preloadPreviousScene);
pengine.preloadPreviousScene=null;
pengine.preload=null;
}
}
//display() {
// super.display();
// push();
// textAlign(CENTER);
// text("Preload : "+this.filesLoaded+" / "+this.filesToLoad, 0, 0);
// pop();
//}
}