From 4af11255416f2b8c05c9190e16f56a822c6a3636 Mon Sep 17 00:00:00 2001 From: yiyuezhuo Date: Sun, 8 May 2022 01:11:47 +0800 Subject: [PATCH] Refactor using better Godot and JavaScript API and add an example --- README.md | 7 ++ addons/HTML5FileExchange/HTML5FileExchange.gd | 112 +++++------------- addons/HTML5FileExchange/Sample.gd | 19 +++ addons/HTML5FileExchange/sample.tscn | 35 ++++++ default_env.tres | 7 ++ icon.png | Bin 0 -> 3305 bytes icon.png.import | 35 ++++++ project.godot | 31 +++++ 8 files changed, 166 insertions(+), 80 deletions(-) create mode 100644 addons/HTML5FileExchange/Sample.gd create mode 100644 addons/HTML5FileExchange/sample.tscn create mode 100644 default_env.tres create mode 100644 icon.png create mode 100644 icon.png.import create mode 100644 project.godot diff --git a/README.md b/README.md index bd3ea14..bdd2590 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ # HTML5 File Exchange for Godot 3.2 + +## Usage + +* Copy addons to your project. +* Enable the plugin. (A `HTML5File` singleton should be added into autoload list). +* Add a HTML5 export item to enable HTML5 debugging option. +* Check sample.tscn for usage. \ No newline at end of file diff --git a/addons/HTML5FileExchange/HTML5FileExchange.gd b/addons/HTML5FileExchange/HTML5FileExchange.gd index 3d7ba80..637356c 100644 --- a/addons/HTML5FileExchange/HTML5FileExchange.gd +++ b/addons/HTML5FileExchange/HTML5FileExchange.gd @@ -1,24 +1,21 @@ extends Node -signal InFocus +signal read_completed +signal load_completed(image) + +var js_callback = JavaScript.create_callback(self, "load_handler"); +var js_interface; func _ready(): if OS.get_name() == "HTML5" and OS.has_feature('JavaScript'): _define_js() - - -func _notification(notification:int) -> void: - if notification == MainLoop.NOTIFICATION_WM_FOCUS_IN: - emit_signal("InFocus") + js_interface = JavaScript.get_interface("_HTML5FileExchange"); func _define_js()->void: #Define JS script JavaScript.eval(""" - var fileData; - var fileType; - var fileName; - var canceled; - function upload() { + var _HTML5FileExchange = {}; + _HTML5FileExchange.upload = function(gd_callback) { canceled = true; var input = document.createElement('INPUT'); input.setAttribute("type", "file"); @@ -29,56 +26,32 @@ func _define_js()->void: canceled = false;} var file = event.target.files[0]; var reader = new FileReader(); - fileType = file.type; - fileName = file.name; + this.fileType = file.type; + // var fileName = file.name; reader.readAsArrayBuffer(file); - reader.onloadend = function (evt) { + reader.onloadend = (evt) => { // Since here's it's arrow function, "this" still refers to _HTML5FileExchange if (evt.target.readyState == FileReader.DONE) { - fileData = evt.target.result; + this.result = evt.target.result; + gd_callback(); // It's hard to retrieve value from callback argument, so it's just for notification } } }); } - function download(fileName, byte) { - var buffer = Uint8Array.from(byte); - var blob = new Blob([buffer], { type: 'image/png'}); - var link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = fileName; - link.click(); - }; """, true) + +func load_handler(_args): + emit_signal("read_completed") - -func load_image()->Image: +func load_image(): if OS.get_name() != "HTML5" or !OS.has_feature('JavaScript'): return - - #Execute js function - JavaScript.eval("upload();", true) #opens promt for choosing file + + js_interface.upload(js_callback); + + yield(self, "read_completed") - #label.text = "Wait for focus" - yield(self, "InFocus") #wait until js promt is closed - - #label.text = "Timer on for loading" - yield(get_tree().create_timer(0.1), "timeout") #give some time for async js data load - - if JavaScript.eval("canceled;", true): # if File Dialog closed w/o file - #label.text = "Canceled prompt" - return - - # use data from png data - #label.text = "Load image" - var imageData - while true: - imageData = JavaScript.eval("fileData;", true) - if imageData != null: - break - #label.text = "No image yet" - yield(get_tree().create_timer(1.0), "timeout") #need more time to load data - - var imageType = JavaScript.eval("fileType;", true) - var imageName = JavaScript.eval("fileName;", true) + var imageType = js_interface.fileType; + var imageData = JavaScript.eval("_HTML5FileExchange.result", true) # interface doesn't work as expected for some reason var image = Image.new() var image_error @@ -90,39 +63,18 @@ func load_image()->Image: "image/webp": image_error = image.load_webp_from_buffer(imageData) var invalidType: - #label.text = "Unsupported file format - %s." % invalidType + print("Unsupported file format - %s." % invalidType) return + if image_error: - #label.text = "An error occurred while trying to display the image." - return - else: - return image - # Display texture - var tex = ImageTexture.new() - tex.create_from_image(image, 0) # Flag = 0 or else export is fucked! - Sprite.texture = tex - #loadedImage = image # Keep Image for later, just in case... - #loadedImageName = imageName - #label.text = "Image %s loaded as %s." % [imageName, imageType] - return - #label.text = "Something went wrong" + print("An error occurred while trying to display the image.") + + emit_signal("load_completed", image) - -func save_image(image:Image, fileName:String = "export")->void: +func save_image(image:Image, fileName:String = "export.png")->void: if OS.get_name() != "HTML5" or !OS.has_feature('JavaScript'): return - + image.clear_mipmaps() - if image.save_png("user://export_temp.png"): - #label.text = "Error saving temp file" - return - var file:File = File.new() - if file.open("user://export_temp.png", File.READ): - #label.text = "Error opening file" - return - var pngData = Array(file.get_buffer(file.get_len())) #read data as PoolByteArray and convert it to Array for JS - file.close() - var dir = Directory.new() - dir.remove("user://export_temp.png") - JavaScript.eval("download('%s', %s);" % [fileName, str(pngData)], true) - #label.text = "Saving DONE" + var buffer = image.save_png_to_buffer() + JavaScript.download_buffer(buffer, fileName) diff --git a/addons/HTML5FileExchange/Sample.gd b/addons/HTML5FileExchange/Sample.gd new file mode 100644 index 0000000..c0f6753 --- /dev/null +++ b/addons/HTML5FileExchange/Sample.gd @@ -0,0 +1,19 @@ +extends TextureRect + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + +func _on_UploadButton_pressed(): + HTML5File.load_image() + + var image = yield(HTML5File, "load_completed") + + var tex = ImageTexture.new() + tex.create_from_image(image, 0) + + texture = tex; + +func _on_DownloadButton_pressed(): + var image = texture.get_data() + HTML5File.save_image(image, "image.png") diff --git a/addons/HTML5FileExchange/sample.tscn b/addons/HTML5FileExchange/sample.tscn new file mode 100644 index 0000000..b3c389a --- /dev/null +++ b/addons/HTML5FileExchange/sample.tscn @@ -0,0 +1,35 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/HTML5FileExchange/Sample.gd" type="Script" id=1] + +[node name="Control" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +margin_right = 40.0 +margin_bottom = 40.0 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +margin_right = 136.0 +margin_bottom = 20.0 + +[node name="UploadButton" type="Button" parent="VBoxContainer/HBoxContainer"] +margin_right = 57.0 +margin_bottom = 20.0 +text = "Upload" + +[node name="DownloadButton" type="Button" parent="VBoxContainer/HBoxContainer"] +margin_left = 61.0 +margin_right = 136.0 +margin_bottom = 20.0 +text = "Download" + +[node name="TextureRect" type="TextureRect" parent="VBoxContainer"] +margin_top = 24.0 +margin_right = 136.0 +margin_bottom = 24.0 +script = ExtResource( 1 ) + +[connection signal="pressed" from="VBoxContainer/HBoxContainer/UploadButton" to="VBoxContainer/TextureRect" method="_on_UploadButton_pressed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/DownloadButton" to="VBoxContainer/TextureRect" method="_on_DownloadButton_pressed"] diff --git a/default_env.tres b/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c98fbb601c83c81ec8c22b1dba7d1d57c62b323c GIT binary patch literal 3305 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 literal 0 HcmV?d00001 diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 0000000..a4c02e6 --- /dev/null +++ b/icon.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..251c54a --- /dev/null +++ b/project.godot @@ -0,0 +1,31 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +[application] + +config/name="HTML5-File-Exchange-for-Godot" +run/main_scene="res://addons/HTML5FileExchange/sample.tscn" +config/icon="res://icon.png" + +[autoload] + +HTML5File="*res://addons/HTML5FileExchange/HTML5FileExchange.gd" + +[editor_plugins] + +enabled=PoolStringArray( "res://addons/HTML5FileExchange/plugin.cfg" ) + +[physics] + +common/enable_pause_aware_picking=true + +[rendering] + +environment/default_environment="res://default_env.tres"