forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			1347 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			1347 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
package kha;
 | 
						||
 | 
						||
import js.Browser;
 | 
						||
import js.Syntax;
 | 
						||
import js.html.CanvasElement;
 | 
						||
import js.html.ClipboardEvent;
 | 
						||
import js.html.DeviceMotionEvent;
 | 
						||
import js.html.DeviceOrientationEvent;
 | 
						||
import js.html.KeyboardEvent;
 | 
						||
import js.html.MouseEvent;
 | 
						||
import js.html.Touch;
 | 
						||
import js.html.TouchEvent;
 | 
						||
import js.html.WebSocket;
 | 
						||
import js.html.WheelEvent;
 | 
						||
import js.html.webgl.GL;
 | 
						||
import kha.System;
 | 
						||
import kha.graphics4.TextureFormat;
 | 
						||
import kha.input.Gamepad;
 | 
						||
import kha.input.KeyCode;
 | 
						||
import kha.input.Keyboard;
 | 
						||
import kha.input.Mouse;
 | 
						||
import kha.input.Sensor;
 | 
						||
import kha.input.Surface;
 | 
						||
import kha.js.AudioElementAudio;
 | 
						||
import kha.js.CanvasGraphics;
 | 
						||
import kha.js.MobileWebAudio;
 | 
						||
import kha.js.vr.VrInterface;
 | 
						||
 | 
						||
using StringTools;
 | 
						||
 | 
						||
class GamepadStates {
 | 
						||
	public var axes: Array<Float>;
 | 
						||
	public var buttons: Array<Float>;
 | 
						||
 | 
						||
	public function new() {
 | 
						||
		axes = new Array<Float>();
 | 
						||
		buttons = new Array<Float>();
 | 
						||
	}
 | 
						||
}
 | 
						||
 | 
						||
class SystemImpl {
 | 
						||
	public static var gl: GL;
 | 
						||
	public static var gl2: Bool;
 | 
						||
	public static var halfFloat: Dynamic;
 | 
						||
	public static var anisotropicFilter: Dynamic;
 | 
						||
	public static var depthTexture: Dynamic;
 | 
						||
	public static var drawBuffers: Dynamic;
 | 
						||
	public static var elementIndexUint: Dynamic;
 | 
						||
	@:noCompletion public static var _hasWebAudio: Bool;
 | 
						||
	// public static var graphics(default, null): Graphics;
 | 
						||
	public static var khanvas: CanvasElement;
 | 
						||
	static var options: SystemOptions;
 | 
						||
	public static var mobile: Bool = false;
 | 
						||
	public static var ios: Bool = false;
 | 
						||
	public static var mobileAudioPlaying: Bool = false;
 | 
						||
	static var chrome: Bool = false;
 | 
						||
	static var firefox: Bool = false;
 | 
						||
	public static var safari: Bool = false;
 | 
						||
	public static var ie: Bool = false;
 | 
						||
	public static var insideInputEvent: Bool = false;
 | 
						||
	static public var activeMouseEvent: Null<MouseEvent>;
 | 
						||
	static public var activeWheelEvent: Null<WheelEvent>;
 | 
						||
	static public var activeKeyEvent: Null<KeyboardEvent>;
 | 
						||
	static var window: Window;
 | 
						||
	public static var estimatedRefreshRate: Int = 60;
 | 
						||
 | 
						||
	static function errorHandler(message: String, source: String, lineno: Int, colno: Int, error: Dynamic) {
 | 
						||
		Browser.console.error("Error: " + message);
 | 
						||
		if (error != null) {
 | 
						||
			if (Std.isOfType(error, haxe.Exception)) {
 | 
						||
				var err: haxe.Exception = error;
 | 
						||
				if (err.stack != null) {
 | 
						||
					Browser.console.error("Stack:\n" + err.stack);
 | 
						||
				}
 | 
						||
			}
 | 
						||
			else if (Std.isOfType(error, js.lib.Error)) {
 | 
						||
				var err: js.lib.Error = error;
 | 
						||
				if (err.stack != null) {
 | 
						||
					Browser.console.error("Stack:\n" + err.stack);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
		return true;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function init(options: SystemOptions, callback: Window->Void): Void {
 | 
						||
		SystemImpl.options = options;
 | 
						||
		#if kha_debug_html5
 | 
						||
		Browser.window.onerror = cast errorHandler;
 | 
						||
 | 
						||
		var showWindow = Syntax.code("window.electron.showWindow");
 | 
						||
		showWindow(options.title, options.window.x, options.window.y, options.width, options.height);
 | 
						||
 | 
						||
		initSecondStep(callback);
 | 
						||
 | 
						||
		chrome = true;
 | 
						||
		mobileAudioPlaying = true;
 | 
						||
		#else
 | 
						||
		mobile = isMobile();
 | 
						||
		ios = isIOS();
 | 
						||
		chrome = isChrome();
 | 
						||
		firefox = isFirefox();
 | 
						||
		safari = isSafari();
 | 
						||
		ie = isIE();
 | 
						||
 | 
						||
		mobileAudioPlaying = !mobile && !chrome && !firefox;
 | 
						||
 | 
						||
		initSecondStep(callback);
 | 
						||
		#end
 | 
						||
 | 
						||
		#if kha_live_reload
 | 
						||
		function openWebSocket(): Void {
 | 
						||
			var host = Browser.location.hostname;
 | 
						||
			if (host == "")
 | 
						||
				host = "localhost";
 | 
						||
			var port = Std.parseInt(Browser.location.port);
 | 
						||
			if (port == null)
 | 
						||
				port = 80;
 | 
						||
			final ws = new WebSocket('ws://$host:${port + 1}');
 | 
						||
			ws.onmessage = () -> Browser.location.reload();
 | 
						||
		}
 | 
						||
		openWebSocket();
 | 
						||
		#end
 | 
						||
	}
 | 
						||
 | 
						||
	static function initSecondStep(callback: Window->Void): Void {
 | 
						||
		init2(options.window.width, options.window.height);
 | 
						||
		initAnimate(callback);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function initSensor(): Void {
 | 
						||
		if (ios) { // In Safari for iOS the directions are reversed on axes x, y and z
 | 
						||
			Browser.window.ondevicemotion = function(event: DeviceMotionEvent) {
 | 
						||
				Sensor._changed(0, -event.accelerationIncludingGravity.x, -event.accelerationIncludingGravity.y, -event.accelerationIncludingGravity.z);
 | 
						||
			};
 | 
						||
		}
 | 
						||
		else {
 | 
						||
			Browser.window.ondevicemotion = function(event: DeviceMotionEvent) {
 | 
						||
				Sensor._changed(0, event.accelerationIncludingGravity.x, event.accelerationIncludingGravity.y, event.accelerationIncludingGravity.z);
 | 
						||
			};
 | 
						||
		}
 | 
						||
		Browser.window.ondeviceorientation = function(event: DeviceOrientationEvent) {
 | 
						||
			Sensor._changed(1, event.beta, event.gamma, event.alpha);
 | 
						||
		};
 | 
						||
	}
 | 
						||
 | 
						||
	static function isMobile(): Bool {
 | 
						||
		var agent = js.Browser.navigator.userAgent;
 | 
						||
		if (agent.contains("Android") || agent.contains("webOS") || agent.contains("BlackBerry") || agent.contains("Windows Phone")) {
 | 
						||
			return true;
 | 
						||
		}
 | 
						||
		if (isIOS())
 | 
						||
			return true;
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function isIOS(): Bool {
 | 
						||
		var agent = js.Browser.navigator.userAgent;
 | 
						||
		if (agent.contains("iPhone") || agent.contains("iPad") || agent.contains("iPod")) {
 | 
						||
			return true;
 | 
						||
		}
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function isChrome(): Bool {
 | 
						||
		var agent = js.Browser.navigator.userAgent;
 | 
						||
		if (agent.contains("Chrome")) {
 | 
						||
			return true;
 | 
						||
		}
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function isFirefox(): Bool {
 | 
						||
		var agent = js.Browser.navigator.userAgent;
 | 
						||
		if (agent.contains("Firefox")) {
 | 
						||
			return true;
 | 
						||
		}
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function isSafari(): Bool {
 | 
						||
		var agent = js.Browser.navigator.userAgent;
 | 
						||
		// Chrome has both in UA
 | 
						||
		if (agent.contains("Safari") && !agent.contains("Chrome")) {
 | 
						||
			return true;
 | 
						||
		}
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function isIE(): Bool {
 | 
						||
		var agent = js.Browser.navigator.userAgent;
 | 
						||
		if (agent.contains("MSIE ") || agent.contains("Trident/")) {
 | 
						||
			return true;
 | 
						||
		}
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function setCanvas(canvas: CanvasElement): Void {
 | 
						||
		khanvas = canvas;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getScreenRotation(): ScreenRotation {
 | 
						||
		return ScreenRotation.RotationNone;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getTime(): Float {
 | 
						||
		final now = js.Browser.window.performance != null ? js.Browser.window.performance.now() : js.lib.Date.now();
 | 
						||
		return now / 1000;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getSystemId(): String {
 | 
						||
		return "HTML5";
 | 
						||
	}
 | 
						||
 | 
						||
	public static function vibrate(ms: Int): Void {
 | 
						||
		Browser.navigator.vibrate(ms);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getLanguage(): String {
 | 
						||
		final lang = Browser.navigator.language;
 | 
						||
		return lang.substr(0, 2).toLowerCase();
 | 
						||
	}
 | 
						||
 | 
						||
	public static function requestShutdown(): Bool {
 | 
						||
		Browser.window.close();
 | 
						||
		return true;
 | 
						||
	}
 | 
						||
 | 
						||
	static inline var maxGamepads: Int = 4;
 | 
						||
	static var frame: Framebuffer;
 | 
						||
	static var keyboard: Keyboard = null;
 | 
						||
	static var mouse: kha.input.Mouse;
 | 
						||
	static var surface: Surface;
 | 
						||
	static var gamepads: Array<Gamepad>;
 | 
						||
	static var gamepadStates: Array<GamepadStates>;
 | 
						||
 | 
						||
	static var minimumScroll: Int = 999;
 | 
						||
	static var mouseX: Int;
 | 
						||
	static var mouseY: Int;
 | 
						||
	static var touchX: Int;
 | 
						||
	static var touchY: Int;
 | 
						||
	static var lastFirstTouchX: Int = 0;
 | 
						||
	static var lastFirstTouchY: Int = 0;
 | 
						||
 | 
						||
	static function init2(defaultWidth: Int, defaultHeight: Int, ?backbufferFormat: TextureFormat) {
 | 
						||
		#if !kha_no_keyboard
 | 
						||
		keyboard = new Keyboard();
 | 
						||
		#end
 | 
						||
		mouse = new kha.input.MouseImpl();
 | 
						||
		surface = new Surface();
 | 
						||
		gamepads = new Array<Gamepad>();
 | 
						||
		gamepadStates = new Array<GamepadStates>();
 | 
						||
		for (i in 0...maxGamepads) {
 | 
						||
			gamepads[i] = new Gamepad(i);
 | 
						||
			gamepadStates[i] = new GamepadStates();
 | 
						||
		}
 | 
						||
		js.Browser.window.addEventListener("gamepadconnected", (e) -> {
 | 
						||
			var pad: js.html.Gamepad = e.gamepad;
 | 
						||
			Gamepad.sendConnectEvent(pad.index);
 | 
						||
			for (i in 0...pad.buttons.length) {
 | 
						||
				gamepadStates[pad.index].buttons[i] = 0;
 | 
						||
			}
 | 
						||
		});
 | 
						||
		js.Browser.window.addEventListener("gamepaddisconnected", (e) -> {
 | 
						||
			Gamepad.sendDisconnectEvent(e.gamepad.index);
 | 
						||
		});
 | 
						||
		var sysGamepads = getGamepads();
 | 
						||
		if (sysGamepads != null) {
 | 
						||
			for (i in 0...sysGamepads.length) {
 | 
						||
				var pad = sysGamepads[i];
 | 
						||
				if (pad != null) {
 | 
						||
					gamepads[pad.index].connected = true;
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		function onCopy(e: ClipboardEvent): Void {
 | 
						||
			if (System.copyListener != null) {
 | 
						||
				var data = System.copyListener();
 | 
						||
				if (data != null)
 | 
						||
					e.clipboardData.setData("text/plain", data);
 | 
						||
				e.preventDefault();
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		function onCut(e: ClipboardEvent): Void {
 | 
						||
			if (System.cutListener != null) {
 | 
						||
				var data = System.cutListener();
 | 
						||
				if (data != null)
 | 
						||
					e.clipboardData.setData("text/plain", data);
 | 
						||
				e.preventDefault();
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		function onPaste(e: ClipboardEvent): Void {
 | 
						||
			if (System.pasteListener != null) {
 | 
						||
				System.pasteListener(e.clipboardData.getData("text/plain"));
 | 
						||
				e.preventDefault();
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		var document = Browser.document;
 | 
						||
		document.addEventListener("copy", onCopy);
 | 
						||
		document.addEventListener("cut", onCut);
 | 
						||
		document.addEventListener("paste", onPaste);
 | 
						||
 | 
						||
		CanvasImage.init();
 | 
						||
		Scheduler.init();
 | 
						||
 | 
						||
		loadFinished(defaultWidth, defaultHeight);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function copyToClipboard(text: String) {
 | 
						||
		var textArea = Browser.document.createElement("textarea");
 | 
						||
		untyped textArea.value = text;
 | 
						||
		textArea.style.top = "0";
 | 
						||
		textArea.style.left = "0";
 | 
						||
		textArea.style.position = "fixed";
 | 
						||
		Browser.document.body.appendChild(textArea);
 | 
						||
		textArea.focus();
 | 
						||
		untyped textArea.select();
 | 
						||
		try {
 | 
						||
			Browser.document.execCommand("copy");
 | 
						||
		}
 | 
						||
		catch (err) {}
 | 
						||
		Browser.document.body.removeChild(textArea);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getMouse(num: Int): Mouse {
 | 
						||
		if (num != 0)
 | 
						||
			return null;
 | 
						||
		return mouse;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getKeyboard(num: Int): Keyboard {
 | 
						||
		if (num != 0)
 | 
						||
			return null;
 | 
						||
		return keyboard;
 | 
						||
	}
 | 
						||
 | 
						||
	static function checkGamepad(pad: js.html.Gamepad) {
 | 
						||
		for (i in 0...pad.axes.length) {
 | 
						||
			if (pad.axes[i] != null) {
 | 
						||
				var axis = pad.axes[i];
 | 
						||
				if (gamepadStates[pad.index].axes[i] != axis) {
 | 
						||
					gamepadStates[pad.index].axes[i] = axis;
 | 
						||
					gamepads[pad.index].sendAxisEvent(i, axis);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
		for (i in 0...pad.buttons.length) {
 | 
						||
			if (pad.buttons[i] != null) {
 | 
						||
				if (gamepadStates[pad.index].buttons[i] != pad.buttons[i].value) {
 | 
						||
					gamepadStates[pad.index].buttons[i] = pad.buttons[i].value;
 | 
						||
					gamepads[pad.index].sendButtonEvent(i, pad.buttons[i].value);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
		if (pad.axes.length <= 4 && pad.buttons.length > 7) {
 | 
						||
			// Fix for the triggers not being axis in html5
 | 
						||
			gamepadStates[pad.index].axes[4] = pad.buttons[6].value;
 | 
						||
			gamepads[pad.index].sendAxisEvent(4, pad.buttons[6].value);
 | 
						||
			gamepadStates[pad.index].axes[5] = pad.buttons[7].value;
 | 
						||
			gamepads[pad.index].sendAxisEvent(5, pad.buttons[7].value);
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	static function getCanvasElement(): CanvasElement {
 | 
						||
		if (khanvas != null)
 | 
						||
			return khanvas;
 | 
						||
		// Only consider custom canvas ID for release builds
 | 
						||
		#if (kha_debug_html5 || !canvas_id)
 | 
						||
		return cast Browser.document.getElementById("khanvas");
 | 
						||
		#else
 | 
						||
		return cast Browser.document.getElementById(Macros.canvasId());
 | 
						||
		#end
 | 
						||
	}
 | 
						||
 | 
						||
	static function loadFinished(defaultWidth: Int, defaultHeight: Int) {
 | 
						||
		var canvas: CanvasElement = getCanvasElement();
 | 
						||
		canvas.style.cursor = "default";
 | 
						||
 | 
						||
		var gl: Bool = false;
 | 
						||
 | 
						||
		#if kha_webgl
 | 
						||
		try {
 | 
						||
			SystemImpl.gl = canvas.getContext("webgl2",
 | 
						||
				{
 | 
						||
					alpha: false,
 | 
						||
					antialias: options.framebuffer.samplesPerPixel > 1,
 | 
						||
					stencil: true
 | 
						||
				}); // preserveDrawingBuffer: true } ); Warning: preserveDrawingBuffer can cause huge performance issues on mobile browsers
 | 
						||
			SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
 | 
						||
 | 
						||
			halfFloat = {HALF_FLOAT_OES: 0x140B}; // GL_HALF_FLOAT
 | 
						||
			depthTexture = {UNSIGNED_INT_24_8_WEBGL: 0x84FA}; // GL_UNSIGNED_INT_24_8
 | 
						||
			drawBuffers = {COLOR_ATTACHMENT0_WEBGL: GL.COLOR_ATTACHMENT0};
 | 
						||
			elementIndexUint = true;
 | 
						||
			SystemImpl.gl.getExtension("EXT_color_buffer_float");
 | 
						||
			SystemImpl.gl.getExtension("OES_texture_float_linear");
 | 
						||
			SystemImpl.gl.getExtension("OES_texture_half_float_linear");
 | 
						||
			anisotropicFilter = SystemImpl.gl.getExtension("EXT_texture_filter_anisotropic");
 | 
						||
			if (anisotropicFilter == null)
 | 
						||
				anisotropicFilter = SystemImpl.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic");
 | 
						||
 | 
						||
			gl = true;
 | 
						||
			gl2 = true;
 | 
						||
			Shaders.init();
 | 
						||
		}
 | 
						||
		catch (e:Dynamic) {
 | 
						||
			trace("Could not initialize WebGL 2, falling back to WebGL.");
 | 
						||
		}
 | 
						||
 | 
						||
		if (!gl2) {
 | 
						||
			try {
 | 
						||
				SystemImpl.gl = canvas.getContext("experimental-webgl",
 | 
						||
					{
 | 
						||
						alpha: false,
 | 
						||
						antialias: options.framebuffer.samplesPerPixel > 1,
 | 
						||
						stencil: true
 | 
						||
					}); // preserveDrawingBuffer: true } ); WARNING: preserveDrawingBuffer causes huge performance issues (on mobile browser)!
 | 
						||
				SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
 | 
						||
				SystemImpl.gl.getExtension("OES_texture_float");
 | 
						||
				SystemImpl.gl.getExtension("OES_texture_float_linear");
 | 
						||
				halfFloat = SystemImpl.gl.getExtension("OES_texture_half_float");
 | 
						||
				SystemImpl.gl.getExtension("OES_texture_half_float_linear");
 | 
						||
				depthTexture = SystemImpl.gl.getExtension("WEBGL_depth_texture");
 | 
						||
				SystemImpl.gl.getExtension("EXT_shader_texture_lod");
 | 
						||
				SystemImpl.gl.getExtension("OES_standard_derivatives");
 | 
						||
				anisotropicFilter = SystemImpl.gl.getExtension("EXT_texture_filter_anisotropic");
 | 
						||
				if (anisotropicFilter == null)
 | 
						||
					anisotropicFilter = SystemImpl.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic");
 | 
						||
				drawBuffers = SystemImpl.gl.getExtension("WEBGL_draw_buffers");
 | 
						||
				elementIndexUint = SystemImpl.gl.getExtension("OES_element_index_uint");
 | 
						||
				gl = true;
 | 
						||
				Shaders.init();
 | 
						||
			}
 | 
						||
			catch (e:Dynamic) {
 | 
						||
				trace("Could not initialize WebGL, falling back to <canvas>.");
 | 
						||
			}
 | 
						||
		}
 | 
						||
		#end
 | 
						||
 | 
						||
		setCanvas(canvas);
 | 
						||
		window = new Window(0, defaultWidth, defaultHeight, canvas);
 | 
						||
 | 
						||
		// var widthTransform: Float = canvas.width / Loader.the.width;
 | 
						||
		// var heightTransform: Float = canvas.height / Loader.the.height;
 | 
						||
		// var transform: Float = Math.min(widthTransform, heightTransform);
 | 
						||
		if (gl) {
 | 
						||
			var g4 = new kha.js.graphics4.Graphics();
 | 
						||
			frame = new Framebuffer(0, null, null, g4);
 | 
						||
			frame.init(new kha.graphics2.Graphics1(frame), new kha.js.graphics4.Graphics2(frame), g4); // new kha.graphics1.Graphics4(frame));
 | 
						||
		}
 | 
						||
		else {
 | 
						||
			Syntax.code("kha_js_Font.Kravur = kha_Kravur; kha_Kravur = kha_js_Font");
 | 
						||
			var g2 = new CanvasGraphics(canvas.getContext("2d"));
 | 
						||
			frame = new Framebuffer(0, null, g2, null);
 | 
						||
			frame.init(new kha.graphics2.Graphics1(frame), g2, null);
 | 
						||
		}
 | 
						||
		// canvas.getContext("2d").scale(transform, transform);
 | 
						||
 | 
						||
		if (!mobile && kha.audio2.Audio._init()) {
 | 
						||
			SystemImpl._hasWebAudio = true;
 | 
						||
			kha.audio2.Audio1._init();
 | 
						||
		}
 | 
						||
		else if (mobile) {
 | 
						||
			SystemImpl._hasWebAudio = false;
 | 
						||
			MobileWebAudio._init();
 | 
						||
			Syntax.code("kha_audio2_Audio1 = kha_js_MobileWebAudio");
 | 
						||
		}
 | 
						||
		else {
 | 
						||
			SystemImpl._hasWebAudio = false;
 | 
						||
			Syntax.code("kha_audio2_Audio1 = kha_js_AudioElementAudio");
 | 
						||
		}
 | 
						||
 | 
						||
		kha.vr.VrInterface.instance = new VrInterface();
 | 
						||
 | 
						||
		// Autofocus
 | 
						||
		canvas.focus();
 | 
						||
 | 
						||
		#if kha_disable_context_menu
 | 
						||
		canvas.oncontextmenu = function(event: Dynamic) {
 | 
						||
			event.stopPropagation();
 | 
						||
			event.preventDefault();
 | 
						||
		}
 | 
						||
		#end
 | 
						||
 | 
						||
		canvas.onmousedown = mouseDown;
 | 
						||
		canvas.onmousemove = mouseMove;
 | 
						||
		if (keyboard != null) {
 | 
						||
			canvas.onkeydown = keyDown;
 | 
						||
			canvas.onkeyup = keyUp;
 | 
						||
			canvas.onkeypress = keyPress;
 | 
						||
		}
 | 
						||
		canvas.onblur = onBlur;
 | 
						||
		canvas.onfocus = onFocus;
 | 
						||
		canvas.onmouseleave = mouseLeave;
 | 
						||
 | 
						||
		// IE11 does not have canvas.onwheel
 | 
						||
		canvas.addEventListener("wheel", mouseWheel, false);
 | 
						||
		canvas.addEventListener("touchstart", touchDown, false);
 | 
						||
		canvas.addEventListener("touchend", touchUp, false);
 | 
						||
		canvas.addEventListener("touchmove", touchMove, false);
 | 
						||
		canvas.addEventListener("touchcancel", touchCancel, false);
 | 
						||
 | 
						||
		Browser.document.addEventListener("dragover", function(event) {
 | 
						||
			event.preventDefault();
 | 
						||
		});
 | 
						||
 | 
						||
		Browser.document.addEventListener("drop", function(event: js.html.DragEvent) {
 | 
						||
			event.preventDefault();
 | 
						||
			if (event.dataTransfer != null && event.dataTransfer.files != null) {
 | 
						||
				for (file in event.dataTransfer.files) {
 | 
						||
					LoaderImpl.dropFiles.set(file.name, file);
 | 
						||
					System.dropFiles("drop://" + file.name);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		});
 | 
						||
 | 
						||
		Browser.window.addEventListener("unload", function() {
 | 
						||
			System.shutdown();
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	static var lastCanvasClientWidth: Int = -1;
 | 
						||
	static var lastCanvasClientHeight: Int = -1;
 | 
						||
 | 
						||
	static function initAnimate(callback: Window->Void) {
 | 
						||
		var canvas: CanvasElement = getCanvasElement();
 | 
						||
 | 
						||
		var window: Dynamic = Browser.window;
 | 
						||
		var requestAnimationFrame = window.requestAnimationFrame;
 | 
						||
		if (requestAnimationFrame == null)
 | 
						||
			requestAnimationFrame = window.mozRequestAnimationFrame;
 | 
						||
		if (requestAnimationFrame == null)
 | 
						||
			requestAnimationFrame = window.webkitRequestAnimationFrame;
 | 
						||
		if (requestAnimationFrame == null)
 | 
						||
			requestAnimationFrame = window.msRequestAnimationFrame;
 | 
						||
 | 
						||
		var isRefreshRateDetectionActive = false;
 | 
						||
		var lastTimestamp = 0.0;
 | 
						||
		final possibleRefreshRates = [30, 60, 75, 90, 120, 144, 240, 340, 360];
 | 
						||
		final refreshRatesCounts = [
 | 
						||
			for (_ in 0...possibleRefreshRates.length)
 | 
						||
				0
 | 
						||
		];
 | 
						||
 | 
						||
		function animate(timestamp) {
 | 
						||
			if (requestAnimationFrame == null)
 | 
						||
				Browser.window.setTimeout(animate, 1000.0 / 60.0);
 | 
						||
			else
 | 
						||
				requestAnimationFrame(animate);
 | 
						||
 | 
						||
			var sysGamepads = getGamepads();
 | 
						||
			if (sysGamepads != null) {
 | 
						||
				for (i in 0...sysGamepads.length) {
 | 
						||
					var pad = sysGamepads[i];
 | 
						||
					if (pad != null) {
 | 
						||
						checkGamepad(pad);
 | 
						||
					}
 | 
						||
				}
 | 
						||
			}
 | 
						||
 | 
						||
			Scheduler.executeFrame();
 | 
						||
 | 
						||
			if (canvas.getContext != null) {
 | 
						||
				#if !kha_html5_disable_automatic_size_adjust
 | 
						||
				if (lastCanvasClientWidth != canvas.clientWidth || lastCanvasClientHeight != canvas.clientHeight) {
 | 
						||
					// canvas.width is the actual backbuffer-size.
 | 
						||
					// canvas.clientWidth (which is read-only and equivalent to
 | 
						||
					// canvas.style.width in pixels) is the output-size
 | 
						||
					// and by default gets scaled by devicePixelRatio.
 | 
						||
					// We revert that scale so backbuffer and output-size
 | 
						||
					// are the same.
 | 
						||
 | 
						||
					var scale = Browser.window.devicePixelRatio;
 | 
						||
					var clientWidth = canvas.clientWidth;
 | 
						||
					var clientHeight = canvas.clientHeight;
 | 
						||
					canvas.width = clientWidth;
 | 
						||
					canvas.height = clientHeight;
 | 
						||
					if (scale != 1) {
 | 
						||
						canvas.style.width = Std.int(clientWidth / scale) + "px";
 | 
						||
						canvas.style.height = Std.int(clientHeight / scale) + "px";
 | 
						||
					}
 | 
						||
					lastCanvasClientWidth = canvas.clientWidth;
 | 
						||
					lastCanvasClientHeight = canvas.clientHeight;
 | 
						||
				}
 | 
						||
				#end
 | 
						||
 | 
						||
				System.render([frame]);
 | 
						||
				if (ie && SystemImpl.gl != null) {
 | 
						||
					// Clear alpha for IE11
 | 
						||
					SystemImpl.gl.clearColor(1, 1, 1, 1);
 | 
						||
					SystemImpl.gl.colorMask(false, false, false, true);
 | 
						||
					SystemImpl.gl.clear(GL.COLOR_BUFFER_BIT);
 | 
						||
					SystemImpl.gl.colorMask(true, true, true, true);
 | 
						||
				}
 | 
						||
			}
 | 
						||
			if (!isRefreshRateDetectionActive)
 | 
						||
				return;
 | 
						||
			if (lastTimestamp == 0) {
 | 
						||
				lastTimestamp = timestamp;
 | 
						||
				return;
 | 
						||
			}
 | 
						||
			final fps = Math.floor(1000 / (timestamp - lastTimestamp));
 | 
						||
			if (estimatedRefreshRate < fps)
 | 
						||
				estimatedRefreshRate = fps;
 | 
						||
			lastTimestamp = timestamp;
 | 
						||
			for (i => rate in possibleRefreshRates) {
 | 
						||
				if (fps > rate - 3 && fps < rate + 3)
 | 
						||
					refreshRatesCounts[i]++;
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		Browser.window.setTimeout(() -> {
 | 
						||
			isRefreshRateDetectionActive = true;
 | 
						||
			Browser.window.setTimeout(() -> {
 | 
						||
				isRefreshRateDetectionActive = false;
 | 
						||
				// Use 60 hz if window was out of focus and throttled to 1 fps
 | 
						||
				var index = possibleRefreshRates.indexOf(60);
 | 
						||
				var max = 0;
 | 
						||
				for (i => count in refreshRatesCounts) {
 | 
						||
					if (count > max) {
 | 
						||
						max = count;
 | 
						||
						index = i;
 | 
						||
					}
 | 
						||
				}
 | 
						||
				estimatedRefreshRate = possibleRefreshRates[index];
 | 
						||
			}, 1000);
 | 
						||
		}, 500);
 | 
						||
 | 
						||
		Scheduler.start();
 | 
						||
		requestAnimationFrame(animate);
 | 
						||
		callback(SystemImpl.window);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function lockMouse(): Void {
 | 
						||
		untyped if (SystemImpl.khanvas.requestPointerLock) {
 | 
						||
			SystemImpl.khanvas.requestPointerLock();
 | 
						||
		}
 | 
						||
		else if (SystemImpl.khanvas.mozRequestPointerLock) {
 | 
						||
			SystemImpl.khanvas.mozRequestPointerLock();
 | 
						||
		}
 | 
						||
		else if (SystemImpl.khanvas.webkitRequestPointerLock) {
 | 
						||
			SystemImpl.khanvas.webkitRequestPointerLock();
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	public static function unlockMouse(): Void {
 | 
						||
		untyped if (document.exitPointerLock) {
 | 
						||
			document.exitPointerLock();
 | 
						||
		}
 | 
						||
		else if (document.mozExitPointerLock) {
 | 
						||
			document.mozExitPointerLock();
 | 
						||
		}
 | 
						||
		else if (document.webkitExitPointerLock) {
 | 
						||
			document.webkitExitPointerLock();
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	public static function canLockMouse(): Bool {
 | 
						||
		return Syntax.code("'pointerLockElement' in document ||
 | 
						||
		'mozPointerLockElement' in document ||
 | 
						||
		'webkitPointerLockElement' in document");
 | 
						||
	}
 | 
						||
 | 
						||
	public static function isMouseLocked(): Bool {
 | 
						||
		return Syntax.code("document.pointerLockElement === kha_SystemImpl.khanvas ||
 | 
						||
			document.mozPointerLockElement === kha_SystemImpl.khanvas ||
 | 
						||
			document.webkitPointerLockElement === kha_SystemImpl.khanvas");
 | 
						||
	}
 | 
						||
 | 
						||
	public static function notifyOfMouseLockChange(func: Void->Void, error: Void->Void): Void {
 | 
						||
		js.Browser.document.addEventListener("pointerlockchange", func, false);
 | 
						||
		js.Browser.document.addEventListener("mozpointerlockchange", func, false);
 | 
						||
		js.Browser.document.addEventListener("webkitpointerlockchange", func, false);
 | 
						||
 | 
						||
		js.Browser.document.addEventListener("pointerlockerror", error, false);
 | 
						||
		js.Browser.document.addEventListener("mozpointerlockerror", error, false);
 | 
						||
		js.Browser.document.addEventListener("webkitpointerlockerror", error, false);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function removeFromMouseLockChange(func: Void->Void, error: Void->Void): Void {
 | 
						||
		js.Browser.document.removeEventListener("pointerlockchange", func, false);
 | 
						||
		js.Browser.document.removeEventListener("mozpointerlockchange", func, false);
 | 
						||
		js.Browser.document.removeEventListener("webkitpointerlockchange", func, false);
 | 
						||
 | 
						||
		js.Browser.document.removeEventListener("pointerlockerror", error, false);
 | 
						||
		js.Browser.document.removeEventListener("mozpointerlockerror", error, false);
 | 
						||
		js.Browser.document.removeEventListener("webkitpointerlockerror", error, false);
 | 
						||
	}
 | 
						||
 | 
						||
	static function setMouseXY(event: MouseEvent): Void {
 | 
						||
		var rect = SystemImpl.khanvas.getBoundingClientRect();
 | 
						||
		var borderWidth = SystemImpl.khanvas.clientLeft;
 | 
						||
		var borderHeight = SystemImpl.khanvas.clientTop;
 | 
						||
		mouseX = Std.int((event.clientX - rect.left - borderWidth) * SystemImpl.khanvas.width / (rect.width - 2 * borderWidth));
 | 
						||
		mouseY = Std.int((event.clientY - rect.top - borderHeight) * SystemImpl.khanvas.height / (rect.height - 2 * borderHeight));
 | 
						||
	}
 | 
						||
 | 
						||
	static var iosSoundEnabled: Bool = false;
 | 
						||
 | 
						||
	static function unlockiOSSound(): Void {
 | 
						||
		if (!ios || iosSoundEnabled)
 | 
						||
			return;
 | 
						||
 | 
						||
		var buffer = MobileWebAudio._context.createBuffer(1, 1, 22050);
 | 
						||
		var source = MobileWebAudio._context.createBufferSource();
 | 
						||
		source.buffer = buffer;
 | 
						||
		source.connect(MobileWebAudio._context.destination);
 | 
						||
		// untyped(if (source.noteOn) source.noteOn(0));
 | 
						||
		source.start();
 | 
						||
		source.stop();
 | 
						||
 | 
						||
		iosSoundEnabled = true;
 | 
						||
	}
 | 
						||
 | 
						||
	static var soundEnabled = false;
 | 
						||
 | 
						||
	static function unlockSound(): Void {
 | 
						||
		if (!soundEnabled) {
 | 
						||
			var context = kha.audio2.Audio._context;
 | 
						||
 | 
						||
			if (context == null) {
 | 
						||
				context = Syntax.code("kha_audio2_Audio1._context");
 | 
						||
			}
 | 
						||
 | 
						||
			if (context != null) {
 | 
						||
				context.resume().then(function(c) {
 | 
						||
					soundEnabled = true;
 | 
						||
				}).catchError(function(err) {
 | 
						||
					trace(err);
 | 
						||
				});
 | 
						||
			}
 | 
						||
 | 
						||
			kha.audio2.Audio.wakeChannels();
 | 
						||
		}
 | 
						||
		unlockiOSSound();
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseLeave(): Void {
 | 
						||
		mouse.sendLeaveEvent(0);
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseWheel(event: WheelEvent): Void {
 | 
						||
		unlockSound();
 | 
						||
		insideInputEvent = true;
 | 
						||
		activeWheelEvent = event;
 | 
						||
 | 
						||
		switch (Mouse.wheelEventBlockBehavior) {
 | 
						||
			case Full:
 | 
						||
				event.preventDefault();
 | 
						||
			case Custom(func):
 | 
						||
				if (func(event))
 | 
						||
					event.preventDefault();
 | 
						||
			case None:
 | 
						||
		}
 | 
						||
 | 
						||
		// Deltamode == 0, deltaY is in pixels.
 | 
						||
		if (event.deltaMode == 0) {
 | 
						||
			if (event.deltaY < 0) {
 | 
						||
				mouse.sendWheelEvent(0, -1);
 | 
						||
			}
 | 
						||
			else if (event.deltaY > 0) {
 | 
						||
				mouse.sendWheelEvent(0, 1);
 | 
						||
			}
 | 
						||
			activeWheelEvent = null;
 | 
						||
			insideInputEvent = false;
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		// Lines
 | 
						||
		if (event.deltaMode == 1) {
 | 
						||
			minimumScroll = Std.int(Math.min(minimumScroll, Math.abs(event.deltaY)));
 | 
						||
			mouse.sendWheelEvent(0, Std.int(event.deltaY / minimumScroll));
 | 
						||
			activeWheelEvent = null;
 | 
						||
			insideInputEvent = false;
 | 
						||
			return;
 | 
						||
		}
 | 
						||
		activeWheelEvent = null;
 | 
						||
		insideInputEvent = false;
 | 
						||
		return;
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseDown(event: MouseEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		activeMouseEvent = event;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		setMouseXY(event);
 | 
						||
		if (event.which == 1) { // left button
 | 
						||
			mouse.sendDownEvent(0, 0, mouseX, mouseY);
 | 
						||
 | 
						||
			khanvas.ownerDocument.addEventListener("mousemove", documentMouseMove, true);
 | 
						||
			khanvas.ownerDocument.addEventListener("mouseup", mouseLeftUp);
 | 
						||
		}
 | 
						||
		else if (event.which == 2) { // middle button
 | 
						||
			mouse.sendDownEvent(0, 2, mouseX, mouseY);
 | 
						||
			khanvas.ownerDocument.addEventListener("mouseup", mouseMiddleUp);
 | 
						||
		}
 | 
						||
		else if (event.which == 3) { // right button
 | 
						||
			mouse.sendDownEvent(0, 1, mouseX, mouseY);
 | 
						||
			khanvas.ownerDocument.addEventListener("mouseup", mouseRightUp);
 | 
						||
		}
 | 
						||
		else if (event.which == 4) { // backwards sidebutton
 | 
						||
			mouse.sendDownEvent(0, 3, mouseX, mouseY);
 | 
						||
			khanvas.ownerDocument.addEventListener("mouseup", mouseBackUp);
 | 
						||
		}
 | 
						||
		else if (event.which == 5) { // forwards sidebutton
 | 
						||
			mouse.sendDownEvent(0, 4, mouseX, mouseY);
 | 
						||
			khanvas.ownerDocument.addEventListener("mouseup", mouseForwardUp);
 | 
						||
		}
 | 
						||
		activeMouseEvent = null;
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseLeftUp(event: MouseEvent): Void {
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		if (event.which != 1)
 | 
						||
			return;
 | 
						||
 | 
						||
		insideInputEvent = true;
 | 
						||
		khanvas.ownerDocument.removeEventListener("mouseup", mouseLeftUp);
 | 
						||
		khanvas.ownerDocument.removeEventListener("mousemove", documentMouseMove, true);
 | 
						||
 | 
						||
		mouse.sendUpEvent(0, 0, mouseX, mouseY);
 | 
						||
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseMiddleUp(event: MouseEvent): Void {
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		if (event.which != 2)
 | 
						||
			return;
 | 
						||
 | 
						||
		insideInputEvent = true;
 | 
						||
		khanvas.ownerDocument.removeEventListener("mouseup", mouseMiddleUp);
 | 
						||
		mouse.sendUpEvent(0, 2, mouseX, mouseY);
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseRightUp(event: MouseEvent): Void {
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		if (event.which != 3)
 | 
						||
			return;
 | 
						||
 | 
						||
		insideInputEvent = true;
 | 
						||
		khanvas.ownerDocument.removeEventListener("mouseup", mouseRightUp);
 | 
						||
		mouse.sendUpEvent(0, 1, mouseX, mouseY);
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseBackUp(event: MouseEvent): Void {
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		if (event.which != 4)
 | 
						||
			return;
 | 
						||
 | 
						||
		insideInputEvent = true;
 | 
						||
		khanvas.ownerDocument.removeEventListener("mouseup", mouseBackUp);
 | 
						||
		mouse.sendUpEvent(0, 3, mouseX, mouseY);
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseForwardUp(event: MouseEvent): Void {
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		if (event.which != 5)
 | 
						||
			return;
 | 
						||
 | 
						||
		insideInputEvent = true;
 | 
						||
		khanvas.ownerDocument.removeEventListener("mouseup", mouseForwardUp);
 | 
						||
		mouse.sendUpEvent(0, 4, mouseX, mouseY);
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function documentMouseMove(event: MouseEvent): Void {
 | 
						||
		event.stopPropagation();
 | 
						||
		mouseMove(event);
 | 
						||
	}
 | 
						||
 | 
						||
	static function mouseMove(event: MouseEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		activeMouseEvent = event;
 | 
						||
 | 
						||
		var lastMouseX = mouseX;
 | 
						||
		var lastMouseY = mouseY;
 | 
						||
		setMouseXY(event);
 | 
						||
 | 
						||
		var movementX = event.movementX;
 | 
						||
		var movementY = event.movementY;
 | 
						||
 | 
						||
		if (event.movementX == null) {
 | 
						||
			movementX = (untyped event.mozMovementX != null) ? untyped event.mozMovementX : ((untyped event.webkitMovementX != null) ? untyped event.webkitMovementX : (mouseX
 | 
						||
				- lastMouseX));
 | 
						||
			movementY = (untyped event.mozMovementY != null) ? untyped event.mozMovementY : ((untyped event.webkitMovementY != null) ? untyped event.webkitMovementY : (mouseY
 | 
						||
				- lastMouseY));
 | 
						||
		}
 | 
						||
 | 
						||
		// this ensures same behaviour across browser until they fix it
 | 
						||
		if (firefox) {
 | 
						||
			movementX = Std.int(movementX * Browser.window.devicePixelRatio);
 | 
						||
			movementY = Std.int(movementY * Browser.window.devicePixelRatio);
 | 
						||
		}
 | 
						||
 | 
						||
		mouse.sendMoveEvent(0, mouseX, mouseY, movementX, movementY);
 | 
						||
		activeMouseEvent = null;
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function setTouchXY(touch: Touch): Void {
 | 
						||
		var rect = SystemImpl.khanvas.getBoundingClientRect();
 | 
						||
		var borderWidth = SystemImpl.khanvas.clientLeft;
 | 
						||
		var borderHeight = SystemImpl.khanvas.clientTop;
 | 
						||
		touchX = Std.int((touch.clientX - rect.left - borderWidth) * SystemImpl.khanvas.width / (rect.width - 2 * borderWidth));
 | 
						||
		touchY = Std.int((touch.clientY - rect.top - borderHeight) * SystemImpl.khanvas.height / (rect.height - 2 * borderHeight));
 | 
						||
	}
 | 
						||
 | 
						||
	static var iosTouchs: Array<Int> = [];
 | 
						||
 | 
						||
	static function touchDown(event: TouchEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		event.stopPropagation();
 | 
						||
 | 
						||
		switch (Surface.touchDownEventBlockBehavior) {
 | 
						||
			case Full:
 | 
						||
				event.preventDefault();
 | 
						||
			case Custom(func):
 | 
						||
				if (func(event))
 | 
						||
					event.preventDefault();
 | 
						||
			case None:
 | 
						||
		}
 | 
						||
 | 
						||
		var index = 0;
 | 
						||
		for (touch in event.changedTouches) {
 | 
						||
			var id = touch.identifier;
 | 
						||
			if (ios) {
 | 
						||
				id = iosTouchs.indexOf(-1);
 | 
						||
				if (id == -1)
 | 
						||
					id = iosTouchs.length;
 | 
						||
				iosTouchs[id] = touch.identifier;
 | 
						||
			}
 | 
						||
 | 
						||
			setTouchXY(touch);
 | 
						||
			mouse.sendDownEvent(0, 0, touchX, touchY);
 | 
						||
			surface.sendTouchStartEvent(id, touchX, touchY);
 | 
						||
			if (index == 0) {
 | 
						||
				lastFirstTouchX = touchX;
 | 
						||
				lastFirstTouchY = touchY;
 | 
						||
			}
 | 
						||
			index++;
 | 
						||
		}
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function touchUp(event: TouchEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		for (touch in event.changedTouches) {
 | 
						||
			var id = touch.identifier;
 | 
						||
			if (ios) {
 | 
						||
				id = iosTouchs.indexOf(id);
 | 
						||
				iosTouchs[id] = -1;
 | 
						||
			}
 | 
						||
 | 
						||
			setTouchXY(touch);
 | 
						||
			mouse.sendUpEvent(0, 0, touchX, touchY);
 | 
						||
			surface.sendTouchEndEvent(id, touchX, touchY);
 | 
						||
		}
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function touchMove(event: TouchEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		var index = 0;
 | 
						||
		for (touch in event.changedTouches) {
 | 
						||
			setTouchXY(touch);
 | 
						||
			if (index == 0) {
 | 
						||
				var movementX = touchX - lastFirstTouchX;
 | 
						||
				var movementY = touchY - lastFirstTouchY;
 | 
						||
				lastFirstTouchX = touchX;
 | 
						||
				lastFirstTouchY = touchY;
 | 
						||
 | 
						||
				mouse.sendMoveEvent(0, touchX, touchY, movementX, movementY);
 | 
						||
			}
 | 
						||
			var id = touch.identifier;
 | 
						||
			if (ios)
 | 
						||
				id = iosTouchs.indexOf(id);
 | 
						||
 | 
						||
			surface.sendMoveEvent(id, touchX, touchY);
 | 
						||
			index++;
 | 
						||
		}
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function touchCancel(event: TouchEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		for (touch in event.changedTouches) {
 | 
						||
			var id = touch.identifier;
 | 
						||
			if (ios)
 | 
						||
				id = iosTouchs.indexOf(id);
 | 
						||
 | 
						||
			setTouchXY(touch);
 | 
						||
			mouse.sendUpEvent(0, 0, touchX, touchY);
 | 
						||
			surface.sendTouchEndEvent(id, touchX, touchY);
 | 
						||
		}
 | 
						||
		iosTouchs = [];
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function onBlur() {
 | 
						||
		// System.pause();
 | 
						||
		System.background();
 | 
						||
	}
 | 
						||
 | 
						||
	static function onFocus() {
 | 
						||
		// System.resume();
 | 
						||
		System.foreground();
 | 
						||
	}
 | 
						||
 | 
						||
	static function keycodeToChar(key: String, keycode: Int, shift: Bool): String {
 | 
						||
		if (key != null) {
 | 
						||
			if (key.length == 1)
 | 
						||
				return key;
 | 
						||
			switch (key) {
 | 
						||
				case "Add":
 | 
						||
					return "+";
 | 
						||
				case "Subtract":
 | 
						||
					return "-";
 | 
						||
				case "Multiply":
 | 
						||
					return "*";
 | 
						||
				case "Divide":
 | 
						||
					return "/";
 | 
						||
			}
 | 
						||
		}
 | 
						||
		switch (keycode) {
 | 
						||
			case 187:
 | 
						||
				if (shift)
 | 
						||
					return "*";
 | 
						||
				else
 | 
						||
					return "+";
 | 
						||
			case 188:
 | 
						||
				if (shift)
 | 
						||
					return ";";
 | 
						||
				else
 | 
						||
					return ",";
 | 
						||
			case 189:
 | 
						||
				if (shift)
 | 
						||
					return "_";
 | 
						||
				else
 | 
						||
					return "-";
 | 
						||
			case 190:
 | 
						||
				if (shift)
 | 
						||
					return ":";
 | 
						||
				else
 | 
						||
					return ".";
 | 
						||
			case 191:
 | 
						||
				if (shift)
 | 
						||
					return "'";
 | 
						||
				else
 | 
						||
					return "#";
 | 
						||
			case 226:
 | 
						||
				if (shift)
 | 
						||
					return ">";
 | 
						||
				else
 | 
						||
					return "<";
 | 
						||
			case 106:
 | 
						||
				return "*";
 | 
						||
			case 107:
 | 
						||
				return "+";
 | 
						||
			case 109:
 | 
						||
				return "-";
 | 
						||
			case 111:
 | 
						||
				return "/";
 | 
						||
			case 49:
 | 
						||
				if (shift)
 | 
						||
					return "!";
 | 
						||
				else
 | 
						||
					return "1";
 | 
						||
			case 50:
 | 
						||
				if (shift)
 | 
						||
					return "\"";
 | 
						||
				else
 | 
						||
					return "2";
 | 
						||
			case 51:
 | 
						||
				if (shift)
 | 
						||
					return "§";
 | 
						||
				else
 | 
						||
					return "3";
 | 
						||
			case 52:
 | 
						||
				if (shift)
 | 
						||
					return "$";
 | 
						||
				else
 | 
						||
					return "4";
 | 
						||
			case 53:
 | 
						||
				if (shift)
 | 
						||
					return "%";
 | 
						||
				else
 | 
						||
					return "5";
 | 
						||
			case 54:
 | 
						||
				if (shift)
 | 
						||
					return "&";
 | 
						||
				else
 | 
						||
					return "6";
 | 
						||
			case 55:
 | 
						||
				if (shift)
 | 
						||
					return "/";
 | 
						||
				else
 | 
						||
					return "7";
 | 
						||
			case 56:
 | 
						||
				if (shift)
 | 
						||
					return "(";
 | 
						||
				else
 | 
						||
					return "8";
 | 
						||
			case 57:
 | 
						||
				if (shift)
 | 
						||
					return ")";
 | 
						||
				else
 | 
						||
					return "9";
 | 
						||
			case 48:
 | 
						||
				if (shift)
 | 
						||
					return "=";
 | 
						||
				else
 | 
						||
					return "0";
 | 
						||
			case 219:
 | 
						||
				if (shift)
 | 
						||
					return "?";
 | 
						||
				else
 | 
						||
					return "ß";
 | 
						||
			case 212:
 | 
						||
				if (shift)
 | 
						||
					return "`";
 | 
						||
				else
 | 
						||
					return "´";
 | 
						||
		}
 | 
						||
		if (keycode >= 96 && keycode <= 105) { // num block
 | 
						||
			return String.fromCharCode("0".code - 96 + keycode);
 | 
						||
		}
 | 
						||
		if (keycode >= "A".code && keycode <= "Z".code) {
 | 
						||
			if (shift)
 | 
						||
				return String.fromCharCode(keycode);
 | 
						||
			else
 | 
						||
				return String.fromCharCode(keycode - "A".code + "a".code);
 | 
						||
		}
 | 
						||
		return String.fromCharCode(keycode);
 | 
						||
	}
 | 
						||
 | 
						||
	static function keyDown(event: KeyboardEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		activeKeyEvent = event;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		preventDefaultKeyBehavior(event);
 | 
						||
		event.stopPropagation();
 | 
						||
 | 
						||
		// prevent key repeat
 | 
						||
		if (event.repeat) {
 | 
						||
			event.preventDefault();
 | 
						||
			return;
 | 
						||
		}
 | 
						||
		var keyCode = fixedKeyCode(event);
 | 
						||
		keyboard.sendDownEvent(keyCode);
 | 
						||
		activeKeyEvent = null;
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function fixedKeyCode(event: KeyboardEvent): KeyCode {
 | 
						||
		return switch (event.keyCode) {
 | 
						||
			case 91, 93: Meta; // left/right in Chrome
 | 
						||
			case 186: Semicolon;
 | 
						||
			case 187: Equals;
 | 
						||
			case 189: HyphenMinus;
 | 
						||
			default:
 | 
						||
				cast event.keyCode;
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	static function preventDefaultKeyBehavior(event: KeyboardEvent): Void {
 | 
						||
		switch (Keyboard.keyBehavior) {
 | 
						||
			case Default:
 | 
						||
				defaultKeyBlock(event);
 | 
						||
			case Full:
 | 
						||
				event.preventDefault();
 | 
						||
			case Custom(func):
 | 
						||
				if (func(cast event.keyCode))
 | 
						||
					event.preventDefault();
 | 
						||
			case None:
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	static function defaultKeyBlock(e: KeyboardEvent): Void {
 | 
						||
		// block if ctrl key pressed
 | 
						||
		if (e.ctrlKey || e.metaKey) {
 | 
						||
			// except for cut-copy-paste
 | 
						||
			if (e.keyCode == 67 || e.keyCode == 88 || e.keyCode == 86) {
 | 
						||
				return;
 | 
						||
			}
 | 
						||
			// and quit on macOS
 | 
						||
			if (e.metaKey && e.keyCode == 81) {
 | 
						||
				return;
 | 
						||
			}
 | 
						||
			e.preventDefault();
 | 
						||
			return;
 | 
						||
		}
 | 
						||
		// allow F-keys
 | 
						||
		if (e.keyCode >= 112 && e.keyCode <= 123)
 | 
						||
			return;
 | 
						||
		// allow char keys
 | 
						||
		if (e.key == null || e.key.length == 1)
 | 
						||
			return;
 | 
						||
		e.preventDefault();
 | 
						||
	}
 | 
						||
 | 
						||
	static function keyUp(event: KeyboardEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		activeKeyEvent = event;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		preventDefaultKeyBehavior(event);
 | 
						||
		event.stopPropagation();
 | 
						||
 | 
						||
		var keyCode = fixedKeyCode(event);
 | 
						||
		keyboard.sendUpEvent(keyCode);
 | 
						||
 | 
						||
		activeKeyEvent = null;
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	static function keyPress(event: KeyboardEvent): Void {
 | 
						||
		insideInputEvent = true;
 | 
						||
		activeKeyEvent = event;
 | 
						||
		unlockSound();
 | 
						||
 | 
						||
		if (event.which == 0)
 | 
						||
			return; // for Firefox and Safari
 | 
						||
		preventDefaultKeyBehavior(event);
 | 
						||
		event.stopPropagation();
 | 
						||
		keyboard.sendPressEvent(String.fromCharCode(event.which));
 | 
						||
 | 
						||
		activeKeyEvent = null;
 | 
						||
		insideInputEvent = false;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function canSwitchFullscreen(): Bool {
 | 
						||
		return Syntax.code("'fullscreenElement ' in document ||
 | 
						||
		'mozFullScreenElement' in document ||
 | 
						||
		'webkitFullscreenElement' in document ||
 | 
						||
		'msFullscreenElement' in document
 | 
						||
		");
 | 
						||
	}
 | 
						||
 | 
						||
	public static function notifyOfFullscreenChange(func: Void->Void, error: Void->Void): Void {
 | 
						||
		js.Browser.document.addEventListener("fullscreenchange", func, false);
 | 
						||
		js.Browser.document.addEventListener("mozfullscreenchange", func, false);
 | 
						||
		js.Browser.document.addEventListener("webkitfullscreenchange", func, false);
 | 
						||
		js.Browser.document.addEventListener("MSFullscreenChange", func, false);
 | 
						||
 | 
						||
		js.Browser.document.addEventListener("fullscreenerror", error, false);
 | 
						||
		js.Browser.document.addEventListener("mozfullscreenerror", error, false);
 | 
						||
		js.Browser.document.addEventListener("webkitfullscreenerror", error, false);
 | 
						||
		js.Browser.document.addEventListener("MSFullscreenError", error, false);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function removeFromFullscreenChange(func: Void->Void, error: Void->Void): Void {
 | 
						||
		js.Browser.document.removeEventListener("fullscreenchange", func, false);
 | 
						||
		js.Browser.document.removeEventListener("mozfullscreenchange", func, false);
 | 
						||
		js.Browser.document.removeEventListener("webkitfullscreenchange", func, false);
 | 
						||
		js.Browser.document.removeEventListener("MSFullscreenChange", func, false);
 | 
						||
 | 
						||
		js.Browser.document.removeEventListener("fullscreenerror", error, false);
 | 
						||
		js.Browser.document.removeEventListener("mozfullscreenerror", error, false);
 | 
						||
		js.Browser.document.removeEventListener("webkitfullscreenerror", error, false);
 | 
						||
		js.Browser.document.removeEventListener("MSFullscreenError", error, false);
 | 
						||
	}
 | 
						||
 | 
						||
	public static function setKeepScreenOn(on: Bool): Void {}
 | 
						||
 | 
						||
	public static function loadUrl(url: String): Void {
 | 
						||
		js.Browser.window.open(url, "_blank");
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getGamepadId(index: Int): String {
 | 
						||
		var sysGamepads = getGamepads();
 | 
						||
		if (sysGamepads != null && untyped sysGamepads[index]) {
 | 
						||
			return sysGamepads[index].id;
 | 
						||
		}
 | 
						||
 | 
						||
		return "unknown";
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getGamepadVendor(index: Int): String {
 | 
						||
		return "unknown";
 | 
						||
	}
 | 
						||
 | 
						||
	public static function setGamepadRumble(index: Int, leftAmount: Float, rightAmount: Float) {}
 | 
						||
 | 
						||
	static function getGamepads(): Array<js.html.Gamepad> {
 | 
						||
		if (chrome && kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsVrEnabled()) {
 | 
						||
			return null; // Chrome crashes if navigator.getGamepads() is called when using VR
 | 
						||
		}
 | 
						||
 | 
						||
		if (untyped navigator.getGamepads) {
 | 
						||
			return js.Browser.navigator.getGamepads();
 | 
						||
		}
 | 
						||
		else {
 | 
						||
			return null;
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	public static function getPen(num: Int): kha.input.Pen {
 | 
						||
		return null;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function safeZone(): Float {
 | 
						||
		return 1.0;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function login(): Void {}
 | 
						||
 | 
						||
	public static function automaticSafeZone(): Bool {
 | 
						||
		return true;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function setSafeZone(value: Float): Void {}
 | 
						||
 | 
						||
	public static function unlockAchievement(id: Int): Void {}
 | 
						||
 | 
						||
	public static function waitingForLogin(): Bool {
 | 
						||
		return false;
 | 
						||
	}
 | 
						||
 | 
						||
	public static function disallowUserChange(): Void {}
 | 
						||
 | 
						||
	public static function allowUserChange(): Void {}
 | 
						||
}
 |