diff --git a/hxd/DropFileEvent.hx b/hxd/DropFileEvent.hx new file mode 100644 index 000000000..929115375 --- /dev/null +++ b/hxd/DropFileEvent.hx @@ -0,0 +1,68 @@ +package hxd; + +import haxe.ds.ReadOnlyArray; +import haxe.io.Bytes; + +/** + The information about the dropped file. +**/ +abstract class DroppedFile { + /** + The dropped file name/path. + **/ + public var file(default, null) : String; + #if js + /** + The native JS data transfer file. + **/ + public var native(default, null) : js.html.File; + + #end + + public function new( file : String ) { + this.file = file; + } + + + /** + Retrieve the dropped file contents asynchronously and pass it to `callback`. + **/ + abstract public function getBytes( callback : (data : Bytes) -> Void ) : Void; +} + +/** + The drag&drop operation event. + + @see `hxd.Window.addDragAndDropTarget` + @see `hxd.Window.removeDragAndDropTarget` +**/ +class DropFileEvent { + /** + The list of the files that were dropped. + + Only guaranteed to be populated when `kind == Drop`. + **/ + public var files(default, null): ReadOnlyArray; + /** + The first dropped file. Alias to `files[0]`. + **/ + public var file(get, never): Null; + /** + The X position inside the window at which the file was dropped. + **/ + public var dropX(default, null): Int; + /** + The Y position inside the window at which the file was dropped. + **/ + public var dropY(default, null): Int; + + public function new( files : Array, dx : Int, dy : Int ) { + this.files = files; + this.dropX = dx; + this.dropY = dy; + } + + inline function get_file() return files[0]; + +} + diff --git a/hxd/Window.hl.hx b/hxd/Window.hl.hx index 54a922354..15c535947 100644 --- a/hxd/Window.hl.hx +++ b/hxd/Window.hl.hx @@ -30,11 +30,19 @@ typedef DisplaySetting = { framerate : Int } +private class NativeDroppedFile extends hxd.DropFileEvent.DroppedFile { + public function getBytes( callback : ( data : haxe.io.Bytes ) -> Void ) { + haxe.Timer.delay(() -> callback(sys.io.File.getBytes(file)), 1); + } +} + //@:coreApi class Window { var resizeEvents : List Void>; var eventTargets : List Void>; + var dropTargets : List Void>; + var dropFiles : Array; public var width(get, never) : Int; public var height(get, never) : Int; @@ -92,6 +100,7 @@ class Window { this.windowHeight = height; eventTargets = new List(); resizeEvents = new List(); + dropTargets = new List(); #if hlsdl var sdlFlags = if (!fixed) sdl.Window.SDL_WINDOW_SHOWN | sdl.Window.SDL_WINDOW_RESIZABLE else sdl.Window.SDL_WINDOW_SHOWN; #if heaps_vulkan @@ -168,6 +177,32 @@ class Window { for( f in resizeEvents ) f(); } + public function addDragAndDropTarget( f : ( event : DropFileEvent ) -> Void ) : Void { + if (dropTargets.length == 0) { + #if (hlsdl >= version("1.14.0")) + sdl.Sdl.setDragAndDropEnabled(true); + #elseif (hldx >= version("1.14.0")) + window.dragAndDropEnabled = true; + #end + } + dropTargets.push(f); + } + + public function removeDragAndDropTarget( f : ( event : DropFileEvent ) -> Void ) : Void { + for( e in dropTargets ) + if( Reflect.compareMethods(e, f) ) { + dropTargets.remove(f); + break; + } + if ( dropTargets.length == 0 ) { + #if (hlsdl >= version("1.14.0")) + sdl.Sdl.setDragAndDropEnabled(false); + #elseif (hldx >= version("1.14.0")) + window.dragAndDropEnabled = false; + #end + } + } + public function setCursorPos( x : Int, y : Int, emitEvent : Bool = false ) : Void { #if hldx if (mouseMode == Absolute) window.setCursorPosition(x, y); @@ -439,6 +474,7 @@ class Window { #end eh = new Event(ERelease, e.mouseX, e.mouseY); eh.touchId = e.fingerId; + #elseif hldx case KeyDown: eh = new Event(EKeyDown); @@ -458,6 +494,27 @@ class Window { eh = new Event(ETextInput, mouseX, mouseY); eh.charCode = e.keyCode; #end + #if (hlsdl >= version("1.14.0") || hldx >= version("1.14.0")) + case DropStart: + dropFiles = []; + case DropFile: + #if hlsdl + dropFiles.push(new NativeDroppedFile(@:privateAccess String.fromUTF8(e.dropFile))); + #else + dropFiles.push(new NativeDroppedFile(@:privateAccess String.fromUCS2(e.dropFile))); + #end + case DropEnd: + var event = new DropFileEvent( + dropFiles, + #if hldx + e.mouseX, e.mouseY + #else + mouseX, mouseY + #end + ); + for ( dt in dropTargets ) dt(event); + dropFiles = null; + #end case Quit: return onClose(); default: diff --git a/hxd/Window.hx b/hxd/Window.hx index 1935924af..9c394b0dc 100644 --- a/hxd/Window.hx +++ b/hxd/Window.hx @@ -92,6 +92,18 @@ class Window { public function resize( width : Int, height : Int ) : Void { } + /** + Add a drag&drop events callback. + **/ + public function addDragAndDropTarget( f : ( event : DropFileEvent ) -> Void ) : Void { + } + + /** + Remove a drag&drop events callback. + **/ + public function removeDragAndDropTarget( f : ( event : DropFileEvent ) -> Void ) : Void { + } + @:deprecated("Use the displayMode property instead") public function setFullScreen( v : Bool ) : Void { } diff --git a/hxd/Window.js.hx b/hxd/Window.js.hx index f2e5ee7bf..22aa566ce 100644 --- a/hxd/Window.js.hx +++ b/hxd/Window.js.hx @@ -10,10 +10,27 @@ enum DisplayMode { FullscreenResize; } +private class NativeDroppedFile extends hxd.DropFileEvent.DroppedFile { + + public function new( native : js.html.File ) { + super(native.name); + this.native = native; + } + + public function getBytes( callback : ( data : haxe.io.Bytes ) -> Void ) { + var reader = new js.html.FileReader(); + reader.onload = (_) -> callback(haxe.io.Bytes.ofData(reader.result)); + reader.onerror = (_) -> callback(null); + reader.readAsArrayBuffer(native); + } + +} + class Window { var resizeEvents : List Void>; var eventTargets : List Void>; + var dropTargets : List Void>; public var width(get, never) : Int; public var height(get, never) : Int; @@ -69,6 +86,7 @@ class Window { var customCanvas = canvas != null; eventTargets = new List(); resizeEvents = new List(); + dropTargets = new List(); if( !js.Browser.supported ) { canvasPos = { "width":0, "top":0, "left":0, "height":0 }; @@ -221,6 +239,40 @@ class Window { public function resize( width : Int, height : Int ) : Void { } + public function addDragAndDropTarget( f : ( event : DropFileEvent ) -> Void ) : Void { + if( dropTargets.length == 0 ) { + var element = canvas; // Probably should adhere to `globalEvents`? + element.addEventListener("dragover", handleDragAndDropEvent); + element.addEventListener("drop", handleDragAndDropEvent); + } + dropTargets.add(f); + } + + public function removeDragAndDropTarget( f : ( event : DropFileEvent ) -> Void ) : Void { + for( e in dropTargets ) + if( Reflect.compareMethods(e, f) ) { + dropTargets.remove(f); + break; + } + if( dropTargets.length == 0 ) { + var element = canvas; // Probably should adhere to `globalEvents`? + element.removeEventListener("dragover", handleDragAndDropEvent); + element.removeEventListener("drop", handleDragAndDropEvent); + } + } + + function handleDragAndDropEvent( e : js.html.DragEvent ) { + e.preventDefault(); + if ( e.type == "dragover" || e.dataTransfer == null || e.dataTransfer.files.length == 0 ) return; + var ev = new DropFileEvent([ + for ( file in e.dataTransfer.files ) new NativeDroppedFile(file) + ], + Math.round((e.clientX - canvasPos.left) * getPixelRatio()), + Math.round((e.clientY - canvasPos.top) * getPixelRatio()) + ); + for( dt in dropTargets ) dt(ev); + } + @:deprecated("Use the displayMode property instead") public function setFullScreen( v : Bool ) : Void { var doc = js.Browser.document;