Преглед изворни кода

Improve usability and style in web export presentation

 - Check for WebGL support, don't load if unsupported
 - Check for IndexedDB support
 - Make canvas support check message visible
 - Colored debug output for warnings and errors
 - Make it obvious status can be closed by clicking
 - Don't use status to display non-critical errors
 - Limit output message count
 - Add clear output button
 - Fix setting total memory
eska пре 8 година
родитељ
комит
1f7d4c4d0e

+ 1 - 1
platform/javascript/export/export.cpp

@@ -377,7 +377,7 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
 	logo->create_from_image(img);
 	logo->create_from_image(img);
 	max_memory=3;
 	max_memory=3;
 	html_title="";
 	html_title="";
-	html_font_family="arial,sans-serif";
+	html_font_family="'Droid Sans',arial,sans-serif";
 	html_controls_enabled=true;
 	html_controls_enabled=true;
 	pack_mode=PACK_SINGLE_FILE;
 	pack_mode=PACK_SINGLE_FILE;
 }
 }

+ 225 - 156
platform/javascript/godot_shell.html

@@ -3,14 +3,14 @@
 <head>
 <head>
 	<meta charset="utf-8" />
 	<meta charset="utf-8" />
 	<title>$GODOT_HEAD_TITLE</title>
 	<title>$GODOT_HEAD_TITLE</title>
-	$GODOT_HEAD_INCLUDE
+$GODOT_HEAD_INCLUDE
 	<style type="text/css">
 	<style type="text/css">
 		body {
 		body {
 			margin: 0;
 			margin: 0;
 			border: 0 none;
 			border: 0 none;
 			padding: 0;
 			padding: 0;
 			text-align: center;
 			text-align: center;
-			background-color: black;
+			background-color: #222226;
 			font-family: $GODOT_STYLE_FONT_FAMILY;
 			font-family: $GODOT_STYLE_FONT_FAMILY;
 		}
 		}
 
 
@@ -71,7 +71,7 @@
 			margin: 0;
 			margin: 0;
 			border: 0 none;
 			border: 0 none;
 			padding: 0;
 			padding: 0;
-			background-color: #111;
+			background-color: #0c0c0c;
 		}
 		}
 
 
 		#canvas {
 		#canvas {
@@ -81,6 +81,7 @@
 			 * calculate cursor coordinates correctly */
 			 * calculate cursor coordinates correctly */
 			border: 0 none;
 			border: 0 none;
 			padding: 0;
 			padding: 0;
+			color: white;
 		}
 		}
 
 
 
 
@@ -101,6 +102,8 @@
 		}
 		}
 
 
 		#status {
 		#status {
+			line-height: 1.3;
+			cursor: pointer;
 			visibility: visible;
 			visibility: visible;
 			padding: 4px 6px;
 			padding: 4px 6px;
 		}
 		}
@@ -123,7 +126,7 @@
 			-ms-user-select: none;
 			-ms-user-select: none;
 		}
 		}
 
 
-		#container:hover > #controls {
+		:hover > #controls {
 			opacity: 1.0;
 			opacity: 1.0;
 			transition: opacity 60ms ease-in-out;
 			transition: opacity 60ms ease-in-out;
 		}
 		}
@@ -135,223 +138,289 @@
 			margin-right: 2px;
 			margin-right: 2px;
 		}
 		}
 
 
+		#controls > label > input {
+			vertical-align: middle;
+		}
+
 		#controls > label > input[type="checkbox"] {
 		#controls > label > input[type="checkbox"] {
 			/* override user agent style */
 			/* override user agent style */
 			margin-left: 0;
 			margin-left: 0;
 		}
 		}
 
 
-		label > input {
-			vertical-align: middle;
-		}
-
-		#display-output { display: none; }
+		#output-toggle { display: none; }
 
 
 
 
 		/* Debug output
 		/* Debug output
 		 * ============ */
 		 * ============ */
 
 
-		#output {
+		#output-panel {
 			display: none;
 			display: none;
-			margin: 6px auto;
-			border: 2px groove grey;
-			padding: 4px;
-			outline: none;
+			max-width: $GODOT_CANVAS_WIDTHpx;
+			font-size: small;
+			margin: 6px auto 0;
+			padding: 0 4px 4px;
 			text-align: left;
 			text-align: left;
+			line-height: 2.2;
+		}
+
+		#output-header {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+		}
+
+		#output-container {
+			padding: 6px;
+			background-color: #2c2a32;
+			box-shadow: inset 0 0 1px 1px #232127;
+			color: #bbb;
+		}
+
+		#output-scroll {
+			line-height: 1;
+			height: 12em;
+			overflow-y: scroll;
 			white-space: pre-wrap;
 			white-space: pre-wrap;
 			font-size: small;
 			font-size: small;
-			color: #eee;
-			background-color: black;
 			font-family: "Lucida Console", Monaco, monospace;
 			font-family: "Lucida Console", Monaco, monospace;
 		}
 		}
 
 
 
 
-		/* Export style include
-		 * ==================== */
+/* Export style include
+ * ==================== */
+
+$GODOT_STYLE_INCLUDE
 
 
-		$GODOT_STYLE_INCLUDE
 	</style>
 	</style>
 </head>
 </head>
 <body>
 <body>
 	<div id="container">
 	<div id="container">
 		<canvas id="canvas" width="$GODOT_CANVAS_WIDTH" height="$GODOT_CANVAS_HEIGHT" onclick="canvas.ownerDocument.defaultView.focus();" oncontextmenu="event.preventDefault();">
 		<canvas id="canvas" width="$GODOT_CANVAS_WIDTH" height="$GODOT_CANVAS_HEIGHT" onclick="canvas.ownerDocument.defaultView.focus();" oncontextmenu="event.preventDefault();">
-			HTML5 canvas appears to be unsupported in the current browser.<br />Please try updating or use a different browser.
+			HTML5 canvas appears to be unsupported in the current browser.<br />
+			Please try updating or use a different browser.
 		</canvas>
 		</canvas>
 		<div id="status-container">
 		<div id="status-container">
-			<span id="status" class="godot" onclick="this.style.visibility='hidden';">Loading page...</span>
+			<span id="status" class="godot" onclick="this.style.visibility='hidden';">Downloading page...</span>
 		</div>
 		</div>
 		<div id="controls" class="godot">
 		<div id="controls" class="godot">
-			<label id="display-output"><input id="output-toggle" type="checkbox" autocomplete="off" onchange="Presentation.setOutputVisible(this.checked);" />display output</label>
+			<label id="output-toggle"><input type="checkbox" checked="checked" autocomplete="off" onchange="Presentation.setOutputVisible(this.checked);" />Display Output</label>
 			<!-- hidden until implemented
 			<!-- hidden until implemented
-			<label><input id="lock-cursor" type="checkbox" autocomplete="off" />lock cursor</label>
-			<label><input id="resize-canvas" type="checkbox" autocomplete="off" />resize canvas</label>
+			<label><input class="postRun-enable" type="checkbox" disabled="disabled" autocomplete="off" />lock cursor</label>
+			<label><input class="postRun-enable" type="checkbox" disabled="disabled" autocomplete="off"  onchange="Presentation.setCanvasMaximized(this.checked);" />maximize</label>
 			-->
 			-->
-			<button id="fullscreen" class="godot" type="button" disabled="disabled" autocomplete="off" onclick="Presentation.goFullscreen();">fullscreen</button>
+			<button id="fullscreen" class="godot postRun-enable" type="button" disabled="disabled" autocomplete="off" onclick="Presentation.requestFullscreen();">Fullscreen</button>
 		</div>
 		</div>
 	</div>
 	</div>
-	<!-- Firefox adds extra space to textarea, but shouldn't matter too much https://bugzilla.mozilla.org/show_bug.cgi?id=33654 -->
-	<textarea id="output" rows="10" cols="100" readonly="readonly" style="resize:none"></textarea>
+	<div id="output-panel" class="godot">
+		<div id="output-header">
+			Output:
+			<button class="godot" type="button" autocomplete="off" onclick="Presentation.clearOutput();">Clear</button>
+		</div>
+		<div id="output-container"><div id="output-scroll"></div></div>
+	</div>
 
 
+	<!-- Scripts -->
 	<script type="text/javascript">//<![CDATA[
 	<script type="text/javascript">//<![CDATA[
 		var Presentation = (function() {
 		var Presentation = (function() {
 			var statusElement = document.getElementById("status");
 			var statusElement = document.getElementById("status");
-			var outputElement = document.getElementById("output");
-			var doneLoading = false;
-
-			function onLoaded() {
-				doneLoading = true;
-				var fullscreenButtonElement = document.getElementById("fullscreen");
-				fullscreenButtonElement.disabled = false;
-			}
+			var canvasElement = document.getElementById("canvas");
 
 
 			var presentation = {
 			var presentation = {
-				statusElement: statusElement,
-				outputElement: outputElement,
-				setOutputVisible: function setOutputVisible(visible) {
-					outputElement.style.display = (visible?"block":"none");
+				postRun: [
+					function() {
+						var elements = document.getElementsByClassName("postRun-enable");
+						Array.prototype.slice.call(elements).forEach(function(element) {
+							element.disabled = false;
+						});
+					}
+				],
+				requestFullscreen: function requestFullscreen() {
+					if (typeof Module !== "undefined" && Module.requestFullscreen) {
+						Module.requestFullscreen(false, false);
+					}
+				},
+				/*
+				requestPointerlock: function requestPointerlock() {
+					if (typeof Module !== "undefined" && Module.requestPointerlock) {
+						Module.requestPointerlock(false, false);
+					}
 				},
 				},
+				setCanvasMaximized: function setCanvasMaximized(enabled) {
+					if (typeof Module !== "undefined" && Module.setCanvasMaximized) {
+						Module.setCanvasMaximized(enabled);
+					}
+				},
+				*/
 				setStatusVisible: function setStatusVisible(visible) {
 				setStatusVisible: function setStatusVisible(visible) {
 					statusElement.style.visibility = (visible?"visible":"hidden");
 					statusElement.style.visibility = (visible?"visible":"hidden");
 				},
 				},
 				setStatus: function setStatus(text) {
 				setStatus: function setStatus(text) {
-					if (!text || text.length === 0) {
-						Presentation.setStatusVisible(false);
-						onLoaded();
-					} else {
-						Presentation.setStatusVisible(true);
-						statusElement.innerHTML = text;
+					if (text.length === 0) {
+						// emscripten sets empty string as status after "Running..."
+						// per timeout, but another status may have been set by then
+						if (Presentation.setStatus.lastText === "Running...")
+							Presentation.setStatusVisible(false);
+						return;
 					}
 					}
+					Presentation.setStatus.lastText = text;
+					while (statusElement.lastChild) {
+						statusElement.removeChild(statusElement.lastChild);
+					}
+					var lines = text.split("\n");
+					lines.forEach(function(line, index) {
+						statusElement.appendChild(document.createTextNode(line));
+						statusElement.appendChild(document.createElement("br"));
+					});
+					var closeNote = document.createElement("span");
+					closeNote.style.fontSize = "small";
+					closeNote.textContent = "click to close";
+					statusElement.appendChild(closeNote);
+					Presentation.setStatusVisible(true);
+				},
+				isWebGLAvailable: function isWebGLAvailable() {
+					var context;
+					try {
+						context = canvasElement.getContext("webgl") || canvasElement.getContext("experimental-webgl");
+					} catch (e) {}
+					return !!context;
 				},
 				},
-				goFullscreen: function goFullscreen() {
-					if (doneLoading) Module.requestFullScreen(false, false);
-				}
 			};
 			};
 
 
+			window.onerror = function(event) { presentation.setStatus("Failure during start-up\nSee JavaScript console") };
+
 			if ($GODOT_CONTROLS_ENABLED) { // controls enabled
 			if ($GODOT_CONTROLS_ENABLED) { // controls enabled
-				(function() {
-					var controlsElement = document.getElementById("controls");
-					controlsElement.style.visibility="visible";
-				})();
+				document.getElementById("controls").style.visibility="visible";
 			}
 			}
 
 
 			if ($GODOT_DEBUG_ENABLED) { // debugging enabled
 			if ($GODOT_DEBUG_ENABLED) { // debugging enabled
-				(function() {
-					var outputToggleLabel = document.getElementById("display-output");
-					var outputToggle = document.getElementById("output-toggle");
-
-					outputElement.value = ""; // clear browser cache
-					outputElement.style.display = "block";
-					outputToggle.checked = true;
-					outputToggleLabel.style.display = "inline";
-
-					presentation.print = function print(text) {
-						if (outputElement.value.length !== 0)
-							outputElement.value += "\n";
-						outputElement.value += text;
-						outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
-					};
-				})();
+				var outputRoot = document.getElementById("output-panel");
+				var outputElement = document.getElementById("output-scroll");
+				var outputToggle = document.getElementById("output-toggle");
+				const maxOutputMessages = 400;
+
+				presentation.setOutputVisible = function setOutputVisible(visible) {
+					outputRoot.style.display = (visible?"block":"none");
+				};
+				presentation.clearOutput = function clearOutput() {
+					while (outputElement.firstChild) {
+						outputElement.firstChild.remove();
+					}
+				};
+
+				presentation.setOutputVisible(true);
+				outputToggle.style.display = "inline";
+
+				presentation.print = function print(text) {
+					if (arguments.length > 1) {
+						text = Array.prototype.slice.call(arguments).join(" ");
+					}
+					if (text.length <= 0) return;
+					while (outputElement.childElementCount >= maxOutputMessages) {
+						outputElement.firstChild.remove();
+					}
+					var msg = document.createElement("div");
+					if (text.trim().startsWith("**ERROR**")
+						|| text.startsWith("**EXCEPTION**")) {
+						msg.style.color = "#d44";
+					} else if (text.trim().startsWith("**WARNING**")) {
+						msg.style.color = "#ccc000";
+					} else if (text.trim().startsWith("**SCRIPT ERROR**")) {
+						msg.style.color = "#c6d";
+					}
+					msg.textContent = text;
+					var scrollToBottom = outputElement.scrollHeight - (outputElement.clientHeight + outputElement.scrollTop) < 10;
+					outputElement.appendChild(msg);
+					if (scrollToBottom) {
+						outputElement.scrollTop = outputElement.scrollHeight;
+					}
+				};
+
+				presentation.postRun.push(function() {
+					window.onerror = function(event) { presentation.print("**EXCEPTION**:", event) };
+				});
+
+			} else {
+				presentation.postRun.push(function() { window.onerror = null; });
 			}
 			}
 
 
 			return presentation;
 			return presentation;
 		})();
 		})();
 
 
 		// Emscripten interface
 		// Emscripten interface
-		var Module = (function() {
-			var print = (function() {
-				if (typeof Presentation.print === "function") {
-					return function print(text) {
-						if (arguments.length > 1)
-							text = Array.prototype.slice.call(arguments).join(" ");
-						console.log(text);
-						Presentation.print(text);
-					};
-				} else {
-					return function print(text) {
-						if (arguments.length > 1)
-							text = Array.prototype.slice.call(arguments).join(" ");
-						console.log(text);
-					};
+		var Module = {
+			TOTAL_MEMORY: $GODOT_TMEM,
+			postRun: (function() {
+				if (typeof Presentation !== "undefined" && Presentation.postRun instanceof Array) {
+					return Presentation.postRun;
 				}
 				}
-			})();
-
-			var canvas = (function() {
-				var canvasElement = document.getElementById("canvas");
-
+			})(),
+			print: function print(text) {
+				if (arguments.length > 1) {
+					text = Array.prototype.slice.call(arguments).join(" ");
+				}
+				console.log(text);
+				if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") {
+					Presentation.print(text);
+				}
+			},
+			printErr: function printErr(text) {
+				if (arguments.length > 1) {
+					text = Array.prototype.slice.call(arguments).join(" ");
+				}
+				console.error(text);
+				if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") {
+					Presentation.print("**ERROR**:", text)
+				}
+			},
+			canvas: (function() {
+				var canvas = document.getElementById("canvas");
 				// As a default initial behavior, pop up an alert when WebGL context is lost. To make your
 				// As a default initial behavior, pop up an alert when WebGL context is lost. To make your
 				// application robust, you may want to override this behavior before shipping!
 				// application robust, you may want to override this behavior before shipping!
 				// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
 				// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
-				canvasElement.addEventListener("webglcontextlost", function(e) { alert("WebGL context lost. Plase reload the page."); e.preventDefault(); }, false);
-
-				return canvasElement;
-			})();
-
-			var setStatus = (function() {
-				if (typeof Presentation.setStatus === "function")
-					return function setStatus(text) {
-						if (!Module.setStatus.last)
-							Module.setStatus.last = { time: Date.now(), text: "" };
-						if (text === Module.setStatus.text)
-							return;
-						var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
-						var now = Date.now();
-						if (m) {
-							if (now - Date.now() < 30) // if this is a progress update, skip it if too soon
-								return;
-							text = m[1];
-						}
-						Presentation.setStatus(text);
-					};
-				else
-					return function setStatus(text) {
-						if (!Module.setStatus.last)
-							Module.setStatus.last = { time: Date.now(), text: "" };
-						if (text === Module.setStatus.text)
-							return;
-						var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
-						var now = Date.now();
-						if (m) {
-							if (now - Date.now() < 30) // if this is a progress update, skip it if too soon
-								return;
-							text = m[1];
-						}
-					};
-			})();
-
-			return {
-				TOTAL_MEMORY: 268435456,
-				preRun: [],
-				postRun: [],
-				print: print,
-				printErr: function printErr(text) {
-					if (arguments.length > 1)
-						text = Array.prototype.slice.call(arguments).join(" ");
-					if (0) { // XXX disabled for safety `if (typeof dump == "function")`
-						dump(text + "\n"); // fast, straight to the real console
-					} else {
-						console.error(text);
-					}
-				},
-				canvas: canvas,
-				setStatus: setStatus,
-				totalDependencies: 0,
-				monitorRunDependencies: function monitorRunDependencies(left) {
-					this.totalDependencies = Math.max(this.totalDependencies, left);
-					Module.setStatus(left ? "Preparing... (" + (this.totalDependencies-left) + "/" + this.totalDependencies + ")" : "All downloads complete.");
+				canvas.addEventListener("webglcontextlost", function(e) { alert("WebGL context lost. Plase reload the page."); e.preventDefault(); }, false);
+				return canvas;
+
+			})(),
+			setStatus: function setStatus(text) {
+				var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+				var now = Date.now();
+				if (m) {
+					if (now - Date.now() < 30) // if this is a progress update, skip it if too soon
+						return;
+					text = m[1];
 				}
 				}
-			};
-		})();
+				if (typeof Presentation !== "undefined" && typeof Presentation.setStatus == "function") {
+					Presentation.setStatus(text);
+				}
+			}
+		};
 
 
-		Presentation.setStatus("Downloading...");
+		if (!Presentation.isWebGLAvailable()) {
+			Presentation.setStatus("WebGL appears to be unsupported in the current browser.\nPlease try updating or use a different browser.");
+			Presentation.preventLoading = true;
+		} else {
+			Presentation.setStatus("Downloading...");
+		}
 
 
-		window.onerror = function(event) {
-			// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
-			Module.setStatus("Exception thrown, see JavaScript console");
-			Module.setStatus = function(text) {
-				if (text) Module.printErr("[post-exception status] " + text);
-			};
-		};
+		if (Presentation.preventLoading) {
+			// prevent *fs.js and Emscripten's SCRIPT placeholder from loading any files
+			Presentation._XHR_send = XMLHttpRequest.prototype.send;
+			XMLHttpRequest.prototype.send = function() {};
+			Presentation._Node_appendChild = Node.prototype.appendChild;
+			Node.prototype.appendChild = function(node) {
+				if (!(node instanceof HTMLScriptElement)) {
+					return Presentation._Node_appendChild.call(this, node);
+				}
+			}
+		}
 	//]]></script>
 	//]]></script>
 	<script type="text/javascript" src="$GODOT_BASEfs.js"></script>
 	<script type="text/javascript" src="$GODOT_BASEfs.js"></script>
-	{{{ SCRIPT }}}
+{{{ SCRIPT }}}
+	<script type="text/javascript">
+		if (Presentation.preventLoading) {
+			XMLHttpRequest.prototype.send = Presentation._XHR_send;
+			Node.prototype.appendChild = Presentation._Node_appendChild;
+		}
+	</script>
 </body>
 </body>
 </html>
 </html>

+ 20 - 22
platform/javascript/javascript_main.cpp

@@ -140,10 +140,9 @@ static void _godot_draw(void) {
 
 
 extern "C" {
 extern "C" {
 
 
-void main_after_fs_sync(int value) {
+void main_after_fs_sync() {
 
 
 	start_step=1;
 	start_step=1;
-	printf("FS SYNCHED!\n");
 }
 }
 
 
 }
 }
@@ -178,26 +177,25 @@ int main(int argc, char *argv[]) {
 	glutDisplayFunc(_godot_draw);
 	glutDisplayFunc(_godot_draw);
    //glutSpecialFunc(gears_special);
    //glutSpecialFunc(gears_special);
 
 
-
-
-	 //mount persistent filesystem
-	 EM_ASM(
-		 FS.mkdir('/userfs');
-		 FS.mount(IDBFS, {}, '/userfs');
-
-
-
-		 // sync from persisted state into memory and then
-		 // run the 'test' function
-		 FS.syncfs(true, function (err) {
-			 assert(!err);
-			 console.log("done syncinc!");
-			 _after_sync_cb = Module.cwrap('main_after_fs_sync', 'void',['number']);
-			 _after_sync_cb(0);
-
-		 });
-
-	  );
+	//mount persistent file system
+	EM_ASM(
+		FS.mkdir('/userfs');
+		FS.mount(IDBFS, {}, '/userfs');
+
+		// sync from persistent state into memory and then
+		// run the 'main_after_fs_sync' function
+		FS.syncfs(true, function(err) {
+
+			if (err) {
+				Module.setStatus('Failed to load persistent data\nPlease allow (third-party) cookies');
+				Module.printErr('Failed to populate IDB file system: ' + err.message);
+				Module.exit();
+			} else {
+				Module.print('Successfully populated IDB file system');
+				ccall('main_after_fs_sync', 'void', []);
+			}
+		});
+	);
 
 
 	glutMainLoop();
 	glutMainLoop();
 
 

+ 3 - 9
platform/javascript/os_javascript.cpp

@@ -502,18 +502,12 @@ bool OS_JavaScript::main_loop_iterate() {
 
 
 		time_to_save_sync-=elapsed;
 		time_to_save_sync-=elapsed;
 
 
-		print_line("elapsed "+itos(elapsed)+" tts "+itos(time_to_save_sync));
-
 		if (time_to_save_sync<0) {
 		if (time_to_save_sync<0) {
 			//time to sync, for real
 			//time to sync, for real
-			// run 'success'
-			print_line("DOING SYNCH!");
 			EM_ASM(
 			EM_ASM(
-			  FS.syncfs(function (err) {
-			    assert(!err);
-				console.log("Synched!");
-			    //ccall('success', 'v');
-			  });
+				FS.syncfs(function(err) {
+					if (err) { Module.printErr('Failed to save IDB file system: ' + err.message); }
+				});
 			);
 			);
 		}
 		}