// PstarEngine version 0.21.3. version Javascript // 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"); } } 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(); //} }