Skip to content

Commit 3690eba

Browse files
committed
Add file drag and drop in wasm
1 parent 38fcceb commit 3690eba

File tree

5 files changed

+189
-6
lines changed

5 files changed

+189
-6
lines changed

Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,29 @@ features = [
101101
"AddEventListenerOptions",
102102
'CssStyleDeclaration',
103103
'BeforeUnloadEvent',
104+
'Blob',
104105
'Document',
106+
'DataTransfer',
105107
'DomRect',
108+
'DragEvent',
109+
'DataTransferItemList',
110+
'DataTransferItem',
106111
'Element',
107112
'Event',
108113
'EventTarget',
114+
'FileReader',
115+
'File',
109116
'FocusEvent',
110117
'HtmlCanvasElement',
111118
'HtmlElement',
119+
'HtmlImageElement',
112120
'KeyboardEvent',
113121
'MediaQueryList',
114122
'MediaQueryListEvent',
115123
'MouseEvent',
116124
'Node',
117125
'PointerEvent',
126+
'Url',
118127
'Window',
119128
'WheelEvent'
120129
]

examples/web.rs

+60-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,66 @@ pub fn main() {
4343
*control_flow = ControlFlow::Wait;
4444

4545
#[cfg(feature = "web-sys")]
46-
log::debug!("{:?}", event);
46+
{
47+
use wasm_bindgen::closure::Closure;
48+
use wasm_bindgen::JsCast;
49+
use web_sys::{FileReader, HtmlImageElement, Url};
50+
51+
log::debug!("{:?}", event);
52+
match &event {
53+
Event::WindowEvent {
54+
window_id,
55+
event: WindowEvent::DroppedFile(file),
56+
} => {
57+
// let reader = FileReader::new().unwrap();
58+
// let f = Closure::wrap(Box::new(move |event: web_sys::Event| {
59+
// let reader: FileReader = event.target().unwrap().dyn_into().unwrap();
60+
// let file = reader.result().unwrap();
61+
// log::debug!("dropped filed {:?}", file);
62+
63+
// let window = web_sys::window().unwrap();
64+
// let document = window.document().unwrap();
65+
// let body = document.body().unwrap();
66+
67+
// let img = document.create_element("img").unwrap();
68+
// img.set_src(Url::create_object_url_with_blob(file));
69+
// img.set_height(60);
70+
// img.set_onload(|| {
71+
// Url::revoke_object_url(img.src());
72+
73+
// });
74+
75+
// body.append_child(&img)
76+
// .expect("Append canvas to HTML body");
77+
78+
// }) as Box<dyn FnMut(_)>);
79+
// reader.set_onload(Some(f.as_ref().unchecked_ref()));
80+
// reader.read_as_array_buffer(&file.slice().unwrap());
81+
// f.forget();
82+
83+
let window = web_sys::window().unwrap();
84+
let document = window.document().unwrap();
85+
let body = document.body().unwrap();
86+
87+
let img = HtmlImageElement::new().unwrap();
88+
img.set_src(&Url::create_object_url_with_blob(file).unwrap());
89+
img.set_height(60);
90+
91+
let f = Closure::wrap(Box::new(|event: web_sys::Event| {
92+
let img: HtmlImageElement = event.target().unwrap().dyn_into().unwrap();
93+
Url::revoke_object_url(&img.src());
94+
}) as Box<dyn FnMut(_)>);
95+
96+
img.set_onload(Some(f.as_ref().unchecked_ref()));
97+
98+
f.forget();
99+
100+
body.append_child(&img).expect("Append img to body");
101+
}
102+
103+
_ => {}
104+
}
105+
}
47106

48107
#[cfg(feature = "stdweb")]
49108
std_web::console!(log, "%s", format!("{:?}", event));

src/event.rs

+6
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,19 @@ pub enum WindowEvent<'a> {
225225
///
226226
/// When the user drops multiple files at once, this event will be emitted for each file
227227
/// separately.
228+
#[cfg(not(feature = "web-sys"))]
228229
DroppedFile(PathBuf),
230+
#[cfg(feature = "web-sys")]
231+
DroppedFile(web_sys::File),
229232

230233
/// A file is being hovered over the window.
231234
///
232235
/// When the user hovers multiple files at once, this event will be emitted for each file
233236
/// separately.
237+
#[cfg(not(feature = "web-sys"))]
234238
HoveredFile(PathBuf),
239+
#[cfg(feature = "web-sys")]
240+
HoveredFile(web_sys::File),
235241

236242
/// A file was hovered, but has exited the window.
237243
///

src/platform_impl/web/event_loop/window_target.rs

+28
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::window::{Theme, WindowId};
77
use std::cell::RefCell;
88
use std::clone::Clone;
99
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
10+
use std::path::PathBuf;
1011
use std::rc::Rc;
1112

1213
pub struct WindowTarget<T: 'static> {
@@ -193,6 +194,33 @@ impl<T> WindowTarget<T> {
193194
});
194195
});
195196

197+
let runner = self.runner.clone();
198+
canvas.on_file_drag_enter(move |file| {
199+
runner.send_event(Event::WindowEvent {
200+
window_id: WindowId(id),
201+
event: WindowEvent::HoveredFile(file),
202+
});
203+
});
204+
205+
let runner = self.runner.clone();
206+
canvas.on_file_drag_exit(move || {
207+
runner.send_event(Event::WindowEvent {
208+
window_id: WindowId(id),
209+
event: WindowEvent::HoveredFileCancelled,
210+
});
211+
});
212+
213+
let runner = self.runner.clone();
214+
canvas.on_file_drop(move |file| {
215+
runner.send_event(Event::WindowEvent {
216+
window_id: WindowId(id),
217+
event: WindowEvent::DroppedFile(file),
218+
});
219+
});
220+
221+
let runner = self.runner.clone();
222+
canvas.on_file_drag_over(move || {});
223+
196224
let runner = self.runner.clone();
197225
let raw = canvas.raw().clone();
198226

src/platform_impl/web/web_sys/canvas.rs

+86-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::rc::Rc;
1111

1212
use wasm_bindgen::{closure::Closure, JsCast};
1313
use web_sys::{
14-
AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
14+
AddEventListenerOptions, DragEvent, Event, File, FocusEvent, HtmlCanvasElement, KeyboardEvent,
1515
MediaQueryListEvent, MouseEvent, WheelEvent,
1616
};
1717

@@ -27,6 +27,10 @@ pub struct Canvas {
2727
on_received_character: Option<EventListenerHandle<dyn FnMut(KeyboardEvent)>>,
2828
on_mouse_wheel: Option<EventListenerHandle<dyn FnMut(WheelEvent)>>,
2929
on_fullscreen_change: Option<EventListenerHandle<dyn FnMut(Event)>>,
30+
on_file_drag_enter: Option<EventListenerHandle<dyn FnMut(DragEvent)>>,
31+
on_file_drag_exit: Option<EventListenerHandle<dyn FnMut(DragEvent)>>,
32+
on_file_drop: Option<EventListenerHandle<dyn FnMut(DragEvent)>>,
33+
on_file_drag_over: Option<EventListenerHandle<dyn FnMut(DragEvent)>>,
3034
on_dark_mode: Option<MediaQueryListHandle>,
3135
mouse_state: MouseState,
3236
}
@@ -71,11 +75,13 @@ impl Canvas {
7175
MouseState::NoPointerEvent(mouse_handler::MouseHandler::new())
7276
};
7377

78+
let common = Common {
79+
raw: canvas,
80+
wants_fullscreen: Rc::new(RefCell::new(false)),
81+
};
82+
7483
Ok(Canvas {
75-
common: Common {
76-
raw: canvas,
77-
wants_fullscreen: Rc::new(RefCell::new(false)),
78-
},
84+
common,
7985
on_blur: None,
8086
on_focus: None,
8187
on_keyboard_release: None,
@@ -84,6 +90,10 @@ impl Canvas {
8490
on_mouse_wheel: None,
8591
on_fullscreen_change: None,
8692
on_dark_mode: None,
93+
on_file_drag_enter: None,
94+
on_file_drag_exit: None,
95+
on_file_drop: None,
96+
on_file_drag_over: None,
8797
mouse_state,
8898
})
8999
}
@@ -280,6 +290,73 @@ impl Canvas {
280290
self.on_dark_mode = MediaQueryListHandle::new("(prefers-color-scheme: dark)", closure);
281291
}
282292

293+
pub fn on_file_drag_enter<F>(&mut self, mut handler: F)
294+
where
295+
F: 'static + FnMut(File),
296+
{
297+
self.on_file_drag_enter =
298+
Some(self.common.add_event("dragenter", move |event: DragEvent| {
299+
event.prevent_default();
300+
event.stop_propagation();
301+
event.cancel_bubble();
302+
303+
let files = event.data_transfer().unwrap().items();
304+
for i in 0..files.length() {
305+
306+
let file = files.get(i).unwrap();
307+
if file.kind() == "file" {
308+
handler(file.get_as_file().unwrap().unwrap());
309+
}
310+
311+
}
312+
}));
313+
}
314+
315+
pub fn on_file_drag_exit<F>(&mut self, mut handler: F)
316+
where
317+
F: 'static + FnMut(),
318+
{
319+
self.on_file_drag_exit =
320+
Some(self.common.add_event("dragleave", move |event: DragEvent| {
321+
event.prevent_default();
322+
event.stop_propagation();
323+
event.cancel_bubble();
324+
handler();
325+
}));
326+
}
327+
328+
pub fn on_file_drop<F>(&mut self, mut handler: F)
329+
where
330+
F: 'static + FnMut(File),
331+
{
332+
self.on_file_drop = Some(self.common.add_event("drop", move |event: DragEvent| {
333+
event.prevent_default();
334+
event.stop_propagation();
335+
event.cancel_bubble();
336+
337+
let files = event.data_transfer().unwrap().items();
338+
339+
for i in 0..files.length() {
340+
let file = files.get(i).unwrap();
341+
if file.kind() == "file" {
342+
handler(file.get_as_file().unwrap().unwrap());
343+
}
344+
}
345+
346+
347+
}));
348+
}
349+
350+
pub fn on_file_drag_over<F>(&mut self, mut handler: F)
351+
where
352+
F: 'static + FnMut(),
353+
{
354+
self.on_file_drag_over =
355+
Some(self.common.add_event("dragover", move |event: DragEvent| {
356+
event.prevent_default();
357+
}));
358+
}
359+
283360
pub fn request_fullscreen(&self) {
284361
self.common.request_fullscreen()
285362
}
@@ -297,6 +374,10 @@ impl Canvas {
297374
self.on_mouse_wheel = None;
298375
self.on_fullscreen_change = None;
299376
self.on_dark_mode = None;
377+
self.on_file_drag_enter = None;
378+
self.on_file_drag_exit = None;
379+
self.on_file_drop = None;
380+
self.on_file_drag_over = None;
300381
match &mut self.mouse_state {
301382
MouseState::HasPointerEvent(h) => h.remove_listeners(),
302383
MouseState::NoPointerEvent(h) => h.remove_listeners(),

0 commit comments

Comments
 (0)