diff --git a/support/client/lib/vwf.js b/support/client/lib/vwf.js index 36e3e758a..55b11078d 100644 --- a/support/client/lib/vwf.js +++ b/support/client/lib/vwf.js @@ -323,6 +323,9 @@ "vwf/model/blockly/msg/js/en": { deps: [ "vwf/model/blockly/blockly_compressed" ] }, + "vwf/model/hud/hud": { + exports: "HUD" + } } }; @@ -363,6 +366,7 @@ active: false }, { library: "vwf/model/graphtool", active: false }, + { library: "vwf/model/hud", active: false }, { library: "vwf/model/sound", active: false }, { library: "vwf/model/object", active: true }, { library: "vwf/model/stage/log", active: true }, @@ -400,6 +404,7 @@ active: false }, { library: "vwf/view/blockly", active: false }, + { library: "vwf/view/hud", active: false }, { library: "vwf/view/sound", active: false }, { library: "vwf/view/touch", active: false }, { library: "vwf/view/cesium", active: false }, @@ -439,6 +444,7 @@ { library: "vwf/model/cesium", active: false }, { library: "vwf/model/blockly", active: false }, { library: "vwf/model/graphtool", active: false }, + { library: "vwf/model/hud", active: false }, { library: "vwf/model/sound", active: false }, { library: "vwf/model/kineticjs", active: false }, { library: "vwf/model/mil-sym", active: false }, @@ -456,6 +462,7 @@ { library: "vwf/view/google-earth", active: false }, { library: "vwf/view/cesium", active: false }, { library: "vwf/view/blockly", active: false }, + { library: "vwf/view/hud", active: false }, { library: "vwf/view/sound", active: false }, { library: "vwf/view/touch", active: false }, { library: "vwf/view/kineticjs", active: false }, diff --git a/support/client/lib/vwf/model/hud.js b/support/client/lib/vwf/model/hud.js new file mode 100644 index 000000000..2eeab935d --- /dev/null +++ b/support/client/lib/vwf/model/hud.js @@ -0,0 +1,170 @@ +"use strict"; + +define( [ "module", "vwf/model", "vwf/utility" ], function( module, model, utility ) { + + var logger; + + return model.load( module, { + + initialize: function() { + this.state.overlays = {}; + this.state.elements = {}; + logger = this.logger; + }, + + creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs, + childSource, childType, childIndex, childName, callback /* ( ready ) */ ) { + + var node; + var protos = this.kernel.prototypes( childID ); + if ( protos && isOverlay( protos ) ) { + node = this.state.overlays[ childID ] = { + "id": childID, + "name": childName, + "elements": {}, + "properties": { + "visible": undefined, + "enabled": undefined + }, + "drawProperties" : {}, + "elementPreDraw": undefined, + "elementPostDraw": undefined, + "globalPreDraw": undefined, + "globalPostDraw": undefined, + "initialized": false + }; + } else if ( protos && isElement( protos ) ) { + node = this.state.elements[ childID ] = { + "id": childID, + "name": childName, + "overlay": this.state.overlays[ nodeID ], + "properties": { + "images": undefined, + "width": undefined, + "height": undefined, + "visible": undefined, + "enabled": undefined, + "alignH": undefined, + "alignV": undefined, + "offsetH": undefined, + "offsetV": undefined, + "drawOrder": undefined + }, + "drawProperties": {}, + "initialized": false + }; + node.overlay.elements[ childID ] = node; + } + }, + + creatingProperty: function( nodeID, propertyName, propertyValue ) { + return this.initializingProperty( nodeID, propertyName, propertyValue ); + }, + + initializingProperty: function( nodeID, propertyName, propertyValue ) { + var value; + if ( propertyValue !== undefined ) { + var node = this.state.overlays[ nodeID ] || this.state.elements[ nodeID ] ; + if ( node !== undefined ) { + switch ( propertyName ) { + default: + value = this.settingProperty( nodeID, propertyName, propertyValue ); + break; + } + } + } + return value; + }, + + settingProperty: function( nodeID, propertyName, propertyValue ) { + var node, value, images, keys, i, image; + if ( this.state.overlays[ nodeID ] ) { + node = this.state.overlays[ nodeID ]; + if ( node.properties.hasOwnProperty( propertyName ) ) { + node.properties[ propertyName ] = propertyValue; + value = propertyValue; + } else { + node.drawProperties[ propertyName ] = propertyValue; + } + } else if ( this.state.elements[ nodeID ] ) { + node = this.state.elements[ nodeID ]; + if ( node.properties.hasOwnProperty( propertyName ) ) { + if ( propertyName === "images" ) { + node.properties.images = propertyValue; + } else { + node.properties[ propertyName ] = propertyValue; + } + } else { + node.drawProperties[ propertyName ] = propertyValue; + } + value = propertyValue; + } + + return value; + }, + + gettingProperty: function( nodeID, propertyName ) { + var node, value; + if ( this.state.overlays[ nodeID ] ) { + node = this.state.overlays[ nodeID ]; + if ( node.properties.hasOwnProperty( propertyName ) ) { + value = node.properties[ propertyName ]; + } + } else if ( this.state.elements[ nodeID ] ) { + node = this.state.elements[ nodeID ]; + if ( node.properties.hasOwnProperty( propertyName ) ) { + value = node.properties[ propertyName ]; + } else if ( node.drawProperties.hasOwnProperty( propertyName ) ) { + value = node.drawProperties[ propertyName ]; + } + } + return value; + }, + + callingMethod: function( nodeID, methodName, methodParameters, methodValue ) { + var node; + if ( this.state.overlays[ nodeID ] ) { + node = this.state.overlays[ nodeID ]; + switch ( methodName ) { + case "elementPreDraw": + case "elementPostDraw": + case "globalPreDraw": + case "globalPostDraw": + this.logger.errorx( "callingMethod", "The " + methodName + " method should not " + + "be called from outside the HUD driver!" ); + return; + break; + } + } else if ( this.state.elements[ nodeID ] ) { + node = this.state.elements[ nodeID ]; + if ( methodName === "draw" ) { + this.logger.errorx( "callingMethod", "The draw method should not be called " + + "from outside the HUD driver!" ); + return; + } + } + } + + } ); + + function isOverlay( prototypes ) { + var foundOverlay = false; + if ( prototypes ) { + for ( var i = 0; i < prototypes.length && !foundOverlay; i++ ) { + foundOverlay = ( prototypes[i] == "http://vwf.example.com/hud/overlay.vwf" ); + } + } + return foundOverlay; + } + + function isElement( prototypes ) { + var foundElement = false; + if ( prototypes ) { + for ( var i = 0; i < prototypes.length && !foundElement; i++ ) { + foundElement = ( prototypes[i] == "http://vwf.example.com/hud/element.vwf" ); + } + } + return foundElement; + } + +} ); \ No newline at end of file diff --git a/support/client/lib/vwf/view/hud.js b/support/client/lib/vwf/view/hud.js new file mode 100644 index 000000000..3d15633de --- /dev/null +++ b/support/client/lib/vwf/view/hud.js @@ -0,0 +1,245 @@ +"use strict"; + +define( [ "module", "vwf/model", "vwf/utility", "vwf/view/hud/hud" ], function( module, model, utility, HUD ) { + + return model.load( module, { + + initialize: function() {}, + + initializedNode: function( nodeID, childID, childExtendsID, childImplementsIDs, + childSource, childType, childIndex, childName ) { + + var node; + if ( this.state.overlays[ childID ] ) { + node = this.state.overlays[ childID ]; + node.viewObject = createOverlay( node ); + node.rafID = requestAnimationFrame( animateHUD.bind( node ) ); + node.initialized = true; + } else if ( this.state.elements[ childID ] ) { + node = this.state.elements[ childID ]; + node.viewObject = createElement( node ); + node.initialized = true; + } + }, + + deletedNode: function( nodeID ) { + if ( this.state.overlays[ nodeID ] ) { + var node, keys, i; + node = this.state.overlays[ nodeID ]; + cancelAnimationFrame( node.rafID ); + delete this.state.overlays[ nodeID ]; + } else if ( this.state.elements[ nodeID ] ) { + var node, parent; + node = this.state.elements[ nodeID ]; + delete this.state.elements[ nodeID ]; + } + }, + + createdProperty: function( nodeID, propertyName, propertyValue ) { + return this.initializedProperty( nodeID, propertyName, propertyValue ); + }, + + initializedProperty: function( nodeID, propertyName, propertyValue ) { + var value; + if ( propertyValue !== undefined ) { + var node = this.state.overlays[ nodeID ] || this.state.elements[ nodeID ] ; + if ( node !== undefined ) { + switch ( propertyName ) { + default: + value = this.satProperty( nodeID, propertyName, propertyValue ); + break; + } + } + } + return value; + }, + + satProperty: function( nodeID, propertyName, propertyValue ) { + var value, node; + if ( this.state.overlays[ nodeID ] ) { + node = this.state.overlays[ nodeID ]; + if ( node.initialized ) { + if ( node.properties.hasOwnProperty( propertyName ) || + node.drawProperties.hasOwnProperty( propertyName ) ) { + node.viewObject[ propertyName ] = propertyValue; + } + } + value = propertyValue; + } else if ( this.state.elements[ nodeID ] ) { + node = this.state.elements[ nodeID ]; + if ( node.initialized ) { + if ( propertyName === "images" ) { + updateImages( node, propertyValue ); + } else if ( node.properties.hasOwnProperty( propertyName ) || + node.drawProperties.hasOwnProperty( propertyName ) ) { + node.viewObject[ propertyName ] = propertyValue; + } + } + value = propertyValue; + } + return value; + }, + + createdMethod: function( nodeID, methodName, methodParameters, methodBody ) { + var node; + if ( this.state.overlays[ nodeID ] ) { + node = this.state.overlays[ nodeID ]; + switch ( methodName ) { + case "elementPreDraw": + case "elementPostDraw": + case "globalPreDraw": + case "globalPostDraw": + this.kernel.getMethod( nodeID, methodName ); + break; + } + } else if ( this.state.elements[ nodeID ] ) { + node = this.state.elements[ nodeID ]; + if ( methodName === "draw" ) { + this.kernel.getMethod( nodeID, "draw" ); + } + } + }, + + gotMethod: function( nodeID, methodName, methodHandler ) { + var node; + if ( this.state.overlays[ nodeID ] ) { + node = this.state.overlays[ nodeID ]; + switch ( methodName ) { + case "elementPreDraw": + case "elementPostDraw": + node.viewObject[ methodName ] = function( context, element ) { + eval( methodHandler.body ); + }; + break; + case "globalPreDraw": + case "globalPostDraw": + node.viewObject[ methodName ] = function( context ) { + eval( methodHandler.body ); + }; + break; + } + } else if ( this.state.elements[ nodeID ] ) { + node = this.state.elements[ nodeID ]; + if ( methodName === "draw" ) { + node.viewObject[ methodName ] = function( context, position ) { + eval( methodHandler.body ); + }; + } + } + } + + } ); + + function animateHUD() { + this.viewObject.update(); + requestAnimationFrame( animateHUD.bind( this ) ); + } + + function createOverlay( node ) { + var overlay = new HUD(); + var keys = Object.keys( node.elements ); + var element, props; + for ( var i = 0; i < keys.length; i++ ) { + element = node.elements[ keys[ i ] ]; + props = element.properties; + overlay.add( element.viewObject, props.alignH, props.alignV, props.offsetH, props.offsetV, props.drawOrder ); + } + return overlay; + } + + function createElement( node ) { + var props = node.properties; + var drawProps = node.drawProperties; + var element = new HUD.Element( node.name, undefined, props.width, props.height, props.visible ); + var i, keys, images; + // Add custom properties to the element + keys = Object.keys( drawProps ); + for ( i = 0; i < keys.length; i++ ) { + element[ keys[ i ] ] = drawProps[ keys[ i ] ]; + } + // Add images to the element as properties + if ( props.images ) { + images = loadImages( node ); + keys = Object.keys( images ); + for ( i = 0; i < keys.length; i++ ) { + element[ keys[ i ] ] = images[ keys[ i ] ]; + } + } + // Add event listeners to the element + element.onClick = function( event ) { + var elementMousePosition = { + "x": event.clientX - this.position.x, + "y": event.clientY - this.position.y + }; + vwf_view.kernel.fireEvent( node.id, "onClick", [ elementMousePosition ] ); + }; + element.onMouseDown = function( event ) { + var elementMousePosition = { + "x": event.clientX - this.position.x, + "y": event.clientY - this.position.y + }; + vwf_view.kernel.fireEvent( node.id, "onMouseDown", [ elementMousePosition ] ); + }; + element.onMouseUp = function( event ) { + var elementMousePosition = { + "x": event.clientX - this.position.x, + "y": event.clientY - this.position.y + }; + vwf_view.kernel.fireEvent( node.id, "onMouseUp", [ elementMousePosition ] ); + }; + element.onMouseMove = function( event ) { + var elementMousePosition = { + "x": event.clientX - this.position.x, + "y": event.clientY - this.position.y + }; + vwf_view.kernel.fireEvent( node.id, "onMouseMove", [ elementMousePosition ] ); + }; + element.onMouseOver = function( event ) { + var elementMousePosition = { + "x": event.clientX - this.position.x, + "y": event.clientY - this.position.y + }; + vwf_view.kernel.fireEvent( node.id, "onMouseOver", [ elementMousePosition ] ); + }; + element.onMouseOut = function( event ) { + var elementMousePosition = { + "x": event.clientX - this.position.x, + "y": event.clientY - this.position.y + }; + vwf_view.kernel.fireEvent( node.id, "onMouseOut", [ elementMousePosition ] ); + }; + return element; + } + + function loadImages( node ) { + var images = node.properties.images; + var keys = Object.keys( images ); + var image, src; + var results = {}; + for ( var i = 0; i < keys.length; i++ ) { + image = new Image(); + src = images[ keys[ i ] ]; + if ( src ) { + image.src = src; + } + results[ keys[ i ] ] = image; + } + return results; + } + + function updateImages( node, images ) { + var keys = Object.keys( images ); + var newImageSrc, oldImage; + for ( var i = 0; i < keys.length; i++ ) { + newImageSrc = images[ keys[ i ] ]; + oldImage = node.viewObject[ keys[ i ] ]; + if ( newImageSrc && oldImage instanceof Image && newImageSrc !== oldImage.src ) { + oldImage.src = newImageSrc; + } else if ( !oldImage ) { + var newImage = node.viewObject[ keys[ i ] ] = new Image(); + newImage.src = newImageSrc; + } + } + } + +} ); \ No newline at end of file diff --git a/support/client/lib/vwf/view/hud/hud.js b/support/client/lib/vwf/view/hud/hud.js new file mode 100644 index 000000000..627803675 --- /dev/null +++ b/support/client/lib/vwf/view/hud/hud.js @@ -0,0 +1,322 @@ +define( function() { + + var HUD = function() { + this.initialize(); + return this; + } + + HUD.prototype = { + constructor: HUD, + elements: undefined, + elementCount: undefined, + sortedElements: undefined, + picks: undefined, + canvas: undefined, + visible: undefined, + enabled: undefined, + defaultHandlers: undefined, + + initialize: function() { + var gameCanvas = document.getElementById( vwf_view.kernel.application() ); + this.elements = {}; + this.elementCount = 0; + this.sortedElements = []; + this.picks = []; + this.canvas = document.createElement( "CANVAS" ); + this.canvas.id = "HUDCanvas"; + gameCanvas.parentElement.appendChild( this.canvas ); + this.visible = true; + this.enabled = true; + this.update(); + this.defaultHandlers = {}; + this.registerEventListeners( gameCanvas ); + }, + + update: function() { + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight; + if ( this.visible ) { + this.draw(); + } + }, + + draw: function() { + var context = this.canvas.getContext( '2d' ); + this.sortedElements.length = 0; + + for ( var el in this.elements ) { + var element = this.elements[ el ]; + element.position.x = 0; + element.position.y = 0; + + switch ( element.alignH ) { + case "left": + element.position.x = 0; + break; + case "center": + element.position.x = -element.width / 2; + element.position.x += this.canvas.width / 2; + break; + case "right": + element.position.x = -element.width; + element.position.x += this.canvas.width; + break; + } + + switch ( element.alignV ) { + case "top": + element.position.y = 0; + break; + case "middle": + element.position.y = -element.height / 2; + element.position.y += this.canvas.height / 2; + break; + case "bottom": + element.position.y = -element.height; + element.position.y += this.canvas.height; + break; + } + + element.position.x += element.offsetH; + element.position.y += element.offsetV; + + if ( element.visible ) { + this.sortedElements.push( element ); + } + } + + this.globalPreDraw( context ); + this.sortedElements.sort( this.sortFunction ); + for ( var i = 0; i < this.sortedElements.length; i++ ) { + this.elementPreDraw( context, this.sortedElements[ i ] ); + this.sortedElements[ i ].draw( context, this.sortedElements[ i ].position ); + this.elementPostDraw( context, this.sortedElements[ i ] ); + } + this.globalPostDraw( context ); + + }, + + add: function( element, alignH, alignV, offsetH, offsetV, drawOrder ) { + + // Add the element to the HUD's elements list + // Initialize the offset position + this.elements[ element.id ] = element; + var newElement = this.elements[ element.id ]; + + newElement[ "offsetH" ] = isNaN( offsetH ) ? 0 : offsetH; + newElement[ "offsetV" ] = isNaN( offsetV ) ? 0 : offsetV; + + newElement[ "position" ] = { + "x": 0, + "y": 0 + } + + switch ( alignH ) { + case "left": + case "center": + case "right": + newElement[ "alignH" ] = alignH; + break; + default: + newElement[ "alignH" ] = "left"; + break; + } + + switch ( alignV ) { + case "top": + case "middle": + case "bottom": + newElement[ "alignV" ] = alignV; + break; + default: + newElement[ "alignV" ] = "top"; + break; + } + + this.countElements(); + if ( isNaN( drawOrder ) ) { + newElement[ "drawOrder" ] = this.elementCount; + } else { + newElement[ "drawOrder" ] = drawOrder; + } + + }, + + sortFunction: function( a, b ) { + return a.drawOrder - b.drawOrder; + }, + + remove: function( element ) { + var index = this.elements[ element.id ].drawOrder; + delete this.elements[ element.id ]; + + for ( var el in this.elements ) { + if ( this.elements[ el ].drawOrder > index ) { + this.elements[ el ].drawOrder--; + } + } + + this.countElements(); + }, + + countElements: function() { + var count = 0; + + for ( var el in this.elements ) { + count++; + } + + this.elementCount = count; + }, + + pick: function( event ) { + // Use sortedElements since they are all visible + var elements = this.sortedElements; + this.picks.length = 0; + // Loop backward to order picks from nearest to furthest + for ( var i = elements.length - 1; i >= 0; i-- ) { + var pos = elements[ i ].position; + var width = pos.x + elements[ i ].width; + var height = pos.y + elements[ i ].height; + + if ( event.clientX > pos.x && event.clientX < width && + event.clientY > pos.y && event.clientY < height ) { + + if ( elements[ i ].isMouseOver !== true ) { + elements[ i ].isMouseOver = true; + elements[ i ].onMouseOver( event ); + } + this.picks.push( elements[ i ] ); + + } else if ( elements[ i ].isMouseOver === true ) { + elements[ i ].isMouseOver = false; + elements[ i ].onMouseOut( event ); + } + } + }, + + registerEventListeners: function( gameCanvas ) { + var emptyEvent = function( event ) {}; + this.defaultHandlers.onClick = gameCanvas.onclick; + gameCanvas.onclick = emptyEvent; + gameCanvas.addEventListener( "click", this.handleEvent.bind( this ) ); + + this.defaultHandlers.onMouseUp = gameCanvas.onmouseup; + gameCanvas.onmouseup = emptyEvent; + gameCanvas.addEventListener( "mouseup", this.handleEvent.bind( this ) ); + + this.defaultHandlers.onMouseDown = gameCanvas.onmousedown; + gameCanvas.onmousedown = emptyEvent; + gameCanvas.addEventListener( "mousedown", this.handleEvent.bind( this ) ); + + this.defaultHandlers.onMouseMove = gameCanvas.onmousemove; + gameCanvas.onmousemove = emptyEvent; + gameCanvas.addEventListener( "mousemove", this.handleEvent.bind( this ) ); + }, + + handleEvent: function( event ) { + var type; + switch ( event.type ) { + case "click": + type = "onClick"; + break; + case "mouseup": + type = "onMouseUp"; + break; + case "mousedown": + type = "onMouseDown"; + break; + case "mousemove": + type = "onMouseMove"; + break; + default: + console.log( "HUD.handleEvent - Unhandled event type: " + event.type ); + return; + } + if ( this.enabled ) { + this.pick( event ); + var topPick = this.picks[ 0 ]; + if ( topPick ) { + if ( topPick.enabled ) { + this.elements[ topPick.id ][ type ]( event ); + } + } else if ( this.defaultHandlers[ type ] instanceof Function ) { + this.defaultHandlers[ type ]( event ); + } + } else if ( this.defaultHandlers[ type ] instanceof Function ) { + this.defaultHandlers[ type ]( event ); + } + }, + + moveToTop: function( id ) { + var index = this.elements[ id ].drawOrder; + for ( var el in this.elements ) { + if ( this.elements[ el ].drawOrder > index ) { + this.elements[ el ].drawOrder--; + } + } + this.elements[ id ].drawOrder = this.elementCount; + }, + + elementPreDraw: function( context, element ) { }, + + elementPostDraw: function( context, element ) { }, + + globalPreDraw: function( context ) { }, + + globalPostDraw: function( context ) { } + + } + + HUD.Element = function( id, drawFunc, width, height, visible ) { + this.initialize( id, drawFunc, width, height ); + return this; + } + + HUD.Element.prototype = { + constructor: HUD.Element, + id: undefined, + width: undefined, + height: undefined, + isMouseOver: undefined, + visible: undefined, + enabled: undefined, + + initialize: function( id, drawFunc, width, height, visible ) { + this.id = id; + + if ( drawFunc instanceof Function ) { + this.draw = drawFunc; + } + + this.width = isNaN( width ) ? 0 : width; + this.height = isNaN( height ) ? 0 : height; + + if ( visible === true || visible === undefined ) { + this.visible = true; + } else { + this.visible = false; + } + + this.enabled = true; + }, + + draw: function( context, position ) { }, + + onClick: function( event ) { }, + + onMouseDown: function( event ) { }, + + onMouseUp: function( event ) { }, + + onMouseMove: function( event ) { }, + + onMouseOver: function( event ) { }, + + onMouseOut: function( event ) { } + + } + + return HUD; + +} ); \ No newline at end of file diff --git a/support/proxy/vwf.example.com/hud/element.vwf.yaml b/support/proxy/vwf.example.com/hud/element.vwf.yaml new file mode 100644 index 000000000..d9bd34392 --- /dev/null +++ b/support/proxy/vwf.example.com/hud/element.vwf.yaml @@ -0,0 +1,21 @@ +extends: http://vwf.example.com/node.vwf +properties: + images: + width: + height: + visible: + enabled: + alignH: + alignV: + offsetH: + offsetV: + drawOrder: +methods: + draw: +events: + onClick: + onMouseDown: + onMouseUp: + onMouseMove: + onMouseOver: + onMouseOut: diff --git a/support/proxy/vwf.example.com/hud/overlay.vwf.yaml b/support/proxy/vwf.example.com/hud/overlay.vwf.yaml new file mode 100644 index 000000000..b752ad0d7 --- /dev/null +++ b/support/proxy/vwf.example.com/hud/overlay.vwf.yaml @@ -0,0 +1,9 @@ +extends: http://vwf.example.com/node.vwf +properties: + visible: true + enabled: true +methods: + elementPreDraw: + elementPostDraw: + globalPreDraw: + globalPostDraw: