changeset 2616:cbf5a01e429e

Add an HTML/JS file picker to web build
author Michael Pavone <pavone@retrodev.com>
date Fri, 21 Feb 2025 00:45:14 -0800
parents cbd54de385d3
children ad9e074c8901
files Makefile nuklear_ui/blastem_nuklear.c
diffstat 2 files changed, 122 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Feb 17 23:40:36 2025 -0800
+++ b/Makefile	Fri Feb 21 00:45:14 2025 -0800
@@ -119,7 +119,7 @@
 
 ifeq ($(CPU),wasm)
 CFLAGS+= --use-port=sdl2
-LDFLAGS+= --use-port=sdl2 --embed-file rom.db --embed-file default.cfg --embed-file systems.cfg --embed-file shaders/ --embed-file images/ --embed-file DroidSans.ttf --embed-file roms/
+LDFLAGS+= --use-port=sdl2 --embed-file rom.db --embed-file default.cfg --embed-file systems.cfg --embed-file shaders/ --embed-file images/ --embed-file DroidSans.ttf -sEXPORTED_FUNCTIONS=_main,_handle_chooser_result -sEXPORTED_RUNTIME_METHODS=ccall,cwrap
 EXE:=.html
 else #CPU=wasm
 
--- a/nuklear_ui/blastem_nuklear.c	Mon Feb 17 23:40:36 2025 -0800
+++ b/nuklear_ui/blastem_nuklear.c	Fri Feb 21 00:45:14 2025 -0800
@@ -105,9 +105,87 @@
 static const char **browser_ext_list;
 static uint32_t browser_num_exts;
 static uint8_t use_native_filechooser;
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+static uint8_t chooser_open;
 
-static void handle_chooser_result(uint8_t normal_open, char *full_path)
+EM_JS(void, show_html_chooser, (const char *title, const char *extensions, int normal_open, int is_settings), {
+	let container = document.getElementById('chooser');
+	let canvas = document.getElementById('canvas');
+	let fileIn = null;
+	let titleEl = null;
+	if (!container) {
+		container = document.createElement('div');
+		container.id = 'chooser';
+		container.style.position = 'absolute';
+		container.style.display = 'none';
+		container.style.borderWidth = '2px';
+		container.style.borderColor = 'black';
+		titleEl = document.createElement('h3');
+		titleEl.id = 'chooser_title';
+		container.appendChild(titleEl);
+		fileIn = document.createElement('input');
+		fileIn.type = 'file';
+		fileIn.id = 'file';
+		container.appendChild(fileIn);
+		canvas.parentNode.appendChild(container);
+	} else {
+		fileIn = document.getElementById('file');
+		titleEl = document.getElementById('chooser_title');
+	}
+	titleEl.innerText = UTF8ToString(title);
+	fileIn.onchange = (event) => {
+		let f = event.target;
+		if (f.files.length) {
+			let reader = new FileReader();
+			let name = f.files[0].name;
+			reader.onload = (event) => {
+				let prefix = '/roms';
+				let prevPath = null;
+				if (normal_open) {
+					prevPath = 'previousRomPath';
+				} else if (is_settings) {
+					prefix = '/firmware';
+				} else {
+					prevPath = 'previousSpecialPath';
+				}
+				if (prevPath && window[prevPath]) {
+					FS.unlink(window[prevPath]);
+				} else {
+					FS.mkdir(prefix);
+				}
+				
+				let buffer = new Uint8Array(event.target.result);
+				FS.createDataFile(prefix, name, buffer, true, false, false);
+				let fullPath = prefix + "/" + name;
+				if (prevPath) {
+					window[prevPath] = fullPath;
+				}
+				console.log(fullPath, normal_open, is_settings);
+				document.getElementById('chooser').style.display = 'none';
+				Module.ccall('handle_chooser_result', 'void', ['number', 'string'], [normal_open, fullPath]);
+			};
+			reader.readAsArrayBuffer(f.files[0]);
+		}
+	};
+	fileIn.accept = UTF8ToString(extensions);
+	let cRect = canvas.getBoundingClientRect();
+	let pRect = canvas.parentNode.parentNode.getBoundingClientRect();
+	container.style.top = '' + (cRect.top - pRect.top) + 'px';
+	container.style.left = '' + (cRect.left - pRect.left) + 'px';
+	container.style.width = '' + cRect.width + 'px';
+	container.style.height = '' + cRect.height + 'px';
+	container.style.display = 'block';
+	container.style.backgroundColor = 'white';
+});
+#endif
+
+void handle_chooser_result(uint8_t normal_open, char *full_path)
 {
+#ifdef __EMSCRIPTEN__
+	chooser_open = 0;
+	full_path = strdup(full_path);
+#endif
 	if(normal_open) {
 		lockon_media(NULL);
 		if (current_system) {
@@ -134,12 +212,49 @@
 
 void view_file_browser(struct nk_context *context, uint8_t normal_open)
 {
+	static const char **ext_list;
+	static uint32_t num_exts;
+	static uint8_t got_ext_list;
+	if (!browser_ext_list) {
+		if (!got_ext_list) {
+			ext_list = (const char **)get_extension_list(config, &num_exts);
+			got_ext_list = 1;
+		}
+		browser_ext_list = ext_list;
+		browser_num_exts = num_exts;
+	}
+#ifdef __EMSCRIPTEN__
+	uint8_t just_opened = !chooser_open;
+	chooser_open = 1;
+	if (just_opened) {
+		size_t total_length = 0;
+		for (uint32_t i = 0; i < browser_num_exts; i++)
+		{
+			total_length += 1 + strlen(browser_ext_list[i]);
+			if (i) {
+				total_length++;
+			}
+		}
+		char *list = calloc(total_length + 1, 1);
+		char *cur = list;
+		for (uint32_t i = 0; i < browser_num_exts; i++)
+		{
+			if (i) {
+				*(cur++) = ',';
+			}
+			*(cur++) = '.';
+			size_t len = strlen(browser_ext_list[i]);
+			memcpy(cur, browser_ext_list[i], len);
+			cur += len;
+		}
+		*(cur) = 0;
+		show_html_chooser(browser_label, list, normal_open, browser_setting_path != NULL);
+		free(list);
+	}
+#else
 	static dir_entry *entries;
 	static size_t num_entries;
 	static int32_t selected_entry = -1;
-	static const char **ext_list;
-	static uint32_t num_exts;
-	static uint8_t got_ext_list;
 	if (!browser_cur_path) {
 		get_initial_browse_path(&browser_cur_path);
 	}
@@ -170,14 +285,6 @@
 			num_entries = 1;
 		}
 	}
-	if (!browser_ext_list) {
-		if (!got_ext_list) {
-			ext_list = (const char **)get_extension_list(config, &num_exts);
-			got_ext_list = 1;
-		}
-		browser_ext_list = ext_list;
-		browser_num_exts = num_exts;
-	}
 	uint32_t width = render_width();
 	uint32_t height = render_height();
 	if (nk_begin(context, "Load ROM", nk_rect(0, 0, width, height), 0)) {
@@ -228,6 +335,8 @@
 		}
 		nk_end(context);
 	}
+#endif
+	
 }
 
 void view_load(struct nk_context *context)