Skip to content

Commit

Permalink
player: support dropping multiple files.
Browse files Browse the repository at this point in the history
Apparently hierarchy is not preserved with browser D&D so if a file in
subdir is not found, we try again using just the filename.
  • Loading branch information
mosra committed Sep 20, 2018
1 parent 7163cfe commit b1f561c
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 25 deletions.
78 changes: 69 additions & 9 deletions src/player/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/String.h>
#include <Magnum/Image.h>
#include <Magnum/Mesh.h>
#include <Magnum/PixelFormat.h>
Expand Down Expand Up @@ -194,7 +195,7 @@ class Player: public Platform::Application, public Interconnect::Receiver {

#ifdef CORRADE_TARGET_EMSCRIPTEN
/* Need to be public to be called from C (which is called from JS) */
void loadFile(const char* filename, Containers::ArrayView<const char> data);
void loadFile(const std::size_t totalCount, const char* filename, Containers::Array<char> data);
#endif

private:
Expand Down Expand Up @@ -229,6 +230,9 @@ class Player: public Platform::Application, public Interconnect::Receiver {
Shaders::Phong _texturedMaskShader{NoCreate};

/* Data loading */
#ifdef CORRADE_TARGET_EMSCRIPTEN
std::unordered_map<std::string, Containers::Array<char>> _droppedFiles;
#endif
PluginManager::Manager<Trade::AbstractImporter> _manager;
Containers::Optional<Data> _data;

Expand Down Expand Up @@ -518,18 +522,70 @@ void Player::updateAnimationTime(Int deciseconds) {
}

#ifdef CORRADE_TARGET_EMSCRIPTEN
void Player::loadFile(const char* filename, Containers::ArrayView<const char> data) {
void Player::loadFile(std::size_t totalCount, const char* filename, Containers::Array<char> data) {
_droppedFiles.emplace(filename, std::move(data));

Debug{} << "Dropped file" << _droppedFiles.size() << Debug::nospace << "/" << Debug::nospace << totalCount << filename;

/* We don't have all files, don't do anything yet */
if(_droppedFiles.size() != totalCount) return;

/* We have everything, find the top-level file */
const std::string* gltfFile = nullptr;
for(const auto& file: _droppedFiles) {
if(Utility::String::endsWith(file.first, ".gltf") ||
Utility::String::endsWith(file.first, ".glb")) {
if(gltfFile) {
Error{} << "More than one glTF file dropped";
_droppedFiles.clear();
return;
}

gltfFile = &file.first;
}
}

if(!gltfFile) {
Error{} << "No glTF file dropped";
_droppedFiles.clear();
return;
}

std::unique_ptr<Trade::AbstractImporter> importer =
_manager.loadAndInstantiate("TinyGltfImporter");
if(!importer) std::exit(1);

Debug{} << "Opening D&D data";
/* Make the extra files available to the importer */
importer->setFileCallback([](const std::string& filename,
Trade::ImporterFileCallbackPolicy, Player& player)
-> Containers::Optional<Containers::ArrayView<const char>>
{
auto found = player._droppedFiles.find(filename);

/* Not found: maybe it's referencing something from a subdirectory,
try just the filename */
if(found == player._droppedFiles.end()) {
const std::string relative = Utility::Directory::filename(filename);
found = player._droppedFiles.find(relative);
if(found == player._droppedFiles.end()) return {};

Warning{} << filename << "was not found, supplying" << relative << "instead";
}
return Containers::ArrayView<const char>{found->second};
}, *this);

Debug{} << "Loading glTF file" << *gltfFile;

/* Load file */
if(!importer->openData(data))
std::exit(4);
if(!importer->openFile(*gltfFile)) {
Error{} << "Can't load the file.";
return;
}

load(*gltfFile, *importer);

load(filename, *importer);
/* Clear all loaded files, not needed anymore */
_droppedFiles.clear();

Ui::Widget::hide({
_baseUiPlane->dropHint,
Expand Down Expand Up @@ -1128,9 +1184,13 @@ void Player::mouseScrollEvent(MouseScrollEvent& event) {

#ifdef CORRADE_TARGET_EMSCRIPTEN
extern "C" {
EMSCRIPTEN_KEEPALIVE void loadFile(const char* name, const char* data, const std::size_t dataSize);
void loadFile(const char* name, const char* data, const std::size_t dataSize) {
Magnum::app->loadFile(name, {data, dataSize});
EMSCRIPTEN_KEEPALIVE void loadFile(std::size_t totalCount, const char* name, char* data, std::size_t dataSize);
void loadFile(const std::size_t totalCount, const char* name, char* data, const std::size_t dataSize) {
/* Add the data to the storage and take ownership of it. Memory was
allocated using malloc() on JS side, so it needs to be freed using
free() on C++ side. */
Magnum::app->loadFile(totalCount, name, Corrade::Containers::Array<char>{
data, dataSize, [](char* ptr, std::size_t) { free(ptr); }});
}
}
#endif
Expand Down
37 changes: 21 additions & 16 deletions src/player/player.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,30 @@ <h1>Magnum glTF Player</h1>
event.preventDefault();

const files = event.dataTransfer.files;
if(!files || files.length !== 1) {
console.error("Unsupported files or content dropped.");
if(!files) {
console.error("No files dropped.");
return;
}

const fileReader = new FileReader();
fileReader.onload = function(event) {
/* TODO: isn't here way too much copying? */
const fileData = new Uint8Array(event.target.result);
const pointer = Module._malloc(fileData.length);
const data = new Uint8Array(Module.HEAPU8.buffer, pointer, fileData.length);
data.set(fileData);
Module.ccall('loadFile', null, ['string', 'number', 'number'], [files[0].name, pointer, fileData.length]);
Module._free(pointer);
};
fileReader.onerror = function() {
console.error("Unable to read file " + file.name);
};
fileReader.readAsArrayBuffer(files[0]);
/* Pass all files through to the player. Memory is allocated on the
JS side and freed on the C++ side to avoid needless copies. */
for(let i = 0; i != files.length; ++i) {
(function(file) {
const fileReader = new FileReader();
fileReader.onload = function(event) {
/* TODO: but still, isn't here way too much copying? */
const fileData = new Uint8Array(event.target.result);
const pointer = Module._malloc(fileData.length);
const data = new Uint8Array(Module.HEAPU8.buffer, pointer, fileData.length);
data.set(fileData);
Module.ccall('loadFile', null, ['number', 'string', 'number', 'number'], [files.length, file.name, pointer, fileData.length]);
};
fileReader.onerror = function() {
console.error("Unable to read file " + file.name);
};
fileReader.readAsArrayBuffer(file);
})(files[i]); /* this is how you do a capturing lambda?! ugh */
}
}

/* Done here instead of using Sdl2Application::setContainerCssClass() so
Expand Down

0 comments on commit b1f561c

Please sign in to comment.