From 143d25bb111bd10864e314a0f2517e3d1a10edfe Mon Sep 17 00:00:00 2001 From: underscorediscovery Date: Sun, 26 Jan 2014 21:55:16 -0330 Subject: [PATCH] Wrapper; Input; adding html5 gamepad support, still a few minor things to add (disconnect/reconnect like native) and initial event types for gamepad for native as well --- lime/InputHandler.hx | 113 ++++++++++-- lime/helpers/html5/InputHelper.hx | 214 +++++++++++++++++++++- lime/helpers/native/InputHelper.hx | 6 +- templates/html5/template/index.html | 1 + templates/html5/template/lib/modernizr.js | 4 + 5 files changed, 317 insertions(+), 21 deletions(-) create mode 100644 templates/html5/template/lib/modernizr.js diff --git a/lime/InputHandler.hx b/lime/InputHandler.hx index fa9285aec..c2b362205 100644 --- a/lime/InputHandler.hx +++ b/lime/InputHandler.hx @@ -74,6 +74,12 @@ class InputHandler { @:noCompletion public function update() { + //update any helper stuff + helper.update(); + + //remove any stale key pressed value + //unless it wasn't alive for a full frame yet, + //then flag it so that it may be for(_value in key_value_pressed.keys()){ var _flag : Bool = key_value_pressed.get(_value); @@ -85,6 +91,9 @@ class InputHandler { } //each pressed_value + //remove any stale key released value + //unless it wasn't alive for a full frame yet, + //then flag it so that it may be for(_value in key_value_released.keys()){ var _flag : Bool = key_value_released.get(_value); @@ -486,12 +495,77 @@ class InputHandler { //Gamepad - @:noCompletion public function lime_gamepadaxis(_event:Dynamic) : Void { + @:noCompletion public function lime_gamepadaxis( _event:Dynamic, ?_pass_through:Bool=false ) : Void { + if(lib.host.ongamepadaxis != null) { - lib.host.ongamepadaxis(_event); - } + + var _gamepad_event = _event; + + if(!_pass_through) { + + _gamepad_event = { + raw : _event, + axis : _event.code, + value : (_event.value / 32767), + gamepad : _event.id + } + + } //pass through + + lib.host.ongamepadaxis( _gamepad_event ); + + } //lib.host.ongamepadaxis != null + } //lime_gamepadaxis + @:noCompletion public function lime_gamepadbuttondown( _event:Dynamic, ?_pass_through:Bool=false ) : Void { + + if(lib.host.ongamepadbuttondown != null) { + + var _gamepad_event = _event; + + if(!_pass_through) { + + _gamepad_event = { + raw : _event, + state : ButtonState.down, + value : 0, + button : _event.code, + gamepad : _event.id + }; + + } + + lib.host.ongamepadbuttondown( _gamepad_event ); + + } + + } //lime_gamepadbuttondown + + @:noCompletion public function lime_gamepadbuttonup( _event:Dynamic, ?_pass_through:Bool=false ) : Void { + + if(lib.host.ongamepadbuttonup != null) { + + var _gamepad_event = _event; + + if(!_pass_through) { + + _gamepad_event = { + raw : _event, + state : ButtonState.up, + value : 1, + button : _event.code, + gamepad : _event.id + }; + + } + + lib.host.ongamepadbuttonup( _gamepad_event ); + + } + + } //lime_gamepadbuttonup + @:noCompletion public function lime_gamepadball(_event:Dynamic) : Void { if(lib.host.ongamepadball != null) { lib.host.ongamepadball(_event); @@ -504,18 +578,6 @@ class InputHandler { } } //lime_gamepadhat - @:noCompletion public function lime_gamepadbuttondown(_event:Dynamic) : Void { - if(lib.host.ongamepadbuttondown != null) { - lib.host.ongamepadbuttondown(_event); - } - } //lime_gamepadbuttondown - - @:noCompletion public function lime_gamepadbuttonup(_event:Dynamic) : Void { - if(lib.host.ongamepadbuttonup != null) { - lib.host.ongamepadbuttonup(_event); - } - } //lime_gamepadbuttonup - private static var efLeftDown = 0x0001; private static var efShiftDown = 0x0002; private static var efCtrlDown = 0x0004; @@ -538,6 +600,11 @@ enum MouseState { up; } +enum ButtonState { + down; + up; +} + enum MouseButton { move; left; @@ -586,5 +653,21 @@ typedef MouseEvent = { typedef GamepadEvent = { var raw : Dynamic; + var gamepad : Int; +} + +typedef GamepadButtonEvent = { + var raw : Dynamic; + var gamepad : Int; + var button : Int; + var value : Float; + var state : ButtonState; +} + +typedef GamepadAxisEvent = { + var raw : Dynamic; + var gamepad : Int; + var axis : Int; + var value : Float; } diff --git a/lime/helpers/html5/InputHelper.hx b/lime/helpers/html5/InputHelper.hx index fa06766ff..fdf7987ba 100644 --- a/lime/helpers/html5/InputHelper.hx +++ b/lime/helpers/html5/InputHelper.hx @@ -1,5 +1,9 @@ package lime.helpers.html5; +import lime.InputHandler.GamepadEvent; +import lime.InputHandler.GamepadButtonEvent; +import lime.InputHandler.GamepadAxisEvent; +import lime.InputHandler.ButtonState; import lime.InputHandler.MouseButton; import lime.InputHandler.MouseState; import lime.RenderHandler.BrowserLike; @@ -19,13 +23,13 @@ class InputHelper { public function startup() { //right click on the canvas should be canceled on browser - lib.window_handle.addEventListener('contextmenu', on_contextmenu ); + lib.window_handle.addEventListener('contextmenu', on_contextmenu ); //mouse events bind to the lime element only, //maybe this should be adjusted for mousemove? - lib.window_handle.addEventListener('mousedown', on_mousedown); - lib.window_handle.addEventListener('mousemove', on_mousemove); - lib.window_handle.addEventListener('mouseup', on_mouseup); + lib.window_handle.addEventListener('mousedown', on_mousedown); + lib.window_handle.addEventListener('mousemove', on_mousemove); + lib.window_handle.addEventListener('mouseup', on_mouseup); //there are two kinds of scroll across browser, we handle both lib.window_handle.addEventListener('mousewheel', on_mousewheel); @@ -35,8 +39,197 @@ class InputHelper { js.Browser.document.addEventListener('keydown', on_keydown); js.Browser.document.addEventListener('keyup', on_keyup); + //handle any gamepad information, if the browser supports it + startup_gamepads(); + } //startup + @:noCompletion public function update() { + if(gamepads_supported) { + poll_gamepads(); + } + } + + function fail_gamepads() { + gamepads_supported = false; + trace("lime : Gamepads are not supported in this browser :("); + } + + + //if gamepads are supported, the method to get them will + //be stored in this variable for reuse + var gamepads_supported : Bool = false; + + function startup_gamepads() { + + active_gamepads = new Map(); + gamepads_supported = (get_gamepad_list != null); + + } + + function poll_gamepads() { + + var list = get_gamepad_list(); + if(list != null) { + for(i in 0 ... list.length) { + if( untyped list[i] != null ) { + handle_gamepad( untyped list[i] ); + } + } + } + + } //poll_gamepads + + //this will take some liberties for now + //because chrome and firefox share the same spec for + //the most part and all future implementations should + //follow spec too. If the spec changes we can adjust if needed + var active_gamepads : Map; + + function handle_gamepad( _gamepad : Dynamic ) { + + //disconnected gamepads we don't need + if(_gamepad == null) return; + + //check if this gamepad exists already + if( !active_gamepads.exists( _gamepad.index ) ) { + + //if not we add it to the list + active_gamepads.set( _gamepad.index, { + id : _gamepad.id, + index : _gamepad.index, + axes : cast _gamepad.axes, + buttons : cast _gamepad.buttons, + timestamp : _gamepad.timestamp + }); + + //fire an on connected event + // :todo: + + } else { + + //found in the list so we can update it if anything changed + var gamepad = active_gamepads.get(_gamepad.index); + //but only if the timestamp differs + if(gamepad.timestamp != _gamepad.timestamp) { + + //update the id if it changed + if(gamepad.id != _gamepad.id) { gamepad.id = _gamepad.id; } + //update the timestamp + gamepad.timestamp = _gamepad.timestamp; + + //we store the list of changed indices + //so we can call the handler functions with each + var axes_changed : Array = []; + var buttons_changed : Array = []; + //the last known values + var last_axes = gamepad.axes; + var last_buttons = gamepad.buttons; + + //the new known values + var new_axes : Array = cast _gamepad.axes; + var new_buttons : Array = cast _gamepad.buttons; + + //check for axes changes + var axis_index : Int = 0; + for(axis in new_axes) { + + if(axis != last_axes[axis_index]) { + axes_changed.push(axis_index); + gamepad.axes[axis_index] = axis; + } + + axis_index++; + + } //axis in new_axes + + //check for button changes + var button_index : Int = 0; + for(button in new_buttons) { + + if(button != last_buttons[button_index]) { + buttons_changed.push(button_index); + gamepad.buttons[button_index] = button; + } + + button_index++; + + } //button in new_buttons + + + //now forward any axis changes to the wrapper + for(index in axes_changed) { + + lib.input.lime_gamepadaxis({ + raw : gamepad, + axis : index, + value : new_axes[index], + gamepad : gamepad.index + }, true); + + } //for each axis changed + + //then forward any button changes to the wrapper + for(index in buttons_changed) { + + var _state = (new_buttons[index] == 0) ? ButtonState.up : ButtonState.down; + var _gamepad_event : GamepadButtonEvent = { + raw : gamepad, + state : _state, + value : new_buttons[index], + button : index, + gamepad : gamepad.index + }; + + if(_state == ButtonState.up) { + lib.input.lime_gamepadbuttonup( _gamepad_event, true ); + } else { + lib.input.lime_gamepadbuttondown( _gamepad_event, true ); + } + + } //for each button change + + } //timestamp changed + + } //exists + + } //handle_gamepad + + //It's really early for gamepads in browser, + // but we can still support them where they exist + function get_gamepad_list() : Dynamic { + + //Modernizr is used to detect gamepad support + var modernizr = untyped js.Browser.window.Modernizr; + if(modernizr != null) { + + if(modernizr.gamepads == true) { + + //try official api first + if( untyped js.Browser.navigator.getGamepads != null ) { + return untyped js.Browser.navigator.getGamepads(); + } + + //try newer webkit GetGamepads() function + if( untyped js.Browser.navigator.webkitGetGamepads != null ) { + return untyped js.Browser.navigator.webkitGetGamepads(); + } + + //if we make it here we failed support so fail out + fail_gamepads(); + + } else { + + fail_gamepads(); + + } + + } //modernizr != null + + return null; + + } //get_gamepad_list + function on_contextmenu( _event:Dynamic ) { _event.preventDefault(); @@ -122,10 +315,12 @@ class InputHelper { switch(lib.render.browser) { case BrowserLike.chrome, BrowserLike.safari, BrowserLike.opera: + deltaX = untyped _event.webkitMovementX; deltaY = untyped _event.webkitMovementY; case BrowserLike.firefox: + deltaX = untyped _event.mozMovementX; deltaY = untyped _event.mozMovementY; @@ -204,4 +399,13 @@ class InputHelper { } //on_keyup -} //InputHelper \ No newline at end of file +} //InputHelper + + +typedef HTML5Gamepad = { + axes : Array, + index : Int, + buttons : Array, + id : String, + timestamp : Float +} \ No newline at end of file diff --git a/lime/helpers/native/InputHelper.hx b/lime/helpers/native/InputHelper.hx index 5b83fc498..19bf584b9 100644 --- a/lime/helpers/native/InputHelper.hx +++ b/lime/helpers/native/InputHelper.hx @@ -12,7 +12,11 @@ class InputHelper { } //new - public function startup() { + @:noCompletion public function startup() { + + } + + @:noCompletion public function update() { } diff --git a/templates/html5/template/index.html b/templates/html5/template/index.html index b6a5304b7..451ec45fb 100755 --- a/templates/html5/template/index.html +++ b/templates/html5/template/index.html @@ -21,6 +21,7 @@ + diff --git a/templates/html5/template/lib/modernizr.js b/templates/html5/template/lib/modernizr.js new file mode 100644 index 000000000..f645852f1 --- /dev/null +++ b/templates/html5/template/lib/modernizr.js @@ -0,0 +1,4 @@ +/* Modernizr 2.7.1 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-audio-video-localstorage-sessionstorage-websockets-webworkers-geolocation-touch-webgl-shiv-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-gamepad-load + */ +;window.Modernizr=function(a,b,c){function z(a){i.cssText=a}function A(a,b){return z(l.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a){var e=a[d];if(!C(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.7.1",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v=function(a,c,d,e){var h,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),l.appendChild(j);return h=["­",'"].join(""),l.id=g,(m?l:n).innerHTML+=h,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=f.style.overflow,f.style.overflow="hidden",f.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),f.style.overflow=k),!!i},w=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=B(e[d],"function"),B(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.webgl=function(){return!!a.WebGLRenderingContext},p.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:v(["@media (",l.join("touch-enabled),("),g,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},p.geolocation=function(){return"geolocation"in navigator},p.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},p.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},p.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},p.localstorage=function(){try{return localStorage.setItem(g,g),localStorage.removeItem(g),!0}catch(a){return!1}},p.sessionstorage=function(){try{return sessionStorage.setItem(g,g),sessionStorage.removeItem(g),!0}catch(a){return!1}},p.webworkers=function(){return!!a.Worker};for(var G in p)y(p,G)&&(u=G.toLowerCase(),e[u]=p[G](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof enableClasses!="undefined"&&enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),h=j=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.hasEvent=w,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=v,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f