Skip to content

Commit

Permalink
Add File-Open dialog to emscripten and wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugh Sanderson committed Sep 17, 2024
1 parent f1030f6 commit 8dc898a
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 15 deletions.
3 changes: 1 addition & 2 deletions acadnme/build.nmml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@
<appIntent value="android.intent.category.LEANBACK_LAUNCHER" />
</android>

<set name="nmeDest" if="rpi" />
<assets from="../bin/apps/" rename="${nmeDest}" if="mobile||web||rpi" less="nocompile" >
<assets from="../bin/apps/" unless="nocompile" >
<asset name="AcadnmeBoot.nme" />
<!--
<asset name="Flappybalt.nme" />
Expand Down
4 changes: 2 additions & 2 deletions project/ToolkitBuild.xml
Original file line number Diff line number Diff line change
Expand Up @@ -653,8 +653,8 @@
<vflag name="-framework" value="QuartzCore" />
</section>

<section if="HXCPP_JS_PRIME">
<vflag name="-s" value="EXPORTED_FUNCTIONS=['_malloc']" />
<section if="HXCPP_JS_PRIME || emscripten">
<vflag name="-s" value="EXPORTED_FUNCTIONS=['_malloc','_free','_main','ccall']" />
</section>

<section if="android">
Expand Down
127 changes: 125 additions & 2 deletions project/src/common/ExternalInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#ifdef ANDROID
#include <android/log.h>
#endif
#if defined(EMSCRIPTEN)
#include "emscripten.h"
#include <sys/stat.h>
#endif

#include <nme/NmeCffi.h>
#include <Utils.h>
Expand All @@ -43,7 +47,6 @@
#endif
#include <nme/NmeApi.h>


#ifdef min
#undef min
#undef max
Expand Down Expand Up @@ -5800,6 +5803,50 @@ DEFINE_PRIM(nme_file_dialog_folder,2);
*/


#ifdef EMSCRIPTEN
AutoGCRoot *openCallbackRoot = nullptr;

extern "C" {

EMSCRIPTEN_KEEPALIVE int nme_file_upload_complete(char const *filename, char const *mime_type, char *buffer, size_t buffer_size, void *callback_data) {
if (!openCallbackRoot)
return 0;
/// Load a file - this function is called from javascript when the file upload is activated
//printf("Got callback %s %p %s x %d!\n", filename, openCallbackRoot, mime_type, (int)buffer_size);

mkdir("/uploads/",S_IRWXU);
std::string localName = "/uploads/";
localName += filename;

FILE *file = fopen(localName.c_str(), "wb");
fwrite(buffer, buffer_size,1, file);
fclose(file);

value val = alloc_string(localName.c_str());

val_call1(openCallbackRoot->get(), val);

delete openCallbackRoot;
openCallbackRoot = nullptr;
return 1;
}

EMSCRIPTEN_KEEPALIVE int nme_file_upload_info( const char *event, double progress) {
if (!openCallbackRoot)
return 0;
//printf("File info: %s\n", event);
std::string type = event;
if (type=="abort" || type=="error")
{
delete openCallbackRoot;
openCallbackRoot = nullptr;
}
return 1;
}

}
#endif

bool nme_file_dialog_open(HxString inTitle, HxString inText, HxString inDefaultPath, HxString inTypes, value inCallback, int inFlags )
{
if (gCurrentFileDialog)
Expand All @@ -5823,8 +5870,84 @@ bool nme_file_dialog_open(HxString inTitle, HxString inText, HxString inDefaultP
}
else
return true;
#endif

#elif defined(EMSCRIPTEN)

//printf("Launch for types:%s.\n", inTypes.c_str());
std::string types(inTypes.c_str());
auto pos = types.find('|');
if (pos!=std::string::npos)
{
types = types.substr(pos+1);
pos = types.find('|');
if (pos!=std::string::npos)
types = types.substr(0,pos);
std::string newFilter = "";
const char *p = types.c_str();
const char *end = p + types.size();
while(p<end)
{
if (*p=='*')
{
p++;
const char *s = p;
while(*p!=';' && *p!='\n')
p++;
if (p>s)
{
if (newFilter.size())
newFilter += ",";
newFilter += std::string(s, p-s);
}
}
}
//printf("New filter: %s\n", newFilter.c_str());
types = newFilter;
}

delete openCallbackRoot;
openCallbackRoot = new AutoGCRoot(inCallback);
EM_ASM({
globalThis['my_callback'] = function(e) {
const file_reader = new FileReader();
file_reader.onload = (event) => {
const uint8Arr = new Uint8Array(event.target.result);
const data_ptr = Module['_malloc'](uint8Arr.length);
const data_on_heap = new Uint8Array(Module['HEAPU8'].buffer, data_ptr, uint8Arr.length);
data_on_heap.set(uint8Arr);
Module['ccall']('nme_file_upload_complete', 'number', ['string', 'string', 'number', 'number', 'number' ], [event.target.filename, event.target.mime_type, data_on_heap.byteOffset, uint8Arr.length]);
Module['_free'](data_ptr);
};
file_reader.onloadstart = (event) => {
Module['ccall']('nme_file_upload_info', 'number', ['string', 'number'], ['start',0]);
};
file_reader.onprogress = (event) => {
var frac = event.loaded/event.total;
Module['ccall']('nme_file_upload_info', 'number', ['string', 'number'], ['progress',frac]);
};
file_reader.onabort = (event) => {
Module['ccall']('nme_file_upload_info', 'number', ['string', 'number'], ['abort',0]);
};
file_reader.onerror = (event) => {
Module['ccall']('nme_file_upload_info', 'number', ['string', 'number'], ['error',0]);
};

file_reader.filename = e.target.files[0].name;
file_reader.mime_type = e.target.files[0].type;
file_reader.readAsArrayBuffer(e.target.files[0]);
};

var file_selector = document.createElement('input');
file_selector.setAttribute('type', 'file');
file_selector.setAttribute('onchange', 'globalThis["my_callback"](event)');
file_selector.setAttribute('accept', UTF8ToString($0));
file_selector.click();
}, types.c_str() );


return true;

#endif
return false;
}
DEFINE_PRIME6(nme_file_dialog_open);
Expand Down
34 changes: 25 additions & 9 deletions samples/AcadnmeBoot/src/AcadnmeBoot.hx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ import nme.utils.ByteArray;
import gm2d.Screen;
import nme.geom.Rectangle;
import gm2d.ui.Layout;
import gm2d.ui.TextLabel;
import gm2d.ui.TileControl;
import gm2d.ui.Widget;
import gm2d.ui.Button;
import gm2d.ui.TextInput;
import gm2d.ui.CheckButtons;
import gm2d.ui.Image;
import gm2d.ui.ListControl;
import gm2d.ui.*;
import gm2d.skin.FillStyle;
import gm2d.skin.LineStyle;
import gm2d.skin.Shape;
Expand All @@ -39,6 +32,7 @@ class AcadnmeBoot extends Screen implements IBoot
var serverEnabled:Bool;
var store:SharedObject;
var storeData:Dynamic;
var button:Button;
var nmeVersion:String;

public function new()
Expand Down Expand Up @@ -96,7 +90,7 @@ class AcadnmeBoot extends Screen implements IBoot
hostBar.setItemLayout( new HorizontalLayout([1,0]).stretch() );

hostBar.addWidget(new TextLabel("Host:" + getConnectionStatus(),{ textColor:accent, align:Layout.AlignCenterY|Layout.AlignStretch }) );
hostBar.addWidget( Button.BMPButton( createMenuIcon(), onMenu, { shape:ShapeNone } ) );
hostBar.addWidget( button = Button.BMPButton( createMenuIcon(), onMenu, { shape:ShapeNone } ) );
hostBar.build();

titleBar.addWidget(hostBar);
Expand Down Expand Up @@ -128,6 +122,26 @@ class AcadnmeBoot extends Screen implements IBoot
}

function onMenu()
{
var menuItemm = new MenuItem("Options");
menuItemm.add( new MenuItem("Settings..", onSettings ) );
menuItemm.add( new MenuItem("Open..", onOpen ) );

var popup = new PopupMenu(menuItemm);
var pos = button.localToGlobal( new Point(0,0) );
gm2d.Game.popup( popup, pos.x, pos.y);

}

function onOpen(_)
{
nme.system.Dialog.fileOpen("Select NME File", "Selct file to run", null, "Nme Files|*.nme", filename-> {
if (filename!=null)
launch(filename);
} );
}

function onSettings(_)
{
var panel = new gm2d.ui.Panel("Settings");
panel.addLabelUI("Enable Network", new CheckButtons(serverEnabled, onEnable) );
Expand Down Expand Up @@ -189,6 +203,8 @@ class AcadnmeBoot extends Screen implements IBoot
path = launchScript[k];
}
}
if (path==null && FileSystem.exists(name) )
path = name;
if (path==null)
return 'Unknown application $name';
haxe.Timer.delay( function() Acadnme.runScript(path), 0 );
Expand Down

0 comments on commit 8dc898a

Please sign in to comment.