From 3690ebae7fe3b5590a3a5d7ab31d601a0bc88bd5 Mon Sep 17 00:00:00 2001 From: Dimitri Belopopsky Date: Fri, 25 Dec 2020 21:54:35 +0100 Subject: [PATCH] Add file drag and drop in wasm --- Cargo.toml | 9 ++ examples/web.rs | 61 ++++++++++++- src/event.rs | 6 ++ .../web/event_loop/window_target.rs | 28 ++++++ src/platform_impl/web/web_sys/canvas.rs | 91 ++++++++++++++++++- 5 files changed, 189 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c776cd0497..b50d0e7f3e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,20 +101,29 @@ features = [ "AddEventListenerOptions", 'CssStyleDeclaration', 'BeforeUnloadEvent', + 'Blob', 'Document', + 'DataTransfer', 'DomRect', + 'DragEvent', + 'DataTransferItemList', + 'DataTransferItem', 'Element', 'Event', 'EventTarget', + 'FileReader', + 'File', 'FocusEvent', 'HtmlCanvasElement', 'HtmlElement', + 'HtmlImageElement', 'KeyboardEvent', 'MediaQueryList', 'MediaQueryListEvent', 'MouseEvent', 'Node', 'PointerEvent', + 'Url', 'Window', 'WheelEvent' ] diff --git a/examples/web.rs b/examples/web.rs index 46b024ccf2d..a67468d4590 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -43,7 +43,66 @@ pub fn main() { *control_flow = ControlFlow::Wait; #[cfg(feature = "web-sys")] - log::debug!("{:?}", event); + { + use wasm_bindgen::closure::Closure; + use wasm_bindgen::JsCast; + use web_sys::{FileReader, HtmlImageElement, Url}; + + log::debug!("{:?}", event); + match &event { + Event::WindowEvent { + window_id, + event: WindowEvent::DroppedFile(file), + } => { + // let reader = FileReader::new().unwrap(); + // let f = Closure::wrap(Box::new(move |event: web_sys::Event| { + // let reader: FileReader = event.target().unwrap().dyn_into().unwrap(); + // let file = reader.result().unwrap(); + // log::debug!("dropped filed {:?}", file); + + // let window = web_sys::window().unwrap(); + // let document = window.document().unwrap(); + // let body = document.body().unwrap(); + + // let img = document.create_element("img").unwrap(); + // img.set_src(Url::create_object_url_with_blob(file)); + // img.set_height(60); + // img.set_onload(|| { + // Url::revoke_object_url(img.src()); + + // }); + + // body.append_child(&img) + // .expect("Append canvas to HTML body"); + + // }) as Box); + // reader.set_onload(Some(f.as_ref().unchecked_ref())); + // reader.read_as_array_buffer(&file.slice().unwrap()); + // f.forget(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let img = HtmlImageElement::new().unwrap(); + img.set_src(&Url::create_object_url_with_blob(file).unwrap()); + img.set_height(60); + + let f = Closure::wrap(Box::new(|event: web_sys::Event| { + let img: HtmlImageElement = event.target().unwrap().dyn_into().unwrap(); + Url::revoke_object_url(&img.src()); + }) as Box); + + img.set_onload(Some(f.as_ref().unchecked_ref())); + + f.forget(); + + body.append_child(&img).expect("Append img to body"); + } + + _ => {} + } + } #[cfg(feature = "stdweb")] std_web::console!(log, "%s", format!("{:?}", event)); diff --git a/src/event.rs b/src/event.rs index f39ddaac500..8d685a85b35 100644 --- a/src/event.rs +++ b/src/event.rs @@ -225,13 +225,19 @@ pub enum WindowEvent<'a> { /// /// When the user drops multiple files at once, this event will be emitted for each file /// separately. + #[cfg(not(feature = "web-sys"))] DroppedFile(PathBuf), + #[cfg(feature = "web-sys")] + DroppedFile(web_sys::File), /// A file is being hovered over the window. /// /// When the user hovers multiple files at once, this event will be emitted for each file /// separately. + #[cfg(not(feature = "web-sys"))] HoveredFile(PathBuf), + #[cfg(feature = "web-sys")] + HoveredFile(web_sys::File), /// A file was hovered, but has exited the window. /// diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 7818ac0d373..3c2d906b688 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -7,6 +7,7 @@ use crate::window::{Theme, WindowId}; use std::cell::RefCell; use std::clone::Clone; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; +use std::path::PathBuf; use std::rc::Rc; pub struct WindowTarget { @@ -193,6 +194,33 @@ impl WindowTarget { }); }); + let runner = self.runner.clone(); + canvas.on_file_drag_enter(move |file| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::HoveredFile(file), + }); + }); + + let runner = self.runner.clone(); + canvas.on_file_drag_exit(move || { + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::HoveredFileCancelled, + }); + }); + + let runner = self.runner.clone(); + canvas.on_file_drop(move |file| { + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::DroppedFile(file), + }); + }); + + let runner = self.runner.clone(); + canvas.on_file_drag_over(move || {}); + let runner = self.runner.clone(); let raw = canvas.raw().clone(); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ae2fe0e1868..dec72306f7c 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -11,7 +11,7 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, + AddEventListenerOptions, DragEvent, Event, File, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, WheelEvent, }; @@ -27,6 +27,10 @@ pub struct Canvas { on_received_character: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, + on_file_drag_enter: Option>, + on_file_drag_exit: Option>, + on_file_drop: Option>, + on_file_drag_over: Option>, on_dark_mode: Option, mouse_state: MouseState, } @@ -71,11 +75,13 @@ impl Canvas { MouseState::NoPointerEvent(mouse_handler::MouseHandler::new()) }; + let common = Common { + raw: canvas, + wants_fullscreen: Rc::new(RefCell::new(false)), + }; + Ok(Canvas { - common: Common { - raw: canvas, - wants_fullscreen: Rc::new(RefCell::new(false)), - }, + common, on_blur: None, on_focus: None, on_keyboard_release: None, @@ -84,6 +90,10 @@ impl Canvas { on_mouse_wheel: None, on_fullscreen_change: None, on_dark_mode: None, + on_file_drag_enter: None, + on_file_drag_exit: None, + on_file_drop: None, + on_file_drag_over: None, mouse_state, }) } @@ -280,6 +290,73 @@ impl Canvas { self.on_dark_mode = MediaQueryListHandle::new("(prefers-color-scheme: dark)", closure); } + pub fn on_file_drag_enter(&mut self, mut handler: F) + where + F: 'static + FnMut(File), + { + self.on_file_drag_enter = + Some(self.common.add_event("dragenter", move |event: DragEvent| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + let files = event.data_transfer().unwrap().items(); + for i in 0..files.length() { + + let file = files.get(i).unwrap(); + if file.kind() == "file" { + handler(file.get_as_file().unwrap().unwrap()); + } + + } + })); + } + + pub fn on_file_drag_exit(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_file_drag_exit = + Some(self.common.add_event("dragleave", move |event: DragEvent| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + handler(); + })); + } + + pub fn on_file_drop(&mut self, mut handler: F) + where + F: 'static + FnMut(File), + { + self.on_file_drop = Some(self.common.add_event("drop", move |event: DragEvent| { + event.prevent_default(); + event.stop_propagation(); + event.cancel_bubble(); + + let files = event.data_transfer().unwrap().items(); + + for i in 0..files.length() { + let file = files.get(i).unwrap(); + if file.kind() == "file" { + handler(file.get_as_file().unwrap().unwrap()); + } + } + + + })); + } + + pub fn on_file_drag_over(&mut self, mut handler: F) + where + F: 'static + FnMut(), + { + self.on_file_drag_over = + Some(self.common.add_event("dragover", move |event: DragEvent| { + event.prevent_default(); + })); + } + pub fn request_fullscreen(&self) { self.common.request_fullscreen() } @@ -297,6 +374,10 @@ impl Canvas { self.on_mouse_wheel = None; self.on_fullscreen_change = None; self.on_dark_mode = None; + self.on_file_drag_enter = None; + self.on_file_drag_exit = None; + self.on_file_drop = None; + self.on_file_drag_over = None; match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.remove_listeners(), MouseState::NoPointerEvent(h) => h.remove_listeners(),